/* Minification failed. Returning unminified contents.
(3150,5-6): run-time error JS1010: Expected identifier: {
(3150,62-63): run-time error JS1195: Expected expression: =
(3152,37-38): run-time error JS1004: Expected ';': {
(47000,51-52): run-time error JS1004: Expected ';': )
 */
/*! jQuery Timepicker Addon - v1.4 - 2013-08-11
* http://trentrichardson.com/examples/timepicker
* Copyright (c) 2013 Trent Richardson; Licensed MIT */
(function ($) {

	/*
	* Lets not redefine timepicker, Prevent "Uncaught RangeError: Maximum call stack size exceeded"
	*/
	$.ui.timepicker = $.ui.timepicker || {};
	if ($.ui.timepicker.version) {
		return;
	}

	/*
	* Extend jQueryUI, get it started with our version number
	*/
	$.extend($.ui, {
		timepicker: {
			version: "1.4"
		}
	});

	/* 
	* Timepicker manager.
	* Use the singleton instance of this class, $.timepicker, to interact with the time picker.
	* Settings for (groups of) time pickers are maintained in an instance object,
	* allowing multiple different settings on the same page.
	*/
	var Timepicker = function () {
		this.regional = []; // Available regional settings, indexed by language code
		this.regional[''] = { // Default regional settings
			currentText: 'Now',
			closeText: 'Done',
			amNames: ['AM', 'A'],
			pmNames: ['PM', 'P'],
			timeFormat: 'HH:mm',
			timeSuffix: '',
			timeOnlyTitle: 'Choose Time',
			timeText: 'Time',
			hourText: 'Hour',
			minuteText: 'Minute',
			secondText: 'Second',
			millisecText: 'Millisecond',
			microsecText: 'Microsecond',
			timezoneText: 'Time Zone',
			isRTL: false
		};
		this._defaults = { // Global defaults for all the datetime picker instances
			showButtonPanel: true,
			timeOnly: false,
			showHour: null,
			showMinute: null,
			showSecond: null,
			showMillisec: null,
			showMicrosec: null,
			showTimezone: null,
			showTime: true,
			stepHour: 1,
			stepMinute: 1,
			stepSecond: 1,
			stepMillisec: 1,
			stepMicrosec: 1,
			hour: 0,
			minute: 0,
			second: 0,
			millisec: 0,
			microsec: 0,
			timezone: null,
			hourMin: 0,
			minuteMin: 0,
			secondMin: 0,
			millisecMin: 0,
			microsecMin: 0,
			hourMax: 23,
			minuteMax: 59,
			secondMax: 59,
			millisecMax: 999,
			microsecMax: 999,
			minDateTime: null,
			maxDateTime: null,
			onSelect: null,
			hourGrid: 0,
			minuteGrid: 0,
			secondGrid: 0,
			millisecGrid: 0,
			microsecGrid: 0,
			alwaysSetTime: true,
			separator: ' ',
			altFieldTimeOnly: true,
			altTimeFormat: null,
			altSeparator: null,
			altTimeSuffix: null,
			pickerTimeFormat: null,
			pickerTimeSuffix: null,
			showTimepicker: true,
			timezoneList: null,
			addSliderAccess: false,
			sliderAccessArgs: null,
			controlType: 'slider',
			defaultValue: null,
			parse: 'strict'
		};
		$.extend(this._defaults, this.regional['']);
	};

	$.extend(Timepicker.prototype, {
		$input: null,
		$altInput: null,
		$timeObj: null,
		inst: null,
		hour_slider: null,
		minute_slider: null,
		second_slider: null,
		millisec_slider: null,
		microsec_slider: null,
		timezone_select: null,
		hour: 0,
		minute: 0,
		second: 0,
		millisec: 0,
		microsec: 0,
		timezone: null,
		hourMinOriginal: null,
		minuteMinOriginal: null,
		secondMinOriginal: null,
		millisecMinOriginal: null,
		microsecMinOriginal: null,
		hourMaxOriginal: null,
		minuteMaxOriginal: null,
		secondMaxOriginal: null,
		millisecMaxOriginal: null,
		microsecMaxOriginal: null,
		ampm: '',
		formattedDate: '',
		formattedTime: '',
		formattedDateTime: '',
		timezoneList: null,
		units: ['hour', 'minute', 'second', 'millisec', 'microsec'],
		support: {},
		control: null,

		/* 
		* Override the default settings for all instances of the time picker.
		* @param  {Object} settings  object - the new settings to use as defaults (anonymous object)
		* @return {Object} the manager object
		*/
		setDefaults: function (settings) {
			extendRemove(this._defaults, settings || {});
			return this;
		},

		/*
		* Create a new Timepicker instance
		*/
		_newInst: function ($input, opts) {
			var tp_inst = new Timepicker(),
				inlineSettings = {},
				fns = {},
				overrides, i;

			for (var attrName in this._defaults) {
				if (this._defaults.hasOwnProperty(attrName)) {
					var attrValue = $input.attr('time:' + attrName);
					if (attrValue) {
						try {
							inlineSettings[attrName] = eval(attrValue);
						} catch (err) {
							inlineSettings[attrName] = attrValue;
						}
					}
				}
			}

			overrides = {
				beforeShow: function (input, dp_inst) {
					if ($.isFunction(tp_inst._defaults.evnts.beforeShow)) {
						return tp_inst._defaults.evnts.beforeShow.call($input[0], input, dp_inst, tp_inst);
					}
				},
				onChangeMonthYear: function (year, month, dp_inst) {
					// Update the time as well : this prevents the time from disappearing from the $input field.
					tp_inst._updateDateTime(dp_inst);
					if ($.isFunction(tp_inst._defaults.evnts.onChangeMonthYear)) {
						tp_inst._defaults.evnts.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst);
					}
				},
				onClose: function (dateText, dp_inst) {
					if (tp_inst.timeDefined === true && $input.val() !== '') {
						tp_inst._updateDateTime(dp_inst);
					}
					if ($.isFunction(tp_inst._defaults.evnts.onClose)) {
						tp_inst._defaults.evnts.onClose.call($input[0], dateText, dp_inst, tp_inst);
					}
				}
			};
			for (i in overrides) {
				if (overrides.hasOwnProperty(i)) {
					fns[i] = opts[i] || null;
				}
			}

			tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, opts, overrides, {
				evnts: fns,
				timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker');
			});
			tp_inst.amNames = $.map(tp_inst._defaults.amNames, function (val) {
				return val.toUpperCase();
			});
			tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function (val) {
				return val.toUpperCase();
			});

			// detect which units are supported
			tp_inst.support = detectSupport(
					tp_inst._defaults.timeFormat + 
					(tp_inst._defaults.pickerTimeFormat ? tp_inst._defaults.pickerTimeFormat : '') +
					(tp_inst._defaults.altTimeFormat ? tp_inst._defaults.altTimeFormat : ''));

			// controlType is string - key to our this._controls
			if (typeof(tp_inst._defaults.controlType) === 'string') {
				if (tp_inst._defaults.controlType === 'slider' && typeof($.ui.slider) === 'undefined') {
					tp_inst._defaults.controlType = 'select';
				}
				tp_inst.control = tp_inst._controls[tp_inst._defaults.controlType];
			}
			// controlType is an object and must implement create, options, value methods
			else {
				tp_inst.control = tp_inst._defaults.controlType;
			}

			// prep the timezone options
			var timezoneList = [-720, -660, -600, -570, -540, -480, -420, -360, -300, -270, -240, -210, -180, -120, -60,
					0, 60, 120, 180, 210, 240, 270, 300, 330, 345, 360, 390, 420, 480, 525, 540, 570, 600, 630, 660, 690, 720, 765, 780, 840];
			if (tp_inst._defaults.timezoneList !== null) {
				timezoneList = tp_inst._defaults.timezoneList;
			}
			var tzl = timezoneList.length, tzi = 0, tzv = null;
			if (tzl > 0 && typeof timezoneList[0] !== 'object') {
				for (; tzi < tzl; tzi++) {
					tzv = timezoneList[tzi];
					timezoneList[tzi] = { value: tzv, label: $.timepicker.timezoneOffsetString(tzv, tp_inst.support.iso8601) };
				}
			}
			tp_inst._defaults.timezoneList = timezoneList;

			// set the default units
			tp_inst.timezone = tp_inst._defaults.timezone !== null ? $.timepicker.timezoneOffsetNumber(tp_inst._defaults.timezone) :
							((new Date()).getTimezoneOffset() * -1);
			tp_inst.hour = tp_inst._defaults.hour < tp_inst._defaults.hourMin ? tp_inst._defaults.hourMin :
							tp_inst._defaults.hour > tp_inst._defaults.hourMax ? tp_inst._defaults.hourMax : tp_inst._defaults.hour;
			tp_inst.minute = tp_inst._defaults.minute < tp_inst._defaults.minuteMin ? tp_inst._defaults.minuteMin :
							tp_inst._defaults.minute > tp_inst._defaults.minuteMax ? tp_inst._defaults.minuteMax : tp_inst._defaults.minute;
			tp_inst.second = tp_inst._defaults.second < tp_inst._defaults.secondMin ? tp_inst._defaults.secondMin :
							tp_inst._defaults.second > tp_inst._defaults.secondMax ? tp_inst._defaults.secondMax : tp_inst._defaults.second;
			tp_inst.millisec = tp_inst._defaults.millisec < tp_inst._defaults.millisecMin ? tp_inst._defaults.millisecMin :
							tp_inst._defaults.millisec > tp_inst._defaults.millisecMax ? tp_inst._defaults.millisecMax : tp_inst._defaults.millisec;
			tp_inst.microsec = tp_inst._defaults.microsec < tp_inst._defaults.microsecMin ? tp_inst._defaults.microsecMin :
							tp_inst._defaults.microsec > tp_inst._defaults.microsecMax ? tp_inst._defaults.microsecMax : tp_inst._defaults.microsec;
			tp_inst.ampm = '';
			tp_inst.$input = $input;

			if (tp_inst._defaults.altField) {
				tp_inst.$altInput = $(tp_inst._defaults.altField).css({
					cursor: 'pointer'
				}).focus(function () {
					$input.trigger("focus");
				});
			}

			if (tp_inst._defaults.minDate === 0 || tp_inst._defaults.minDateTime === 0) {
				tp_inst._defaults.minDate = new Date();
			}
			if (tp_inst._defaults.maxDate === 0 || tp_inst._defaults.maxDateTime === 0) {
				tp_inst._defaults.maxDate = new Date();
			}

			// datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime..
			if (tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) {
				tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime());
			}
			if (tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) {
				tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime());
			}
			if (tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) {
				tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime());
			}
			if (tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) {
				tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime());
			}
			tp_inst.$input.bind('focus', function () {
				tp_inst._onFocus();
			});

			return tp_inst;
		},

		/*
		* add our sliders to the calendar
		*/
		_addTimePicker: function (dp_inst) {
			var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ? this.$input.val() + ' ' + this.$altInput.val() : this.$input.val();

			this.timeDefined = this._parseTime(currDT);
			this._limitMinMaxDateTime(dp_inst, false);
			this._injectTimePicker();
		},

		/*
		* parse the time string from input value or _setTime
		*/
		_parseTime: function (timeString, withDate) {
			if (!this.inst) {
				this.inst = $.datepicker._getInst(this.$input[0]);
			}

			if (withDate || !this._defaults.timeOnly) {
				var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat');
				try {
					var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults);
					if (!parseRes.timeObj) {
						return false;
					}
					$.extend(this, parseRes.timeObj);
				} catch (err) {
					$.timepicker.log("Error parsing the date/time string: " + err +
									"\ndate/time string = " + timeString +
									"\ntimeFormat = " + this._defaults.timeFormat +
									"\ndateFormat = " + dp_dateFormat);
					return false;
				}
				return true;
			} else {
				var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults);
				if (!timeObj) {
					return false;
				}
				$.extend(this, timeObj);
				return true;
			}
		},

		/*
		* generate and inject html for timepicker into ui datepicker
		*/
		_injectTimePicker: function () {
			var $dp = this.inst.dpDiv,
				o = this.inst.settings,
				tp_inst = this,
				litem = '',
				uitem = '',
				show = null,
				max = {},
				gridSize = {},
				size = null,
				i = 0,
				l = 0;

			// Prevent displaying twice
			if ($dp.find("div.ui-timepicker-div").length === 0 && o.showTimepicker) {
				var noDisplay = ' style="display:none;"',
					html = '<div class="ui-timepicker-div' + (o.isRTL ? ' ui-timepicker-rtl' : '') + '"><dl>' + '<dt class="ui_tpicker_time_label"' + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' +
								'<dd class="ui_tpicker_time"' + ((o.showTime) ? '' : noDisplay) + '></dd>';

				// Create the markup
				for (i = 0, l = this.units.length; i < l; i++) {
					litem = this.units[i];
					uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1);
					show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem];

					// Added by Peter Medeiros:
					// - Figure out what the hour/minute/second max should be based on the step values.
					// - Example: if stepMinute is 15, then minMax is 45.
					max[litem] = parseInt((o[litem + 'Max'] - ((o[litem + 'Max'] - o[litem + 'Min']) % o['step' + uitem])), 10);
					gridSize[litem] = 0;

					html += '<dt class="ui_tpicker_' + litem + '_label"' + (show ? '' : noDisplay) + '>' + o[litem + 'Text'] + '</dt>' +
								'<dd class="ui_tpicker_' + litem + '"><div class="ui_tpicker_' + litem + '_slider"' + (show ? '' : noDisplay) + '></div>';

					if (show && o[litem + 'Grid'] > 0) {
						html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>';

						if (litem === 'hour') {
							for (var h = o[litem + 'Min']; h <= max[litem]; h += parseInt(o[litem + 'Grid'], 10)) {
								gridSize[litem]++;
								var tmph = $.datepicker.formatTime(this.support.ampm ? 'hht' : 'HH', {hour: h}, o);
								html += '<td data-for="' + litem + '">' + tmph + '</td>';
							}
						}
						else {
							for (var m = o[litem + 'Min']; m <= max[litem]; m += parseInt(o[litem + 'Grid'], 10)) {
								gridSize[litem]++;
								html += '<td data-for="' + litem + '">' + ((m < 10) ? '0' : '') + m + '</td>';
							}
						}

						html += '</tr></table></div>';
					}
					html += '</dd>';
				}
				
				// Timezone
				var showTz = o.showTimezone !== null ? o.showTimezone : this.support.timezone;
				html += '<dt class="ui_tpicker_timezone_label"' + (showTz ? '' : noDisplay) + '>' + o.timezoneText + '</dt>';
				html += '<dd class="ui_tpicker_timezone" ' + (showTz ? '' : noDisplay) + '></dd>';

				// Create the elements from string
				html += '</dl></div>';
				var $tp = $(html);

				// if we only want time picker...
				if (o.timeOnly === true) {
					$tp.prepend('<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' + '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' + '</div>');
					$dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide();
				}
				
				// add sliders, adjust grids, add events
				for (i = 0, l = tp_inst.units.length; i < l; i++) {
					litem = tp_inst.units[i];
					uitem = litem.substr(0, 1).toUpperCase() + litem.substr(1);
					show = o['show' + uitem] !== null ? o['show' + uitem] : this.support[litem];

					// add the slider
					tp_inst[litem + '_slider'] = tp_inst.control.create(tp_inst, $tp.find('.ui_tpicker_' + litem + '_slider'), litem, tp_inst[litem], o[litem + 'Min'], max[litem], o['step' + uitem]);

					// adjust the grid and add click event
					if (show && o[litem + 'Grid'] > 0) {
						size = 100 * gridSize[litem] * o[litem + 'Grid'] / (max[litem] - o[litem + 'Min']);
						$tp.find('.ui_tpicker_' + litem + ' table').css({
							width: size + "%",
							marginLeft: o.isRTL ? '0' : ((size / (-2 * gridSize[litem])) + "%"),
							marginRight: o.isRTL ? ((size / (-2 * gridSize[litem])) + "%") : '0',
							borderCollapse: 'collapse'
						}).find("td").click(function (e) {
								var $t = $(this),
									h = $t.html(),
									n = parseInt(h.replace(/[^0-9]/g), 10),
									ap = h.replace(/[^apm]/ig),
									f = $t.data('for'); // loses scope, so we use data-for

								if (f === 'hour') {
									if (ap.indexOf('p') !== -1 && n < 12) {
										n += 12;
									}
									else {
										if (ap.indexOf('a') !== -1 && n === 12) {
											n = 0;
										}
									}
								}
								
								tp_inst.control.value(tp_inst, tp_inst[f + '_slider'], litem, n);

								tp_inst._onTimeChange();
								tp_inst._onSelectHandler();
							}).css({
								cursor: 'pointer',
								width: (100 / gridSize[litem]) + '%',
								textAlign: 'center',
								overflow: 'hidden'
							});
					} // end if grid > 0
				} // end for loop

				// Add timezone options
				this.timezone_select = $tp.find('.ui_tpicker_timezone').append('<select></select>').find("select");
				$.fn.append.apply(this.timezone_select,
				$.map(o.timezoneList, function (val, idx) {
					return $("<option />").val(typeof val === "object" ? val.value : val).text(typeof val === "object" ? val.label : val);
				}));
				if (typeof(this.timezone) !== "undefined" && this.timezone !== null && this.timezone !== "") {
					var local_timezone = (new Date(this.inst.selectedYear, this.inst.selectedMonth, this.inst.selectedDay, 12)).getTimezoneOffset() * -1;
					if (local_timezone === this.timezone) {
						selectLocalTimezone(tp_inst);
					} else {
						this.timezone_select.val(this.timezone);
					}
				} else {
					if (typeof(this.hour) !== "undefined" && this.hour !== null && this.hour !== "") {
						this.timezone_select.val(o.timezone);
					} else {
						selectLocalTimezone(tp_inst);
					}
				}
				this.timezone_select.change(function () {
					tp_inst._onTimeChange();
					tp_inst._onSelectHandler();
				});
				// End timezone options
				
				// inject timepicker into datepicker
				var $buttonPanel = $dp.find('.ui-datepicker-buttonpane');
				if ($buttonPanel.length) {
					$buttonPanel.before($tp);
				} else {
					$dp.append($tp);
				}

				this.$timeObj = $tp.find('.ui_tpicker_time');

				if (this.inst !== null) {
					var timeDefined = this.timeDefined;
					this._onTimeChange();
					this.timeDefined = timeDefined;
				}

				// slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/
				if (this._defaults.addSliderAccess) {
					var sliderAccessArgs = this._defaults.sliderAccessArgs,
						rtl = this._defaults.isRTL;
					sliderAccessArgs.isRTL = rtl;
						
					setTimeout(function () { // fix for inline mode
						if ($tp.find('.ui-slider-access').length === 0) {
							$tp.find('.ui-slider:visible').sliderAccess(sliderAccessArgs);

							// fix any grids since sliders are shorter
							var sliderAccessWidth = $tp.find('.ui-slider-access:eq(0)').outerWidth(true);
							if (sliderAccessWidth) {
								$tp.find('table:visible').each(function () {
									var $g = $(this),
										oldWidth = $g.outerWidth(),
										oldMarginLeft = $g.css(rtl ? 'marginRight' : 'marginLeft').toString().replace('%', ''),
										newWidth = oldWidth - sliderAccessWidth,
										newMarginLeft = ((oldMarginLeft * newWidth) / oldWidth) + '%',
										css = { width: newWidth, marginRight: 0, marginLeft: 0 };
									css[rtl ? 'marginRight' : 'marginLeft'] = newMarginLeft;
									$g.css(css);
								});
							}
						}
					}, 10);
				}
				// end slideAccess integration

				tp_inst._limitMinMaxDateTime(this.inst, true);
			}
		},

		/*
		* This function tries to limit the ability to go outside the
		* min/max date range
		*/
		_limitMinMaxDateTime: function (dp_inst, adjustSliders) {
			var o = this._defaults,
				dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay);

			if (!this._defaults.showTimepicker) {
				return;
			} // No time so nothing to check here

			if ($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date) {
				var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'),
					minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0);

				if (this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null || this.microsecMinOriginal === null) {
					this.hourMinOriginal = o.hourMin;
					this.minuteMinOriginal = o.minuteMin;
					this.secondMinOriginal = o.secondMin;
					this.millisecMinOriginal = o.millisecMin;
					this.microsecMinOriginal = o.microsecMin;
				}

				if (dp_inst.settings.timeOnly || minDateTimeDate.getTime() === dp_date.getTime()) {
					this._defaults.hourMin = minDateTime.getHours();
					if (this.hour <= this._defaults.hourMin) {
						this.hour = this._defaults.hourMin;
						this._defaults.minuteMin = minDateTime.getMinutes();
						if (this.minute <= this._defaults.minuteMin) {
							this.minute = this._defaults.minuteMin;
							this._defaults.secondMin = minDateTime.getSeconds();
							if (this.second <= this._defaults.secondMin) {
								this.second = this._defaults.secondMin;
								this._defaults.millisecMin = minDateTime.getMilliseconds();
								if (this.millisec <= this._defaults.millisecMin) {
									this.millisec = this._defaults.millisecMin;
									this._defaults.microsecMin = minDateTime.getMicroseconds();
								} else {
									if (this.microsec < this._defaults.microsecMin) {
										this.microsec = this._defaults.microsecMin;
									}
									this._defaults.microsecMin = this.microsecMinOriginal;
								}
							} else {
								this._defaults.millisecMin = this.millisecMinOriginal;
								this._defaults.microsecMin = this.microsecMinOriginal;
							}
						} else {
							this._defaults.secondMin = this.secondMinOriginal;
							this._defaults.millisecMin = this.millisecMinOriginal;
							this._defaults.microsecMin = this.microsecMinOriginal;
						}
					} else {
						this._defaults.minuteMin = this.minuteMinOriginal;
						this._defaults.secondMin = this.secondMinOriginal;
						this._defaults.millisecMin = this.millisecMinOriginal;
						this._defaults.microsecMin = this.microsecMinOriginal;
					}
				} else {
					this._defaults.hourMin = this.hourMinOriginal;
					this._defaults.minuteMin = this.minuteMinOriginal;
					this._defaults.secondMin = this.secondMinOriginal;
					this._defaults.millisecMin = this.millisecMinOriginal;
					this._defaults.microsecMin = this.microsecMinOriginal;
				}
			}

			if ($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date) {
				var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'),
					maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0);

				if (this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null || this.millisecMaxOriginal === null) {
					this.hourMaxOriginal = o.hourMax;
					this.minuteMaxOriginal = o.minuteMax;
					this.secondMaxOriginal = o.secondMax;
					this.millisecMaxOriginal = o.millisecMax;
					this.microsecMaxOriginal = o.microsecMax;
				}

				if (dp_inst.settings.timeOnly || maxDateTimeDate.getTime() === dp_date.getTime()) {
					this._defaults.hourMax = maxDateTime.getHours();
					if (this.hour >= this._defaults.hourMax) {
						this.hour = this._defaults.hourMax;
						this._defaults.minuteMax = maxDateTime.getMinutes();
						if (this.minute >= this._defaults.minuteMax) {
							this.minute = this._defaults.minuteMax;
							this._defaults.secondMax = maxDateTime.getSeconds();
							if (this.second >= this._defaults.secondMax) {
								this.second = this._defaults.secondMax;
								this._defaults.millisecMax = maxDateTime.getMilliseconds();
								if (this.millisec >= this._defaults.millisecMax) {
									this.millisec = this._defaults.millisecMax;
									this._defaults.microsecMax = maxDateTime.getMicroseconds();
								} else {
									if (this.microsec > this._defaults.microsecMax) {
										this.microsec = this._defaults.microsecMax;
									}
									this._defaults.microsecMax = this.microsecMaxOriginal;
								}
							} else {
								this._defaults.millisecMax = this.millisecMaxOriginal;
								this._defaults.microsecMax = this.microsecMaxOriginal;
							}
						} else {
							this._defaults.secondMax = this.secondMaxOriginal;
							this._defaults.millisecMax = this.millisecMaxOriginal;
							this._defaults.microsecMax = this.microsecMaxOriginal;
						}
					} else {
						this._defaults.minuteMax = this.minuteMaxOriginal;
						this._defaults.secondMax = this.secondMaxOriginal;
						this._defaults.millisecMax = this.millisecMaxOriginal;
						this._defaults.microsecMax = this.microsecMaxOriginal;
					}
				} else {
					this._defaults.hourMax = this.hourMaxOriginal;
					this._defaults.minuteMax = this.minuteMaxOriginal;
					this._defaults.secondMax = this.secondMaxOriginal;
					this._defaults.millisecMax = this.millisecMaxOriginal;
					this._defaults.microsecMax = this.microsecMaxOriginal;
				}
			}

			if (adjustSliders !== undefined && adjustSliders === true) {
				var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)), 10),
					minMax = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)), 10),
					secMax = parseInt((this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)), 10),
					millisecMax = parseInt((this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)), 10),
					microsecMax = parseInt((this._defaults.microsecMax - ((this._defaults.microsecMax - this._defaults.microsecMin) % this._defaults.stepMicrosec)), 10);

				if (this.hour_slider) {
					this.control.options(this, this.hour_slider, 'hour', { min: this._defaults.hourMin, max: hourMax });
					this.control.value(this, this.hour_slider, 'hour', this.hour - (this.hour % this._defaults.stepHour));
				}
				if (this.minute_slider) {
					this.control.options(this, this.minute_slider, 'minute', { min: this._defaults.minuteMin, max: minMax });
					this.control.value(this, this.minute_slider, 'minute', this.minute - (this.minute % this._defaults.stepMinute));
				}
				if (this.second_slider) {
					this.control.options(this, this.second_slider, 'second', { min: this._defaults.secondMin, max: secMax });
					this.control.value(this, this.second_slider, 'second', this.second - (this.second % this._defaults.stepSecond));
				}
				if (this.millisec_slider) {
					this.control.options(this, this.millisec_slider, 'millisec', { min: this._defaults.millisecMin, max: millisecMax });
					this.control.value(this, this.millisec_slider, 'millisec', this.millisec - (this.millisec % this._defaults.stepMillisec));
				}
				if (this.microsec_slider) {
					this.control.options(this, this.microsec_slider, 'microsec', { min: this._defaults.microsecMin, max: microsecMax });
					this.control.value(this, this.microsec_slider, 'microsec', this.microsec - (this.microsec % this._defaults.stepMicrosec));
				}
			}

		},

		/*
		* when a slider moves, set the internal time...
		* on time change is also called when the time is updated in the text field
		*/
		_onTimeChange: function () {
			if (!this._defaults.showTimepicker) {
                                return;
			}
			var hour = (this.hour_slider) ? this.control.value(this, this.hour_slider, 'hour') : false,
				minute = (this.minute_slider) ? this.control.value(this, this.minute_slider, 'minute') : false,
				second = (this.second_slider) ? this.control.value(this, this.second_slider, 'second') : false,
				millisec = (this.millisec_slider) ? this.control.value(this, this.millisec_slider, 'millisec') : false,
				microsec = (this.microsec_slider) ? this.control.value(this, this.microsec_slider, 'microsec') : false,
				timezone = (this.timezone_select) ? this.timezone_select.val() : false,
				o = this._defaults,
				pickerTimeFormat = o.pickerTimeFormat || o.timeFormat,
				pickerTimeSuffix = o.pickerTimeSuffix || o.timeSuffix;

			if (typeof(hour) === 'object') {
				hour = false;
			}
			if (typeof(minute) === 'object') {
				minute = false;
			}
			if (typeof(second) === 'object') {
				second = false;
			}
			if (typeof(millisec) === 'object') {
				millisec = false;
			}
			if (typeof(microsec) === 'object') {
				microsec = false;
			}
			if (typeof(timezone) === 'object') {
				timezone = false;
			}

			if (hour !== false) {
				hour = parseInt(hour, 10);
			}
			if (minute !== false) {
				minute = parseInt(minute, 10);
			}
			if (second !== false) {
				second = parseInt(second, 10);
			}
			if (millisec !== false) {
				millisec = parseInt(millisec, 10);
			}
			if (microsec !== false) {
				microsec = parseInt(microsec, 10);
			}

			var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0];

			// If the update was done in the input field, the input field should not be updated.
			// If the update was done using the sliders, update the input field.
			var hasChanged = (hour !== this.hour || minute !== this.minute || second !== this.second || millisec !== this.millisec || microsec !== this.microsec || 
					(this.ampm.length > 0 && (hour < 12) !== ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) || (this.timezone !== null && timezone !== this.timezone));

			if (hasChanged) {

				if (hour !== false) {
					this.hour = hour;
				}
				if (minute !== false) {
					this.minute = minute;
				}
				if (second !== false) {
					this.second = second;
				}
				if (millisec !== false) {
					this.millisec = millisec;
				}
				if (microsec !== false) {
					this.microsec = microsec;
				}
				if (timezone !== false) {
					this.timezone = timezone;
				}

				if (!this.inst) {
					this.inst = $.datepicker._getInst(this.$input[0]);
				}

				this._limitMinMaxDateTime(this.inst, true);
			}
			if (this.support.ampm) {
				this.ampm = ampm;
			}

			// Updates the time within the timepicker
			this.formattedTime = $.datepicker.formatTime(o.timeFormat, this, o);
			if (this.$timeObj) {
				if (pickerTimeFormat === o.timeFormat) {
					this.$timeObj.text(this.formattedTime + pickerTimeSuffix);
				}
				else {
					this.$timeObj.text($.datepicker.formatTime(pickerTimeFormat, this, o) + pickerTimeSuffix);
				}
			}

			this.timeDefined = true;
			if (hasChanged) {
				this._updateDateTime();
			}
		},

		/*
		* call custom onSelect.
		* bind to sliders slidestop, and grid click.
		*/
		_onSelectHandler: function () {
			var onSelect = this._defaults.onSelect || this.inst.settings.onSelect;
			var inputEl = this.$input ? this.$input[0] : null;
			if (onSelect && inputEl) {
				onSelect.apply(inputEl, [this.formattedDateTime, this]);
			}
		},

		/*
		* update our input with the new date time..
		*/
		_updateDateTime: function (dp_inst) {
			dp_inst = this.inst || dp_inst;
			var dtTmp = (dp_inst.currentYear > 0? 
							new Date(dp_inst.currentYear, dp_inst.currentMonth, dp_inst.currentDay) : 
							new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)),
				dt = $.datepicker._daylightSavingAdjust(dtTmp),
				//dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)),
				//dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.currentYear, dp_inst.currentMonth, dp_inst.currentDay)),
				dateFmt = $.datepicker._get(dp_inst, 'dateFormat'),
				formatCfg = $.datepicker._getFormatConfig(dp_inst),
				timeAvailable = dt !== null && this.timeDefined;
			this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg);
			var formattedDateTime = this.formattedDate;
			
			// if a slider was changed but datepicker doesn't have a value yet, set it
			if (dp_inst.lastVa === "") {
                dp_inst.currentYear = dp_inst.selectedYear;
                dp_inst.currentMonth = dp_inst.selectedMonth;
                dp_inst.currentDay = dp_inst.selectedDay;
            }

			/*
			* remove following lines to force every changes in date picker to change the input value
			* Bug descriptions: when an input field has a default value, and click on the field to pop up the date picker. 
			* If the user manually empty the value in the input field, the date picker will never change selected value.
			*/
			//if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) {
			//	return;
			//}

			if (this._defaults.timeOnly === true) {
				formattedDateTime = this.formattedTime;
			} else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) {
				formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix;
			}

			this.formattedDateTime = formattedDateTime;

			if (!this._defaults.showTimepicker) {
				this.$input.val(this.formattedDate);
			} else if (this.$altInput && this._defaults.timeOnly === false && this._defaults.altFieldTimeOnly === true) {
				this.$altInput.val(this.formattedTime);
				this.$input.val(this.formattedDate);
			} else if (this.$altInput) {
				this.$input.val(formattedDateTime);
				var altFormattedDateTime = '',
					altSeparator = this._defaults.altSeparator ? this._defaults.altSeparator : this._defaults.separator,
					altTimeSuffix = this._defaults.altTimeSuffix ? this._defaults.altTimeSuffix : this._defaults.timeSuffix;
				
				if (!this._defaults.timeOnly) {
					if (this._defaults.altFormat) {
						altFormattedDateTime = $.datepicker.formatDate(this._defaults.altFormat, (dt === null ? new Date() : dt), formatCfg);
					}
					else {
						altFormattedDateTime = this.formattedDate;
					}

					if (altFormattedDateTime) {
						altFormattedDateTime += altSeparator;
					}
				}

				if (this._defaults.altTimeFormat) {
					altFormattedDateTime += $.datepicker.formatTime(this._defaults.altTimeFormat, this, this._defaults) + altTimeSuffix;
				}
				else {
					altFormattedDateTime += this.formattedTime + altTimeSuffix;
				}
				this.$altInput.val(altFormattedDateTime);
			} else {
				this.$input.val(formattedDateTime);
			}

			this.$input.trigger("change");
		},

		_onFocus: function () {
			if (!this.$input.val() && this._defaults.defaultValue) {
				this.$input.val(this._defaults.defaultValue);
				var inst = $.datepicker._getInst(this.$input.get(0)),
					tp_inst = $.datepicker._get(inst, 'timepicker');
				if (tp_inst) {
					if (tp_inst._defaults.timeOnly && (inst.input.val() !== inst.lastVal)) {
						try {
							$.datepicker._updateDatepicker(inst);
						} catch (err) {
							$.timepicker.log(err);
						}
					}
				}
			}
		},

		/*
		* Small abstraction to control types
		* We can add more, just be sure to follow the pattern: create, options, value
		*/
		_controls: {
			// slider methods
			slider: {
				create: function (tp_inst, obj, unit, val, min, max, step) {
					var rtl = tp_inst._defaults.isRTL; // if rtl go -60->0 instead of 0->60
					return obj.prop('slide', null).slider({
						orientation: "horizontal",
						value: rtl ? val * -1 : val,
						min: rtl ? max * -1 : min,
						max: rtl ? min * -1 : max,
						step: step,
						slide: function (event, ui) {
							tp_inst.control.value(tp_inst, $(this), unit, rtl ? ui.value * -1 : ui.value);
							tp_inst._onTimeChange();
						},
						stop: function (event, ui) {
							tp_inst._onSelectHandler();
						}
					});	
				},
				options: function (tp_inst, obj, unit, opts, val) {
					if (tp_inst._defaults.isRTL) {
						if (typeof(opts) === 'string') {
							if (opts === 'min' || opts === 'max') {
								if (val !== undefined) {
									return obj.slider(opts, val * -1);
								}
								return Math.abs(obj.slider(opts));
							}
							return obj.slider(opts);
						}
						var min = opts.min, 
							max = opts.max;
						opts.min = opts.max = null;
						if (min !== undefined) {
							opts.max = min * -1;
						}
						if (max !== undefined) {
							opts.min = max * -1;
						}
						return obj.slider(opts);
					}
					if (typeof(opts) === 'string' && val !== undefined) {
						return obj.slider(opts, val);
					}
					return obj.slider(opts);
				},
				value: function (tp_inst, obj, unit, val) {
					if (tp_inst._defaults.isRTL) {
						if (val !== undefined) {
							return obj.slider('value', val * -1);
						}
						return Math.abs(obj.slider('value'));
					}
					if (val !== undefined) {
						return obj.slider('value', val);
					}
					return obj.slider('value');
				}
			},
			// select methods
			select: {
				create: function (tp_inst, obj, unit, val, min, max, step) {
					var sel = '<select class="ui-timepicker-select" data-unit="' + unit + '" data-min="' + min + '" data-max="' + max + '" data-step="' + step + '">',
						format = tp_inst._defaults.pickerTimeFormat || tp_inst._defaults.timeFormat;

					for (var i = min; i <= max; i += step) {
						sel += '<option value="' + i + '"' + (i === val ? ' selected' : '') + '>';
						if (unit === 'hour') {
							sel += $.datepicker.formatTime($.trim(format.replace(/[^ht ]/ig, '')), {hour: i}, tp_inst._defaults);
						}
						else if (unit === 'millisec' || unit === 'microsec' || i >= 10) { sel += i; }
						else {sel += '0' + i.toString(); }
						sel += '</option>';
					}
					sel += '</select>';

					obj.children('select').remove();

					$(sel).appendTo(obj).change(function (e) {
						tp_inst._onTimeChange();
						tp_inst._onSelectHandler();
					});

					return obj;
				},
				options: function (tp_inst, obj, unit, opts, val) {
					var o = {},
						$t = obj.children('select');
					if (typeof(opts) === 'string') {
						if (val === undefined) {
							return $t.data(opts);
						}
						o[opts] = val;	
					}
					else { o = opts; }
					return tp_inst.control.create(tp_inst, obj, $t.data('unit'), $t.val(), o.min || $t.data('min'), o.max || $t.data('max'), o.step || $t.data('step'));
				},
				value: function (tp_inst, obj, unit, val) {
					var $t = obj.children('select');
					if (val !== undefined) {
						return $t.val(val);
					}
					return $t.val();
				}
			}
		} // end _controls

	});

	$.fn.extend({
		/*
		* shorthand just to use timepicker.
		*/
		timepicker: function (o) {
			o = o || {};
			var tmp_args = Array.prototype.slice.call(arguments);

			if (typeof o === 'object') {
				tmp_args[0] = $.extend(o, {
					timeOnly: true
				});
			}

			return $(this).each(function () {
				$.fn.datetimepicker.apply($(this), tmp_args);
			});
		},

		/*
		* extend timepicker to datepicker
		*/
		datetimepicker: function (o) {
			o = o || {};
			var tmp_args = arguments;

			if (typeof(o) === 'string') {
				if (o === 'getDate') {
					return $.fn.datepicker.apply($(this[0]), tmp_args);
				} else {
					return this.each(function () {
						var $t = $(this);
						$t.datepicker.apply($t, tmp_args);
					});
				}
			} else {
				return this.each(function () {
					var $t = $(this);
					$t.datepicker($.timepicker._newInst($t, o)._defaults);
				});
			}
		}
	});

	/*
	* Public Utility to parse date and time
	*/
	$.datepicker.parseDateTime = function (dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) {
		var parseRes = parseDateTimeInternal(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings);
		if (parseRes.timeObj) {
			var t = parseRes.timeObj;
			parseRes.date.setHours(t.hour, t.minute, t.second, t.millisec);
			parseRes.date.setMicroseconds(t.microsec);
		}

		return parseRes.date;
	};

	/*
	* Public utility to parse time
	*/
	$.datepicker.parseTime = function (timeFormat, timeString, options) {
		var o = extendRemove(extendRemove({}, $.timepicker._defaults), options || {}),
			iso8601 = (timeFormat.replace(/\'.*?\'/g, '').indexOf('Z') !== -1);

		// Strict parse requires the timeString to match the timeFormat exactly
		var strictParse = function (f, s, o) {

			// pattern for standard and localized AM/PM markers
			var getPatternAmpm = function (amNames, pmNames) {
				var markers = [];
				if (amNames) {
					$.merge(markers, amNames);
				}
				if (pmNames) {
					$.merge(markers, pmNames);
				}
				markers = $.map(markers, function (val) {
					return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&');
				});
				return '(' + markers.join('|') + ')?';
			};

			// figure out position of time elements.. cause js cant do named captures
			var getFormatPositions = function (timeFormat) {
				var finds = timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|c{1}|t{1,2}|z|'.*?')/g),
					orders = {
						h: -1,
						m: -1,
						s: -1,
						l: -1,
						c: -1,
						t: -1,
						z: -1
					};

				if (finds) {
					for (var i = 0; i < finds.length; i++) {
						if (orders[finds[i].toString().charAt(0)] === -1) {
							orders[finds[i].toString().charAt(0)] = i + 1;
						}
					}
				}
				return orders;
			};

			var regstr = '^' + f.toString()
					.replace(/([hH]{1,2}|mm?|ss?|[tT]{1,2}|[zZ]|[lc]|'.*?')/g, function (match) {
							var ml = match.length;
							switch (match.charAt(0).toLowerCase()) {
							case 'h':
								return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})';
							case 'm':
								return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})';
							case 's':
								return ml === 1 ? '(\\d?\\d)' : '(\\d{' + ml + '})';
							case 'l':
								return '(\\d?\\d?\\d)';
							case 'c':
								return '(\\d?\\d?\\d)';
							case 'z':
								return '(z|[-+]\\d\\d:?\\d\\d|\\S+)?';
							case 't':
								return getPatternAmpm(o.amNames, o.pmNames);
							default:    // literal escaped in quotes
								return '(' + match.replace(/\'/g, "").replace(/(\.|\$|\^|\\|\/|\(|\)|\[|\]|\?|\+|\*)/g, function (m) { return "\\" + m; }) + ')?';
							}
						})
					.replace(/\s/g, '\\s?') +
					o.timeSuffix + '$',
				order = getFormatPositions(f),
				ampm = '',
				treg;

			treg = s.match(new RegExp(regstr, 'i'));

			var resTime = {
				hour: 0,
				minute: 0,
				second: 0,
				millisec: 0,
				microsec: 0
			};

			if (treg) {
				if (order.t !== -1) {
					if (treg[order.t] === undefined || treg[order.t].length === 0) {
						ampm = '';
						resTime.ampm = '';
					} else {
						ampm = $.inArray(treg[order.t].toUpperCase(), o.amNames) !== -1 ? 'AM' : 'PM';
						resTime.ampm = o[ampm === 'AM' ? 'amNames' : 'pmNames'][0];
					}
				}

				if (order.h !== -1) {
					if (ampm === 'AM' && treg[order.h] === '12') {
						resTime.hour = 0; // 12am = 0 hour
					} else {
						if (ampm === 'PM' && treg[order.h] !== '12') {
							resTime.hour = parseInt(treg[order.h], 10) + 12; // 12pm = 12 hour, any other pm = hour + 12
						} else {
							resTime.hour = Number(treg[order.h]);
						}
					}
				}

				if (order.m !== -1) {
					resTime.minute = Number(treg[order.m]);
				}
				if (order.s !== -1) {
					resTime.second = Number(treg[order.s]);
				}
				if (order.l !== -1) {
					resTime.millisec = Number(treg[order.l]);
				}
				if (order.c !== -1) {
					resTime.microsec = Number(treg[order.c]);
				}
				if (order.z !== -1 && treg[order.z] !== undefined) {
					resTime.timezone = $.timepicker.timezoneOffsetNumber(treg[order.z]);
				}


				return resTime;
			}
			return false;
		};// end strictParse

		// First try JS Date, if that fails, use strictParse
		var looseParse = function (f, s, o) {
			try {
				var d = new Date('2012-01-01 ' + s);
				if (isNaN(d.getTime())) {
					d = new Date('2012-01-01T' + s);
					if (isNaN(d.getTime())) {
						d = new Date('01/01/2012 ' + s);
						if (isNaN(d.getTime())) {
							throw "Unable to parse time with native Date: " + s;
						}
					}
				}

				return {
					hour: d.getHours(),
					minute: d.getMinutes(),
					second: d.getSeconds(),
					millisec: d.getMilliseconds(),
					microsec: d.getMicroseconds(),
					timezone: d.getTimezoneOffset() * -1
				};
			}
			catch (err) {
				try {
					return strictParse(f, s, o);
				}
				catch (err2) {
					$.timepicker.log("Unable to parse \ntimeString: " + s + "\ntimeFormat: " + f);
				}				
			}
			return false;
		}; // end looseParse
		
		if (typeof o.parse === "function") {
			return o.parse(timeFormat, timeString, o);
		}
		if (o.parse === 'loose') {
			return looseParse(timeFormat, timeString, o);
		}
		return strictParse(timeFormat, timeString, o);
	};

	/**
	 * Public utility to format the time
	 * @param {string} format format of the time
	 * @param {Object} time Object not a Date for timezones
	 * @param {Object} [options] essentially the regional[].. amNames, pmNames, ampm
	 * @returns {string} the formatted time
	 */
	$.datepicker.formatTime = function (format, time, options) {
		options = options || {};
		options = $.extend({}, $.timepicker._defaults, options);
		time = $.extend({
			hour: 0,
			minute: 0,
			second: 0,
			millisec: 0,
			microsec: 0,
			timezone: null
		}, time);

		var tmptime = format,
			ampmName = options.amNames[0],
			hour = parseInt(time.hour, 10);

		if (hour > 11) {
			ampmName = options.pmNames[0];
		}

		tmptime = tmptime.replace(/(?:HH?|hh?|mm?|ss?|[tT]{1,2}|[zZ]|[lc]|'.*?')/g, function (match) {
			switch (match) {
			case 'HH':
				return ('0' + hour).slice(-2);
			case 'H':
				return hour;
			case 'hh':
				return ('0' + convert24to12(hour)).slice(-2);
			case 'h':
				return convert24to12(hour);
			case 'mm':
				return ('0' + time.minute).slice(-2);
			case 'm':
				return time.minute;
			case 'ss':
				return ('0' + time.second).slice(-2);
			case 's':
				return time.second;
			case 'l':
				return ('00' + time.millisec).slice(-3);
			case 'c':
				return ('00' + time.microsec).slice(-3);
			case 'z':
				return $.timepicker.timezoneOffsetString(time.timezone === null ? options.timezone : time.timezone, false);
			case 'Z':
				return $.timepicker.timezoneOffsetString(time.timezone === null ? options.timezone : time.timezone, true);
			case 'T':
				return ampmName.charAt(0).toUpperCase();
			case 'TT':
				return ampmName.toUpperCase();
			case 't':
				return ampmName.charAt(0).toLowerCase();
			case 'tt':
				return ampmName.toLowerCase();
			default:
				return match.replace(/'/g, "");
			}
		});

		return tmptime;
	};

	/*
	* the bad hack :/ override datepicker so it doesn't close on select
	// inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378
	*/
	$.datepicker._base_selectDate = $.datepicker._selectDate;
	$.datepicker._selectDate = function (id, dateStr) {
		var inst = this._getInst($(id)[0]),
			tp_inst = this._get(inst, 'timepicker');

		if (tp_inst) {
			tp_inst._limitMinMaxDateTime(inst, true);
			inst.inline = inst.stay_open = true;
			//This way the onSelect handler called from calendarpicker get the full dateTime
			this._base_selectDate(id, dateStr);
			inst.inline = inst.stay_open = false;
			this._notifyChange(inst);
			this._updateDatepicker(inst);
		} else {
			this._base_selectDate(id, dateStr);
		}
	};

	/*
	* second bad hack :/ override datepicker so it triggers an event when changing the input field
	* and does not redraw the datepicker on every selectDate event
	*/
	$.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker;
	$.datepicker._updateDatepicker = function (inst) {

		// don't popup the datepicker if there is another instance already opened
		var input = inst.input[0];
		if ($.datepicker._curInst && $.datepicker._curInst !== inst && $.datepicker._datepickerShowing && $.datepicker._lastInput !== input) {
			return;
		}

		if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) {

			this._base_updateDatepicker(inst);

			// Reload the time control when changing something in the input text field.
			var tp_inst = this._get(inst, 'timepicker');
			if (tp_inst) {
				tp_inst._addTimePicker(inst);
			}
		}
	};

	/*
	* third bad hack :/ override datepicker so it allows spaces and colon in the input field
	*/
	$.datepicker._base_doKeyPress = $.datepicker._doKeyPress;
	$.datepicker._doKeyPress = function (event) {
		var inst = $.datepicker._getInst(event.target),
			tp_inst = $.datepicker._get(inst, 'timepicker');

		if (tp_inst) {
			if ($.datepicker._get(inst, 'constrainInput')) {
				var ampm = tp_inst.support.ampm,
					tz = tp_inst._defaults.showTimezone !== null ? tp_inst._defaults.showTimezone : tp_inst.support.timezone,
					dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')),
					datetimeChars = tp_inst._defaults.timeFormat.toString()
											.replace(/[hms]/g, '')
											.replace(/TT/g, ampm ? 'APM' : '')
											.replace(/Tt/g, ampm ? 'AaPpMm' : '')
											.replace(/tT/g, ampm ? 'AaPpMm' : '')
											.replace(/T/g, ampm ? 'AP' : '')
											.replace(/tt/g, ampm ? 'apm' : '')
											.replace(/t/g, ampm ? 'ap' : '') + 
											" " + tp_inst._defaults.separator + 
											tp_inst._defaults.timeSuffix + 
											(tz ? tp_inst._defaults.timezoneList.join('') : '') + 
											(tp_inst._defaults.amNames.join('')) + (tp_inst._defaults.pmNames.join('')) + 
											dateChars,
					chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode);
				return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1);
			}
		}

		return $.datepicker._base_doKeyPress(event);
	};

	/*
	* Fourth bad hack :/ override _updateAlternate function used in inline mode to init altField
	* Update any alternate field to synchronise with the main field.
	*/
	$.datepicker._base_updateAlternate = $.datepicker._updateAlternate;
	$.datepicker._updateAlternate = function (inst) {
		var tp_inst = this._get(inst, 'timepicker');
		if (tp_inst) {
			var altField = tp_inst._defaults.altField;
			if (altField) { // update alternate field too
				var altFormat = tp_inst._defaults.altFormat || tp_inst._defaults.dateFormat,
					date = this._getDate(inst),
					formatCfg = $.datepicker._getFormatConfig(inst),
					altFormattedDateTime = '', 
					altSeparator = tp_inst._defaults.altSeparator ? tp_inst._defaults.altSeparator : tp_inst._defaults.separator, 
					altTimeSuffix = tp_inst._defaults.altTimeSuffix ? tp_inst._defaults.altTimeSuffix : tp_inst._defaults.timeSuffix,
					altTimeFormat = tp_inst._defaults.altTimeFormat !== null ? tp_inst._defaults.altTimeFormat : tp_inst._defaults.timeFormat;
				
				altFormattedDateTime += $.datepicker.formatTime(altTimeFormat, tp_inst, tp_inst._defaults) + altTimeSuffix;
				if (!tp_inst._defaults.timeOnly && !tp_inst._defaults.altFieldTimeOnly && date !== null) {
					if (tp_inst._defaults.altFormat) {
						altFormattedDateTime = $.datepicker.formatDate(tp_inst._defaults.altFormat, date, formatCfg) + altSeparator + altFormattedDateTime;
					}
					else {
						altFormattedDateTime = tp_inst.formattedDate + altSeparator + altFormattedDateTime;
					}
				}
				$(altField).val(altFormattedDateTime);
			}
		}
		else {
			$.datepicker._base_updateAlternate(inst);
		}
	};

	/*
	* Override key up event to sync manual input changes.
	*/
	$.datepicker._base_doKeyUp = $.datepicker._doKeyUp;
	$.datepicker._doKeyUp = function (event) {
		var inst = $.datepicker._getInst(event.target),
			tp_inst = $.datepicker._get(inst, 'timepicker');

		if (tp_inst) {
			if (tp_inst._defaults.timeOnly && (inst.input.val() !== inst.lastVal)) {
				try {
					$.datepicker._updateDatepicker(inst);
				} catch (err) {
					$.timepicker.log(err);
				}
			}
		}

		return $.datepicker._base_doKeyUp(event);
	};

	/*
	* override "Today" button to also grab the time.
	*/
	$.datepicker._base_gotoToday = $.datepicker._gotoToday;
	$.datepicker._gotoToday = function (id) {
		var target = $(id)[0];
		var inst = this._getInst(target),
			$dp = inst.dpDiv;
		//this._base_gotoToday(id);
		var tp_inst = this._get(inst, 'timepicker');
		selectLocalTimezone(tp_inst);
		var now = new Date();
		if (typeof moment !== 'undefined' && typeof tracking !== 'undefined' && tracking.user !== undefined && tracking.user.tickOffset !== undefined) {
			now = new Date(moment.utc().subtract(tracking.user.tickOffset, 'ms'));
		}
		this._setTime(inst, now);
		this._updateDatepicker(inst);
		this._base_setDateDatepicker.apply(this, [target, now]);
		this._setTimeDatepicker(target, now, true);
	};

	/*
	* Disable & enable the Time in the datetimepicker
	*/
	$.datepicker._disableTimepickerDatepicker = function (target) {
		var inst = this._getInst(target);
		if (!inst) {
			return;
		}

		var tp_inst = this._get(inst, 'timepicker');
		$(target).datepicker('getDate'); // Init selected[Year|Month|Day]
		if (tp_inst) {
			inst.settings.showTimepicker = false;
			tp_inst._defaults.showTimepicker = false;
			tp_inst._updateDateTime(inst);
		}
	};

	$.datepicker._enableTimepickerDatepicker = function (target) {
		var inst = this._getInst(target);
		if (!inst) {
			return;
		}

		var tp_inst = this._get(inst, 'timepicker');
		$(target).datepicker('getDate'); // Init selected[Year|Month|Day]
		if (tp_inst) {
			inst.settings.showTimepicker = true;
			tp_inst._defaults.showTimepicker = true;
			tp_inst._addTimePicker(inst); // Could be disabled on page load
			tp_inst._updateDateTime(inst);
		}
	};

	/*
	* Create our own set time function
	*/
	$.datepicker._setTime = function (inst, date) {
		var tp_inst = this._get(inst, 'timepicker');
		if (tp_inst) {
			var defaults = tp_inst._defaults;

			// calling _setTime with no date sets time to defaults
			tp_inst.hour = date ? date.getHours() : defaults.hour;
			tp_inst.minute = date ? date.getMinutes() : defaults.minute;
			tp_inst.second = date ? date.getSeconds() : defaults.second;
			tp_inst.millisec = date ? date.getMilliseconds() : defaults.millisec;
			tp_inst.microsec = date ? date.getMicroseconds() : defaults.microsec;

			//check if within min/max times.. 
			tp_inst._limitMinMaxDateTime(inst, true);

			tp_inst._onTimeChange();
			tp_inst._updateDateTime(inst);
		}
	};

	/*
	* Create new public method to set only time, callable as $().datepicker('setTime', date)
	*/
	$.datepicker._setTimeDatepicker = function (target, date, withDate) {
		var inst = this._getInst(target);
		if (!inst) {
			return;
		}

		var tp_inst = this._get(inst, 'timepicker');

		if (tp_inst) {
			this._setDateFromField(inst);
			var tp_date;
			if (date) {
				if (typeof date === "string") {
					tp_inst._parseTime(date, withDate);
					tp_date = new Date();
					tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec);
					tp_date.setMicroseconds(tp_inst.microsec);
				} else {
					tp_date = new Date(date.getTime());
					tp_date.setMicroseconds(date.getMicroseconds());
				}
				if (tp_date.toString() === 'Invalid Date') {
					tp_date = undefined;
				}
				this._setTime(inst, tp_date);
			}
		}

	};

	/*
	* override setDate() to allow setting time too within Date object
	*/
	$.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker;
	$.datepicker._setDateDatepicker = function (target, date) {
		var inst = this._getInst(target);
		if (!inst) {
			return;
		}

		if (typeof(date) === 'string') {
			date = new Date(date);
			if (!date.getTime()) {
				$.timepicker.log("Error creating Date object from string.");
			}
		}

		var tp_inst = this._get(inst, 'timepicker');
		var tp_date;
		if (date instanceof Date) {
			tp_date = new Date(date.getTime());
			tp_date.setMicroseconds(date.getMicroseconds());
		} else {
			tp_date = date;
		}
		
		// This is important if you are using the timezone option, javascript's Date 
		// object will only return the timezone offset for the current locale, so we 
		// adjust it accordingly.  If not using timezone option this won't matter..
		// If a timezone is different in tp, keep the timezone as is
		if (tp_inst) {
			// look out for DST if tz wasn't specified
			if (!tp_inst.support.timezone && tp_inst._defaults.timezone === null) {
				tp_inst.timezone = tp_date.getTimezoneOffset() * -1;
			}
			date = $.timepicker.timezoneAdjust(date, tp_inst.timezone);
			tp_date = $.timepicker.timezoneAdjust(tp_date, tp_inst.timezone);
		}

		this._updateDatepicker(inst);
		this._base_setDateDatepicker.apply(this, arguments);
		this._setTimeDatepicker(target, tp_date, true);
	};

	/*
	* override getDate() to allow getting time too within Date object
	*/
	$.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker;
	$.datepicker._getDateDatepicker = function (target, noDefault) {
		var inst = this._getInst(target);
		if (!inst) {
			return;
		}

		var tp_inst = this._get(inst, 'timepicker');

		if (tp_inst) {
			// if it hasn't yet been defined, grab from field
			if (inst.lastVal === undefined) {
				this._setDateFromField(inst, noDefault);
			}

			var date = this._getDate(inst);
			if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) {
				date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec);
				date.setMicroseconds(tp_inst.microsec);

				// This is important if you are using the timezone option, javascript's Date 
				// object will only return the timezone offset for the current locale, so we 
				// adjust it accordingly.  If not using timezone option this won't matter..
				if (tp_inst.timezone != null) {
					// look out for DST if tz wasn't specified
					if (!tp_inst.support.timezone && tp_inst._defaults.timezone === null) {
						tp_inst.timezone = date.getTimezoneOffset() * -1;
					}
					date = $.timepicker.timezoneAdjust(date, tp_inst.timezone);
				}
			}
			return date;
		}
		return this._base_getDateDatepicker(target, noDefault);
	};

	/*
	* override parseDate() because UI 1.8.14 throws an error about "Extra characters"
	* An option in datapicker to ignore extra format characters would be nicer.
	*/
	$.datepicker._base_parseDate = $.datepicker.parseDate;
	$.datepicker.parseDate = function (format, value, settings) {
		var date;
		try {
			date = this._base_parseDate(format, value, settings);
		} catch (err) {
			// Hack!  The error message ends with a colon, a space, and
			// the "extra" characters.  We rely on that instead of
			// attempting to perfectly reproduce the parsing algorithm.
			if (err.indexOf(":") >= 0) {
				date = this._base_parseDate(format, value.substring(0, value.length - (err.length - err.indexOf(':') - 2)), settings);
				$.timepicker.log("Error parsing the date string: " + err + "\ndate string = " + value + "\ndate format = " + format);
			} else {
				throw err;
			}
		}
		return date;
	};

	/*
	* override formatDate to set date with time to the input
	*/
	$.datepicker._base_formatDate = $.datepicker._formatDate;
	$.datepicker._formatDate = function (inst, day, month, year) {
		var tp_inst = this._get(inst, 'timepicker');
		if (tp_inst) {
			tp_inst._updateDateTime(inst);
			return tp_inst.$input.val();
		}
		return this._base_formatDate(inst);
	};

	/*
	* override options setter to add time to maxDate(Time) and minDate(Time). MaxDate
	*/
	$.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker;
	$.datepicker._optionDatepicker = function (target, name, value) {
		var inst = this._getInst(target),
			name_clone;
		if (!inst) {
			return null;
		}

		var tp_inst = this._get(inst, 'timepicker');
		if (tp_inst) {
			var min = null,
				max = null,
				onselect = null,
				overrides = tp_inst._defaults.evnts,
				fns = {},
				prop;
			if (typeof name === 'string') { // if min/max was set with the string
				if (name === 'minDate' || name === 'minDateTime') {
					min = value;
				} else if (name === 'maxDate' || name === 'maxDateTime') {
					max = value;
				} else if (name === 'onSelect') {
					onselect = value;
				} else if (overrides.hasOwnProperty(name)) {
					if (typeof (value) === 'undefined') {
						return overrides[name];
					}
					fns[name] = value;
					name_clone = {}; //empty results in exiting function after overrides updated
				}
			} else if (typeof name === 'object') { //if min/max was set with the JSON
				if (name.minDate) {
					min = name.minDate;
				} else if (name.minDateTime) {
					min = name.minDateTime;
				} else if (name.maxDate) {
					max = name.maxDate;
				} else if (name.maxDateTime) {
					max = name.maxDateTime;
				}
				for (prop in overrides) {
					if (overrides.hasOwnProperty(prop) && name[prop]) {
						fns[prop] = name[prop];
					}
				}
			}
			for (prop in fns) {
				if (fns.hasOwnProperty(prop)) {
					overrides[prop] = fns[prop];
					if (!name_clone) { name_clone = $.extend({}, name); }
					delete name_clone[prop];
				}
			}
			if (name_clone && isEmptyObject(name_clone)) { return; }
			if (min) { //if min was set
				if (min === 0) {
					min = new Date();
				} else {
					min = new Date(min);
				}
				tp_inst._defaults.minDate = min;
				tp_inst._defaults.minDateTime = min;
			} else if (max) { //if max was set
				if (max === 0) {
					max = new Date();
				} else {
					max = new Date(max);
				}
				tp_inst._defaults.maxDate = max;
				tp_inst._defaults.maxDateTime = max;
			} else if (onselect) {
				tp_inst._defaults.onSelect = onselect;
			}
		}
		if (value === undefined) {
			return this._base_optionDatepicker.call($.datepicker, target, name);
		}
		return this._base_optionDatepicker.call($.datepicker, target, name_clone || name, value);
	};
	
	/*
	* jQuery isEmptyObject does not check hasOwnProperty - if someone has added to the object prototype,
	* it will return false for all objects
	*/
	var isEmptyObject = function (obj) {
		var prop;
		for (prop in obj) {
			if (obj.hasOwnProperty(prop)) {
				return false;
			}
		}
		return true;
	};

	/*
	* jQuery extend now ignores nulls!
	*/
	var extendRemove = function (target, props) {
		$.extend(target, props);
		for (var name in props) {
			if (props[name] === null || props[name] === undefined) {
				target[name] = props[name];
			}
		}
		return target;
	};

	/*
	* Determine by the time format which units are supported
	* Returns an object of booleans for each unit
	*/
	var detectSupport = function (timeFormat) {
		var tf = timeFormat.replace(/'.*?'/g, '').toLowerCase(), // removes literals
			isIn = function (f, t) { // does the format contain the token?
					return f.indexOf(t) !== -1 ? true : false;
				};
		return {
				hour: isIn(tf, 'h'),
				minute: isIn(tf, 'm'),
				second: isIn(tf, 's'),
				millisec: isIn(tf, 'l'),
				microsec: isIn(tf, 'c'),
				timezone: isIn(tf, 'z'),
				ampm: isIn(tf, 't') && isIn(timeFormat, 'h'),
				iso8601: isIn(timeFormat, 'Z')
			};
	};

	/*
	* Converts 24 hour format into 12 hour
	* Returns 12 hour without leading 0
	*/
	var convert24to12 = function (hour) {
		hour %= 12;

		if (hour === 0) {
			hour = 12;
		}

		return String(hour);
	};

	var computeEffectiveSetting = function (settings, property) {
		return settings && settings[property] ? settings[property] : $.timepicker._defaults[property];
	};

	/*
	* Splits datetime string into date and time substrings.
	* Throws exception when date can't be parsed
	* Returns {dateString: dateString, timeString: timeString}
	*/
	var splitDateTime = function (dateTimeString, timeSettings) {
		// The idea is to get the number separator occurrences in datetime and the time format requested (since time has
		// fewer unknowns, mostly numbers and am/pm). We will use the time pattern to split.
		var separator = computeEffectiveSetting(timeSettings, 'separator'),
			format = computeEffectiveSetting(timeSettings, 'timeFormat'),
			timeParts = format.split(separator), // how many occurrences of separator may be in our format?
			timePartsLen = timeParts.length,
			allParts = dateTimeString.split(separator),
			allPartsLen = allParts.length;

		if (allPartsLen > 1) {
			return {
				dateString: allParts.splice(0, allPartsLen - timePartsLen).join(separator),
				timeString: allParts.splice(0, timePartsLen).join(separator)
			};
		}

		return {
			dateString: dateTimeString,
			timeString: ''
		};
	};

	/*
	* Internal function to parse datetime interval
	* Returns: {date: Date, timeObj: Object}, where
	*   date - parsed date without time (type Date)
	*   timeObj = {hour: , minute: , second: , millisec: , microsec: } - parsed time. Optional
	*/
	var parseDateTimeInternal = function (dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) {
		var date,
			parts,
			parsedTime;

		parts = splitDateTime(dateTimeString, timeSettings);
		//date = $.datepicker._base_parseDate(dateFormat, parts.dateString, dateSettings);
		date = $.datepicker.parseDate(dateFormat, parts.dateStrings, dateSettings);

		if (parts.timeString === '') {
			return {
				date: date
			};
		}

		parsedTime = $.datepicker.parseTime(timeFormat, parts.timeString, timeSettings);

		if (!parsedTime) {
			throw 'Wrong time format';
		}

		return {
			date: date,
			timeObj: parsedTime
		};
	};

	/*
	* Internal function to set timezone_select to the local timezone
	*/
	var selectLocalTimezone = function (tp_inst, date) {
		if (tp_inst && tp_inst.timezone_select) {
			var now = date || new Date();
			tp_inst.timezone_select.val(-now.getTimezoneOffset());
		}
	};

	/*
	* Create a Singleton Instance
	*/
	$.timepicker = new Timepicker();

	/**
	 * Get the timezone offset as string from a date object (eg '+0530' for UTC+5.5)
	 * @param {number} tzMinutes if not a number, less than -720 (-1200), or greater than 840 (+1400) this value is returned
	 * @param {boolean} iso8601 if true formats in accordance to iso8601 "+12:45"
	 * @return {string}
	 */
	$.timepicker.timezoneOffsetString = function (tzMinutes, iso8601) {
		if (isNaN(tzMinutes) || tzMinutes > 840 || tzMinutes < -720) {
			return tzMinutes;
		}

		var off = tzMinutes,
			minutes = off % 60,
			hours = (off - minutes) / 60,
			iso = iso8601 ? ':' : '',
			tz = (off >= 0 ? '+' : '-') + ('0' + Math.abs(hours)).slice(-2) + iso + ('0' + Math.abs(minutes)).slice(-2);
		
		if (tz === '+00:00') {
			return 'Z';
		}
		return tz;
	};

	/**
	 * Get the number in minutes that represents a timezone string
	 * @param  {string} tzString formatted like "+0500", "-1245", "Z"
	 * @return {number} the offset minutes or the original string if it doesn't match expectations
	 */
	$.timepicker.timezoneOffsetNumber = function (tzString) {
		var normalized = tzString.toString().replace(':', ''); // excuse any iso8601, end up with "+1245"

		if (normalized.toUpperCase() === 'Z') { // if iso8601 with Z, its 0 minute offset
			return 0;
		}

		if (!/^(\-|\+)\d{4}$/.test(normalized)) { // possibly a user defined tz, so just give it back
			return tzString;
		}

		return ((normalized.substr(0, 1) === '-' ? -1 : 1) * // plus or minus
					((parseInt(normalized.substr(1, 2), 10) * 60) + // hours (converted to minutes)
					parseInt(normalized.substr(3, 2), 10))); // minutes
	};

	/**
	 * No way to set timezone in js Date, so we must adjust the minutes to compensate. (think setDate, getDate)
	 * @param  {Date} date
	 * @param  {string} toTimezone formatted like "+0500", "-1245"
	 * @return {Date}
	 */
	$.timepicker.timezoneAdjust = function (date, toTimezone) {
		var toTz = $.timepicker.timezoneOffsetNumber(toTimezone);
		if (!isNaN(toTz)) {
			date.setMinutes(date.getMinutes() + -date.getTimezoneOffset() - toTz);
		}
		return date;
	};

	/**
	 * Calls `timepicker()` on the `startTime` and `endTime` elements, and configures them to
	 * enforce date range limits.
	 * n.b. The input value must be correctly formatted (reformatting is not supported)
	 * @param  {Element} startTime
	 * @param  {Element} endTime
	 * @param  {Object} options Options for the timepicker() call
	 * @return {jQuery}
	 */
	$.timepicker.timeRange = function (startTime, endTime, options) {
		return $.timepicker.handleRange('timepicker', startTime, endTime, options);
	};

	/**
	 * Calls `datetimepicker` on the `startTime` and `endTime` elements, and configures them to
	 * enforce date range limits.
	 * @param  {Element} startTime
	 * @param  {Element} endTime
	 * @param  {Object} options Options for the `timepicker()` call. Also supports `reformat`,
	 *   a boolean value that can be used to reformat the input values to the `dateFormat`.
	 * @param  {string} method Can be used to specify the type of picker to be added
	 * @return {jQuery}
	 */
	$.timepicker.datetimeRange = function (startTime, endTime, options) {
		$.timepicker.handleRange('datetimepicker', startTime, endTime, options);
	};

	/**
	 * Calls `datepicker` on the `startTime` and `endTime` elements, and configures them to
	 * enforce date range limits.
	 * @param  {Element} startTime
	 * @param  {Element} endTime
	 * @param  {Object} options Options for the `timepicker()` call. Also supports `reformat`,
	 *   a boolean value that can be used to reformat the input values to the `dateFormat`.
	 * @return {jQuery}
	 */
	$.timepicker.dateRange = function (startTime, endTime, options) {
		$.timepicker.handleRange('datepicker', startTime, endTime, options);
	};

	/**
	 * Calls `method` on the `startTime` and `endTime` elements, and configures them to
	 * enforce date range limits.
	 * @param  {string} method Can be used to specify the type of picker to be added
	 * @param  {Element} startTime
	 * @param  {Element} endTime
	 * @param  {Object} options Options for the `timepicker()` call. Also supports `reformat`,
	 *   a boolean value that can be used to reformat the input values to the `dateFormat`.
	 * @return {jQuery}
	 */
	$.timepicker.handleRange = function (method, startTime, endTime, options) {
		options = $.extend({}, {
			minInterval: 0, // min allowed interval in milliseconds
			maxInterval: 0, // max allowed interval in milliseconds
			start: {},      // options for start picker
			end: {}         // options for end picker
		}, options);

		function checkDates(changed, other) {
			var startdt = startTime[method]('getDate'),
				enddt = endTime[method]('getDate'),
				changeddt = changed[method]('getDate');

			if (startdt !== null) {
				var minDate = new Date(startdt.getTime()),
					maxDate = new Date(startdt.getTime());

				minDate.setMilliseconds(minDate.getMilliseconds() + options.minInterval);
				maxDate.setMilliseconds(maxDate.getMilliseconds() + options.maxInterval);

				if (options.minInterval > 0 && minDate > enddt) { // minInterval check
					endTime[method]('setDate', minDate);
				}
				else if (options.maxInterval > 0 && maxDate < enddt) { // max interval check
					endTime[method]('setDate', maxDate);
				}
				else if (startdt > enddt) {
					other[method]('setDate', changeddt);
				}
			}
		}

		function selected(changed, other, option) {
			if (!changed.val()) {
				return;
			}
			var date = changed[method].call(changed, 'getDate');
			if (date !== null && options.minInterval > 0) {
				if (option === 'minDate') {
					date.setMilliseconds(date.getMilliseconds() + options.minInterval);
				}
				if (option === 'maxDate') {
					date.setMilliseconds(date.getMilliseconds() - options.minInterval);
				}
			}
			if (date.getTime) {
				other[method].call(other, 'option', option, date);
			}
		}

		$.fn[method].call(startTime, $.extend({
			onClose: function (dateText, inst) {
				checkDates($(this), endTime);
			},
			onSelect: function (selectedDateTime) {
				selected($(this), endTime, 'minDate');
			}
		}, options, options.start));
		$.fn[method].call(endTime, $.extend({
			onClose: function (dateText, inst) {
				checkDates($(this), startTime);
			},
			onSelect: function (selectedDateTime) {
				selected($(this), startTime, 'maxDate');
			}
		}, options, options.end));

		checkDates(startTime, endTime);
		selected(startTime, endTime, 'minDate');
		selected(endTime, startTime, 'maxDate');
		return $([startTime.get(0), endTime.get(0)]);
	};

	/**
	 * Log error or data to the console during error or debugging
	 * @param  {Object} err pass any type object to log to the console during error or debugging
	 * @return {void}
	 */
	$.timepicker.log = function (err) {
		if (window.console) {
			window.console.log(err);
		}
	};

	/*
	 * Add util object to allow access to private methods for testability.
	 */
	$.timepicker._util = {
		_extendRemove: extendRemove,
		_isEmptyObject: isEmptyObject,
		_convert24to12: convert24to12,
		_detectSupport: detectSupport,
		_selectLocalTimezone: selectLocalTimezone,
		_computeEffectiveSetting: computeEffectiveSetting,
		_splitDateTime: splitDateTime,
		_parseDateTimeInternal: parseDateTimeInternal
	};

	/*
	* Microsecond support
	*/
	if (!Date.prototype.getMicroseconds) {
		Date.prototype.microseconds = 0;
		Date.prototype.getMicroseconds = function () { return this.microseconds; };
		Date.prototype.setMicroseconds = function (m) {
			this.setMilliseconds(this.getMilliseconds() + Math.floor(m / 1000));
			this.microseconds = m % 1000;
			return this;
		};
	}

	/*
	* Keep up with the version
	*/
	$.timepicker.version = "1.4";

})(jQuery);
;
/*
 Input Mask plugin for jquery
 http://github.com/RobinHerbots/jquery.inputmask
 Copyright (c) 2010 - 2013 Robin Herbots
 Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.php)
 Version: 2.4.8
*/
(function(c){void 0===c.fn.inputmask&&(c.inputmask={defaults:{placeholder:"_",optionalmarker:{start:"[",end:"]"},quantifiermarker:{start:"{",end:"}"},groupmarker:{start:"(",end:")"},escapeChar:"\\",mask:null,oncomplete:c.noop,onincomplete:c.noop,oncleared:c.noop,repeat:0,greedy:!0,autoUnmask:!1,clearMaskOnLostFocus:!0,insertMode:!0,clearIncomplete:!1,aliases:{},onKeyUp:c.noop,onKeyDown:c.noop,onUnMask:void 0,showMaskOnFocus:!0,showMaskOnHover:!0,onKeyValidation:c.noop,skipOptionalPartCharacter:" ",
showTooltip:!1,numericInput:!1,isNumeric:!1,radixPoint:"",skipRadixDance:!1,rightAlignNumerics:!0,definitions:{9:{validator:"[0-9]",cardinality:1},a:{validator:"[A-Za-z\u0410-\u044f\u0401\u0451]",cardinality:1},"*":{validator:"[A-Za-z\u0410-\u044f\u0401\u04510-9]",cardinality:1}},keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,
NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91},ignorables:[8,9,13,19,27,33,34,35,36,37,38,39,40,45,46,93,112,113,114,115,116,117,118,119,120,121,122,123],getMaskLength:function(a,c,e,f,b){b=a.length;c||("*"==e?b=f.length+1:1<e&&(b+=a.length*(e-1)));return b}},escapeRegex:function(a){return a.replace(RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\)","gim"),"\\$1")}},c.fn.inputmask=function(a,
d){function e(a){var b=document.createElement("input");a="on"+a;var g=a in b;g||(b.setAttribute(a,"return;"),g="function"==typeof b[a]);return g}function f(a,b){var e=g.aliases[a];return e?(e.alias&&f(e.alias),c.extend(!0,g,e),c.extend(!0,g,b),!0):!1}function b(a){g.numericInput&&(a=a.split("").reverse().join(""));var b=!1,e=0,d=g.greedy,f=g.repeat;"*"==f&&(d=!1);1==a.length&&!1==d&&0!=f&&(g.placeholder="");a=c.map(a.split(""),function(a,c){var d=[];if(a==g.escapeChar)b=!0;else if(a!=g.optionalmarker.start&&
a!=g.optionalmarker.end||b){var f=g.definitions[a];if(f&&!b)for(var h=0;h<f.cardinality;h++)d.push(y(e+h));else d.push(a),b=!1;e+=d.length;return d}});for(var h=a.slice(),s=1;s<f&&d;s++)h=h.concat(a.slice());return{mask:h,repeat:f,greedy:d}}function h(a){g.numericInput&&(a=a.split("").reverse().join(""));var b=!1,e=!1,d=!1;return c.map(a.split(""),function(a,c){var f=[];if(a==g.escapeChar)e=!0;else{if(a!=g.optionalmarker.start||e){if(a!=g.optionalmarker.end||e){var h=g.definitions[a];if(h&&!e){for(var L=
h.prevalidator,k=L?L.length:0,p=1;p<h.cardinality;p++){var l=k>=p?L[p-1]:[],m=l.validator,l=l.cardinality;f.push({fn:m?"string"==typeof m?RegExp(m):new function(){this.test=m}:/./,cardinality:l?l:1,optionality:b,newBlockMarker:!0==b?d:!1,offset:0,casing:h.casing,def:h.definitionSymbol||a});!0==b&&(d=!1)}f.push({fn:h.validator?"string"==typeof h.validator?RegExp(h.validator):new function(){this.test=h.validator}:/./,cardinality:h.cardinality,optionality:b,newBlockMarker:d,offset:0,casing:h.casing,
def:h.definitionSymbol||a})}else f.push({fn:null,cardinality:0,optionality:b,newBlockMarker:d,offset:0,casing:null,def:a}),e=!1;d=!1;return f}b=!1}else b=!0;d=!0}})}function k(){function a(b){var e=b.length;for(i=0;i<e&&b.charAt(i)!=g.optionalmarker.start;i++);var d=[b.substring(0,i)];i<e&&d.push(b.substring(i+1,e));return d}function e(k,s,l){var r=0,x=0,p=s.length;for(i=0;i<p&&!(s.charAt(i)==g.optionalmarker.start&&r++,s.charAt(i)==g.optionalmarker.end&&x++,0<r&&r==x);i++);r=[s.substring(0,i)];i<
p&&r.push(s.substring(i+1,p));x=a(r[0]);1<x.length?(s=k+x[0]+(g.optionalmarker.start+x[1]+g.optionalmarker.end)+(1<r.length?r[1]:""),-1==c.inArray(s,f)&&""!=s&&(f.push(s),p=b(s),d.push({mask:s,_buffer:p.mask,buffer:p.mask.slice(),tests:h(s),lastValidPosition:-1,greedy:p.greedy,repeat:p.repeat,metadata:l})),s=k+x[0]+(1<r.length?r[1]:""),-1==c.inArray(s,f)&&""!=s&&(f.push(s),p=b(s),d.push({mask:s,_buffer:p.mask,buffer:p.mask.slice(),tests:h(s),lastValidPosition:-1,greedy:p.greedy,repeat:p.repeat,metadata:l})),
1<a(x[1]).length&&e(k+x[0],x[1]+r[1],l),1<r.length&&1<a(r[1]).length&&(e(k+x[0]+(g.optionalmarker.start+x[1]+g.optionalmarker.end),r[1],l),e(k+x[0],r[1],l))):(s=k+r,-1==c.inArray(s,f)&&""!=s&&(f.push(s),p=b(s),d.push({mask:s,_buffer:p.mask,buffer:p.mask.slice(),tests:h(s),lastValidPosition:-1,greedy:p.greedy,repeat:p.repeat,metadata:l})))}var d=[],f=[],k=[];c.isFunction(g.mask)&&(g.mask=g.mask.call(this,g));c.isArray(g.mask)?c.each(g.mask,function(a,b){void 0!=b.mask?e("",b.mask.toString(),b):e("",
b.toString())}):e("",g.mask.toString());(function(a){function b(){this.matches=[];this.isQuantifier=this.isOptional=this.isGroup=!1}var e=/(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??|[^.?*+^${[]()|\\]+|./g,d=new b,c,f=[];for(k=[];c=e.exec(a);)switch(c=c[0],c.charAt(0)){case g.optionalmarker.end:case g.groupmarker.end:c=f.pop();0<f.length?f[f.length-1].matches.push(c):(k.push(c),d=c);break;case g.optionalmarker.start:!d.isGroup&&0<d.matches.length&&k.push(d);d=new b;d.isOptional=!0;f.push(d);break;case g.groupmarker.start:!d.isGroup&&
0<d.matches.length&&k.push(d);d=new b;d.isGroup=!0;f.push(d);break;case g.quantifiermarker.start:var h=new b;h.isQuantifier=!0;h.matches.push(c);0<f.length?f[f.length-1].matches.push(h):d.matches.push(h);break;default:if(0<f.length)f[f.length-1].matches.push(c);else{if(d.isGroup||d.isOptional)d=new b;d.matches.push(c)}}0<d.matches.length&&k.push(d);return k})(g.mask);return g.greedy?d:d.sort(function(a,b){return a.mask.length-b.mask.length})}function y(a){return g.placeholder.charAt(a%g.placeholder.length)}
function n(a,b){function d(){return a[b]}function e(){return d().tests}function f(){return d()._buffer}function h(){return d().buffer}function k(f,e,D){function l(a,d,b,e){for(var f=n(a),h=b?1:0,c="",H=d.buffer,S=d.tests[f].cardinality;S>h;S--)c+=F(H,f-(S-1));b&&(c+=b);return null!=d.tests[f].fn?d.tests[f].fn.test(c,H,a,e,g):b==F(d._buffer,a,!0)||b==g.skipOptionalPartCharacter?{refresh:!0,c:F(d._buffer,a,!0),pos:a}:!1}if(D=!0===D){var m=l(f,d(),e,D);!0===m&&(m={pos:f});return m}var s=[],m=!1,u=b,
v=h().slice(),t=d().lastValidPosition;w(f);var y=[];c.each(a,function(a,g){if("object"==typeof g){b=a;var c=f,k=d().lastValidPosition,n;if(k==t){if(1<c-t)for(k=-1==k?0:k;k<c&&(n=l(k,d(),v[k],!0),!1!==n);k++)G(h(),k,v[k],!0),!0===n&&(n={pos:k}),n=n.pos||k,d().lastValidPosition<n&&(d().lastValidPosition=n);if(!r(c)&&!l(c,d(),e,D)){k=q(c)-c;for(n=0;n<k&&!1===l(++c,d(),e,D);n++);y.push(b)}}(d().lastValidPosition>=t||b==u)&&0<=c&&c<p()&&(m=l(c,d(),e,D),!1!==m&&(!0===m&&(m={pos:c}),n=m.pos||c,d().lastValidPosition<
n&&(d().lastValidPosition=n)),s.push({activeMasksetIndex:a,result:m}))}});b=u;return function(d,b){var h=!1;c.each(b,function(a,b){if(h=-1==c.inArray(b.activeMasksetIndex,d)&&!1!==b.result)return!1});if(h)b=c.map(b,function(b,f){if(-1==c.inArray(b.activeMasksetIndex,d))return b;a[b.activeMasksetIndex].lastValidPosition=t});else{var g=-1,k=-1;c.each(b,function(a,b){-1!=c.inArray(b.activeMasksetIndex,d)&&!1!==b.result&(-1==g||g>b.result.pos)&&(g=b.result.pos,k=b.activeMasksetIndex)});b=c.map(b,function(b,
h){if(-1!=c.inArray(b.activeMasksetIndex,d)){if(b.result.pos==g)return b;if(!1!==b.result){for(var H=f;H<g;H++)if(rsltValid=l(H,a[b.activeMasksetIndex],a[k].buffer[H],!0),!1===rsltValid){a[b.activeMasksetIndex].lastValidPosition=g-1;break}else G(a[b.activeMasksetIndex].buffer,H,a[k].buffer[H],!0),a[b.activeMasksetIndex].lastValidPosition=H;rsltValid=l(g,a[b.activeMasksetIndex],e,!0);!1!==rsltValid&&(G(a[b.activeMasksetIndex].buffer,g,e,!0),a[b.activeMasksetIndex].lastValidPosition=g);return b}}})}return b}(y,
s)}function l(){var f=b,e={activeMasksetIndex:0,lastValidPosition:-1,next:-1};c.each(a,function(a,f){"object"==typeof f&&(b=a,d().lastValidPosition>e.lastValidPosition?(e.activeMasksetIndex=a,e.lastValidPosition=d().lastValidPosition,e.next=q(d().lastValidPosition)):d().lastValidPosition==e.lastValidPosition&&(-1==e.next||e.next>q(d().lastValidPosition))&&(e.activeMasksetIndex=a,e.lastValidPosition=d().lastValidPosition,e.next=q(d().lastValidPosition)))});b=-1!=e.lastValidPosition&&a[f].lastValidPosition==
e.lastValidPosition?f:e.activeMasksetIndex;f!=b&&(U(h(),q(e.lastValidPosition),p()),d().writeOutBuffer=!0);u.data("_inputmask").activeMasksetIndex=b}function r(a){a=n(a);a=e()[a];return void 0!=a?a.fn:!1}function n(a){return a%e().length}function p(){return g.getMaskLength(f(),d().greedy,d().repeat,h(),g)}function q(a){var b=p();if(a>=b)return b;for(;++a<b&&!r(a););return a}function w(a){if(0>=a)return 0;for(;0<--a&&!r(a););return a}function G(a,b,d,f){f&&(b=P(a,b));f=e()[n(b)];var h=d;if(void 0!=
h&&void 0!=f)switch(f.casing){case "upper":h=d.toUpperCase();break;case "lower":h=d.toLowerCase()}a[b]=h}function F(a,b,d){d&&(b=P(a,b));return a[b]}function P(a,b){for(var d;void 0==a[b]&&a.length<p();)for(d=0;void 0!==f()[d];)a.push(f()[d++]);return b}function J(a,b,d){a._valueSet(b.join(""));void 0!=d&&v(a,d)}function U(a,b,d,e){for(var h=p();b<d&&b<h;b++)!0===e?r(b)||G(a,b,""):G(a,b,F(f().slice(),b,!0))}function Q(a,b){var d=n(b);G(a,b,F(f(),d))}function M(e,h,g,k,l){k=void 0!=k?k.slice():V(e._valueGet()).split("");
c.each(a,function(a,b){"object"==typeof b&&(b.buffer=b._buffer.slice(),b.lastValidPosition=-1,b.p=-1)});!0!==g&&(b=0);h&&e._valueSet("");p();c.each(k,function(a,b){if(!0===l){var k=d().p,k=-1==k?k:w(k),n=-1==k?a:q(k);-1==c.inArray(b,f().slice(k+1,n))&&c(e).trigger("_keypress",[!0,b.charCodeAt(0),h,g,a])}else c(e).trigger("_keypress",[!0,b.charCodeAt(0),h,g,a])});!0===g&&-1!=d().p&&(d().lastValidPosition=w(d().p))}function W(a){return c.inputmask.escapeRegex.call(this,a)}function V(a){return a.replace(RegExp("("+
W(f().join(""))+")*$"),"")}function X(a){var b=h(),d=b.slice(),f,g;for(g=d.length-1;0<=g;g--)if(f=n(g),e()[f].optionality)if(r(g)&&k(g,b[g],!0))break;else d.pop();else break;J(a,d)}function fa(a,b){if(!e()||!0!==b&&a.hasClass("hasDatepicker"))return a[0]._valueGet();var d=c.map(h(),function(a,b){return r(b)&&k(b,a,!0)?a:null}),d=(A?d.reverse():d).join("");return void 0!=g.onUnMask?g.onUnMask.call(this,h().join(""),d):d}function C(a){!A||"number"!=typeof a||g.greedy&&""==g.placeholder||(a=h().length-
a);return a}function v(a,b,d){var e=a.jquery&&0<a.length?a[0]:a;if("number"==typeof b)b=C(b),d=C(d),c(a).is(":visible")&&(d="number"==typeof d?d:b,e.scrollLeft=e.scrollWidth,!1==g.insertMode&&b==d&&d++,e.setSelectionRange?(e.selectionStart=b,e.selectionEnd=z?b:d):e.createTextRange&&(a=e.createTextRange(),a.collapse(!0),a.moveEnd("character",d),a.moveStart("character",b),a.select()));else{if(!c(a).is(":visible"))return{begin:0,end:0};e.setSelectionRange?(b=e.selectionStart,d=e.selectionEnd):document.selection&&
document.selection.createRange&&(a=document.selection.createRange(),b=0-a.duplicate().moveStart("character",-1E5),d=b+a.text.length);b=C(b);d=C(d);return{begin:b,end:d}}}function O(d){if("*"!=g.repeat){var e=!1,h=0,k=b;c.each(a,function(a,g){if("object"==typeof g){b=a;var c=w(p());if(g.lastValidPosition>=h&&g.lastValidPosition==c){for(var k=!0,l=0;l<=c;l++){var q=r(l),m=n(l);if(q&&(void 0==d[l]||d[l]==y(l))||!q&&d[l]!=f()[m]){k=!1;break}}if(e=e||k)return!1}h=g.lastValidPosition}});b=k;return e}}var A=
!1,K=h().join(""),u,ga;this.unmaskedvalue=function(a,b){A=a.data("_inputmask").isRTL;return fa(a,b)};this.isComplete=function(a){return O(a)};this.mask=function(z){function P(a){a=c._data(a).events;c.each(a,function(a,b){c.each(b,function(a,b){if("inputmask"==b.namespace&&"setvalue"!=b.type&&"_keypress"!=b.type){var d=b.handler;b.handler=function(a){if(this.readOnly||this.disabled)a.preventDefault;else return d.apply(this,arguments)}}})})}function D(a){var b;Object.getOwnPropertyDescriptor&&(b=Object.getOwnPropertyDescriptor(a,
"value"));if(b&&b.get){if(!a._valueGet){var d=b.get,e=b.set;a._valueGet=function(){return A?d.call(this).split("").reverse().join(""):d.call(this)};a._valueSet=function(a){e.call(this,A?a.split("").reverse().join(""):a)};Object.defineProperty(a,"value",{get:function(){var a=c(this),b=c(this).data("_inputmask"),e=b.masksets,f=b.activeMasksetIndex;return b&&b.opts.autoUnmask?a.inputmask("unmaskedvalue"):d.call(this)!=e[f]._buffer.join("")?d.call(this):""},set:function(a){e.call(this,a);c(this).triggerHandler("setvalue.inputmask")}})}}else if(document.__lookupGetter__&&
a.__lookupGetter__("value"))a._valueGet||(d=a.__lookupGetter__("value"),e=a.__lookupSetter__("value"),a._valueGet=function(){return A?d.call(this).split("").reverse().join(""):d.call(this)},a._valueSet=function(a){e.call(this,A?a.split("").reverse().join(""):a)},a.__defineGetter__("value",function(){var a=c(this),b=c(this).data("_inputmask"),e=b.masksets,f=b.activeMasksetIndex;return b&&b.opts.autoUnmask?a.inputmask("unmaskedvalue"):d.call(this)!=e[f]._buffer.join("")?d.call(this):""}),a.__defineSetter__("value",
function(a){e.call(this,a);c(this).triggerHandler("setvalue.inputmask")}));else if(a._valueGet||(a._valueGet=function(){return A?this.value.split("").reverse().join(""):this.value},a._valueSet=function(a){this.value=A?a.split("").reverse().join(""):a}),void 0==c.valHooks.text||!0!=c.valHooks.text.inputmaskpatch)d=c.valHooks.text&&c.valHooks.text.get?c.valHooks.text.get:function(a){return a.value},e=c.valHooks.text&&c.valHooks.text.set?c.valHooks.text.set:function(a,b){a.value=b;return a},jQuery.extend(c.valHooks,
{text:{get:function(a){var b=c(a);if(b.data("_inputmask")){if(b.data("_inputmask").opts.autoUnmask)return b.inputmask("unmaskedvalue");a=d(a);b=b.data("_inputmask");return a!=b.masksets[b.activeMasksetIndex]._buffer.join("")?a:""}return d(a)},set:function(a,b){var d=c(a),f=e(a,b);d.data("_inputmask")&&d.triggerHandler("setvalue.inputmask");return f},inputmaskpatch:!0}})}function Y(a,b,g,c){var l=h();if(!1!==c)for(;!r(a)&&0<=a-1;)a--;for(c=a;c<b&&c<p();c++)if(r(c)){Q(l,c);var m=q(c),L=F(l,m);if(L!=
y(m))if(m<p()&&!1!==k(c,L,!0)&&e()[n(c)].def==e()[n(m)].def)G(l,c,L,!0);else if(r(c))break}else Q(l,c);void 0!=g&&G(l,w(b),g);if(!1==d().greedy){b=V(l.join("")).split("");l.length=b.length;c=0;for(g=l.length;c<g;c++)l[c]=b[c];0==l.length&&(d().buffer=f().slice())}return a}function ba(a,b,g){var c=h();if(F(c,a,!0)!=y(a))for(var l=w(b);l>a&&0<=l;l--)if(r(l)){var m=w(l),q=F(c,m);if(q!=y(m))if(!1!==k(m,q,!0)&&e()[n(l)].def==e()[n(m)].def)G(c,l,q,!0),Q(c,m);else break}else Q(c,l);void 0!=g&&F(c,a)==y(a)&&
G(c,a,g);a=c.length;if(!1==d().greedy){g=V(c.join("")).split("");c.length=g.length;l=0;for(m=c.length;l<m;l++)c[l]=g[l];0==c.length&&(d().buffer=f().slice())}return b-(a-c.length)}function ca(b,e,c){if(g.numericInput||A){switch(e){case g.keyCode.BACKSPACE:e=g.keyCode.DELETE;break;case g.keyCode.DELETE:e=g.keyCode.BACKSPACE}if(A){var f=c.end;c.end=c.begin;c.begin=f}}f=!0;c.begin==c.end?(f=e==g.keyCode.BACKSPACE?c.begin-1:c.begin,g.isNumeric&&""!=g.radixPoint&&h()[f]==g.radixPoint&&(c.begin=h().length-
1==f?c.begin:e==g.keyCode.BACKSPACE?f:q(f),c.end=c.begin),f=!1,e==g.keyCode.BACKSPACE?c.begin--:e==g.keyCode.DELETE&&c.end++):1!=c.end-c.begin||g.insertMode||(f=!1,e==g.keyCode.BACKSPACE&&c.begin--);U(h(),c.begin,c.end);var k=p();if(!1==g.greedy)Y(c.begin,k,void 0,!A&&e==g.keyCode.BACKSPACE&&!f);else{for(var l=c.begin,m=c.begin;m<c.end;m++)if(r(m)||!f)l=Y(c.begin,k,void 0,!A&&e==g.keyCode.BACKSPACE&&!f);f||(c.begin=l)}e=q(-1);U(h(),c.begin,c.end,!0);M(b,!1,void 0==a[1]||e>=c.end,h());d().lastValidPosition<
e?(d().lastValidPosition=-1,d().p=e):d().p=c.begin}function da(a){T=!1;var b=this,e=c(b),k=a.keyCode,m=v(b);k==g.keyCode.BACKSPACE||k==g.keyCode.DELETE||t&&127==k||a.ctrlKey&&88==k?(a.preventDefault(),88==k&&(K=h().join("")),ca(b,k,m),l(),J(b,h(),d().p),b._valueGet()==f().join("")&&e.trigger("cleared"),g.showTooltip&&e.prop("title",d().mask)):k==g.keyCode.END||k==g.keyCode.PAGE_DOWN?setTimeout(function(){var e=q(d().lastValidPosition);g.insertMode||e!=p()||a.shiftKey||e--;v(b,a.shiftKey?m.begin:e,
e)},0):k==g.keyCode.HOME&&!a.shiftKey||k==g.keyCode.PAGE_UP?v(b,0,a.shiftKey?m.begin:0):k==g.keyCode.ESCAPE||90==k&&a.ctrlKey?(M(b,!0,!1,K.split("")),e.click()):k!=g.keyCode.INSERT||a.shiftKey||a.ctrlKey?!1!=g.insertMode||a.shiftKey||(k==g.keyCode.RIGHT?setTimeout(function(){var a=v(b);v(b,a.begin)},0):k==g.keyCode.LEFT&&setTimeout(function(){var a=v(b);v(b,a.begin-1)},0)):(g.insertMode=!g.insertMode,v(b,g.insertMode||m.begin!=p()?m.begin:m.begin-1));e=v(b);!0===g.onKeyDown.call(this,a,h(),g)&&v(b,
e.begin,e.end);Z=-1!=c.inArray(k,g.ignorables)}function ea(e,f,m,n,r,z){if(void 0==m&&T)return!1;T=!0;var u=c(this);e=e||window.event;m=m||e.which||e.charCode||e.keyCode;if((!e.ctrlKey||!e.altKey)&&(e.ctrlKey||e.metaKey||Z)&&!0!==f)return!0;if(m){!0!==f&&46==m&&!1==e.shiftKey&&","==g.radixPoint&&(m=44);var t,N,x=String.fromCharCode(m);f?(m=r?z:d().lastValidPosition+1,t={begin:m,end:m}):t=v(this);z=A?1<t.begin-t.end||1==t.begin-t.end&&g.insertMode:1<t.end-t.begin||1==t.end-t.begin&&g.insertMode;var D=
b;z&&(b=D,c.each(a,function(a,e){"object"==typeof e&&(b=a,d().undoBuffer=h().join(""))}),ca(this,g.keyCode.DELETE,t),g.insertMode||c.each(a,function(a,e){"object"==typeof e&&(b=a,ba(t.begin,p()),d().lastValidPosition=q(d().lastValidPosition))}),b=D);var C=h().join("").indexOf(g.radixPoint);g.isNumeric&&!0!==f&&-1!=C&&(g.greedy&&t.begin<=C?(t.begin=w(t.begin),t.end=t.begin):x==g.radixPoint&&(t.begin=C,t.end=t.begin));var B=t.begin;m=k(B,x,r);!0===r&&(m=[{activeMasksetIndex:b,result:m}]);var E=-1;c.each(m,
function(a,e){b=e.activeMasksetIndex;d().writeOutBuffer=!0;var c=e.result;if(!1!==c){var f=!1,k=h();!0!==c&&(f=c.refresh,B=void 0!=c.pos?c.pos:B,x=void 0!=c.c?c.c:x);if(!0!==f){if(!0==g.insertMode){c=p();for(k=k.slice();F(k,c,!0)!=y(c)&&c>=B;)c=0==c?-1:w(c);c>=B?(ba(B,p(),x),k=d().lastValidPosition,c=q(k),c!=p()&&k>=B&&F(h(),c,!0)!=y(c)&&(d().lastValidPosition=c)):d().writeOutBuffer=!1}else G(k,B,x,!0);if(-1==E||E>q(B))E=q(B)}else!r&&(k=B<p()?B+1:B,-1==E||E>k)&&(E=k);E>d().p&&(d().p=E)}});!0!==r&&
(b=D,l());if(!1!==n&&(c.each(m,function(a,d){if(d.activeMasksetIndex==b)return N=d,!1}),void 0!=N)){var K=this;setTimeout(function(){g.onKeyValidation.call(K,N.result,g)},0);if(d().writeOutBuffer&&!1!==N.result){var I=h();n=f?void 0:g.numericInput?B>C?w(E):x==g.radixPoint?E-1:w(E-1):E;J(this,I,n);!0!==f&&setTimeout(function(){!0===O(I)&&u.trigger("complete")},0)}else z&&(d().buffer=d().undoBuffer.split(""))}g.showTooltip&&u.prop("title",d().mask);e.preventDefault()}}function W(a){var b=c(this),d=
a.keyCode,e=h();m&&d==g.keyCode.BACKSPACE&&ga==this._valueGet()&&da.call(this,a);g.onKeyUp.call(this,a,e,g);d==g.keyCode.TAB&&g.showMaskOnFocus&&(b.hasClass("focus.inputmask")&&0==this._valueGet().length?(e=f().slice(),J(this,e),v(this,0),K=h().join("")):(J(this,e),e.join("")==f().join("")&&-1!=c.inArray(g.radixPoint,e)?(v(this,C(0)),b.click()):v(this,C(0),C(p()))))}u=c(z);if(u.is(":input")){u.data("_inputmask",{masksets:a,activeMasksetIndex:b,opts:g,isRTL:!1});g.showTooltip&&u.prop("title",d().mask);
d().greedy=d().greedy?d().greedy:0==d().repeat;if(null!=u.attr("maxLength")){var I=u.prop("maxLength");-1<I&&c.each(a,function(a,b){"object"==typeof b&&"*"==b.repeat&&(b.repeat=I)});p()>=I&&-1<I&&(I<f().length&&(f().length=I),!1==d().greedy&&(d().repeat=Math.round(I/f().length)),u.prop("maxLength",2*p()))}D(z);var T=!1,Z=!1;g.numericInput&&(g.isNumeric=g.numericInput);("rtl"==z.dir||g.numericInput&&g.rightAlignNumerics||g.isNumeric&&g.rightAlignNumerics)&&u.css("text-align","right");if("rtl"==z.dir||
g.numericInput){z.dir="ltr";u.removeAttr("dir");var $=u.data("_inputmask");$.isRTL=!0;u.data("_inputmask",$);A=!0}u.unbind(".inputmask");u.removeClass("focus.inputmask");u.closest("form").bind("submit",function(){K!=h().join("")&&u.change()}).bind("reset",function(){setTimeout(function(){u.trigger("setvalue")},0)});u.bind("mouseenter.inputmask",function(){!c(this).hasClass("focus.inputmask")&&g.showMaskOnHover&&this._valueGet()!=h().join("")&&J(this,h())}).bind("blur.inputmask",function(){var d=c(this),
e=this._valueGet(),k=h();d.removeClass("focus.inputmask");K!=h().join("")&&d.change();g.clearMaskOnLostFocus&&""!=e&&(e==f().join("")?this._valueSet(""):X(this));!1===O(k)&&(d.trigger("incomplete"),g.clearIncomplete&&(c.each(a,function(a,b){"object"==typeof b&&(b.buffer=b._buffer.slice(),b.lastValidPosition=-1)}),b=0,g.clearMaskOnLostFocus?this._valueSet(""):(k=f().slice(),J(this,k))))}).bind("focus.inputmask",function(){var a=c(this),b=this._valueGet();g.showMaskOnFocus&&!a.hasClass("focus.inputmask")&&
(!g.showMaskOnHover||g.showMaskOnHover&&""==b)&&this._valueGet()!=h().join("")&&J(this,h(),q(d().lastValidPosition));a.addClass("focus.inputmask");K=h().join("")}).bind("mouseleave.inputmask",function(){var a=c(this);g.clearMaskOnLostFocus&&(a.hasClass("focus.inputmask")||this._valueGet()==a.attr("placeholder")||(this._valueGet()==f().join("")||""==this._valueGet()?this._valueSet(""):X(this)))}).bind("click.inputmask",function(){var a=this;setTimeout(function(){var b=v(a),e=h();if(b.begin==b.end){var b=
g.isRTL?C(b.begin):b.begin,f=d().lastValidPosition,e=g.isNumeric?!1===g.skipRadixDance&&""!=g.radixPoint&&-1!=c.inArray(g.radixPoint,e)?g.numericInput?q(c.inArray(g.radixPoint,e)):c.inArray(g.radixPoint,e):q(f):q(f);b<e?r(b)?v(a,b):v(a,q(b)):v(a,e)}},0)}).bind("dblclick.inputmask",function(){var a=this;setTimeout(function(){v(a,0,q(d().lastValidPosition))},0)}).bind(N+".inputmask dragdrop.inputmask drop.inputmask",function(a){var b=this,d=c(b);if("propertychange"==a.type&&b._valueGet().length<=p())return!0;
setTimeout(function(){M(b,!0,!1,void 0,!0);!0===O(h())&&d.trigger("complete");d.click()},0)}).bind("setvalue.inputmask",function(){M(this,!0);K=h().join("");this._valueGet()==f().join("")&&this._valueSet("")}).bind("_keypress.inputmask",ea).bind("complete.inputmask",g.oncomplete).bind("incomplete.inputmask",g.onincomplete).bind("cleared.inputmask",g.oncleared).bind("keyup.inputmask",W);m?u.bind("input.inputmask",function(a){a=c(this);ga=h().join("");M(this,!1,!1);J(this,h());!0===O(h())&&a.trigger("complete");
a.click()}):u.bind("keydown.inputmask",da).bind("keypress.inputmask",ea);M(z,!0,!1);K=h().join("");var aa;try{aa=document.activeElement}catch(fa){}aa===z?(u.addClass("focus.inputmask"),v(z,q(d().lastValidPosition))):g.clearMaskOnLostFocus?h().join("")==f().join("")?z._valueSet(""):X(z):J(z,h());P(z)}};return this}var g=c.extend(!0,{},c.inputmask.defaults,d),w=null!==navigator.userAgent.match(/msie 10/i),t=null!==navigator.userAgent.match(/iphone/i),z=null!==navigator.userAgent.match(/android.*safari.*/i),
m=null!==navigator.userAgent.match(/android.*chrome.*/i),N=e("paste")&&!w?"paste":e("input")?"input":"propertychange",l,q=0;if("string"===typeof a)switch(a){case "mask":return f(g.alias,d),l=k(),0==l.length?this:this.each(function(){n(c.extend(!0,{},l),0).mask(this)});case "unmaskedvalue":return w=c(this),w.data("_inputmask")?(l=w.data("_inputmask").masksets,q=w.data("_inputmask").activeMasksetIndex,g=w.data("_inputmask").opts,n(l,q).unmaskedvalue(w)):w.val();case "remove":return this.each(function(){var a=
c(this);if(a.data("_inputmask")){l=a.data("_inputmask").masksets;q=a.data("_inputmask").activeMasksetIndex;g=a.data("_inputmask").opts;this._valueSet(n(l,q).unmaskedvalue(a,!0));a.removeData("_inputmask");a.unbind(".inputmask");a.removeClass("focus.inputmask");var b;Object.getOwnPropertyDescriptor&&(b=Object.getOwnPropertyDescriptor(this,"value"));b&&b.get?this._valueGet&&Object.defineProperty(this,"value",{get:this._valueGet,set:this._valueSet}):document.__lookupGetter__&&this.__lookupGetter__("value")&&
this._valueGet&&(this.__defineGetter__("value",this._valueGet),this.__defineSetter__("value",this._valueSet));try{delete this._valueGet,delete this._valueSet}catch(d){this._valueSet=this._valueGet=void 0}}});case "getemptymask":return this.data("_inputmask")?(l=this.data("_inputmask").masksets,q=this.data("_inputmask").activeMasksetIndex,l[q]._buffer.join("")):"";case "hasMaskedValue":return this.data("_inputmask")?!this.data("_inputmask").opts.autoUnmask:!1;case "isComplete":return l=this.data("_inputmask").masksets,
q=this.data("_inputmask").activeMasksetIndex,g=this.data("_inputmask").opts,n(l,q).isComplete(this[0]._valueGet().split(""));case "getmetadata":if(this.data("_inputmask"))return l=this.data("_inputmask").masksets,q=this.data("_inputmask").activeMasksetIndex,l[q].metadata;return;default:return f(a,d)||(g.mask=a),l=k(),0==l.length?this:this.each(function(){n(c.extend(!0,{},l),q).mask(this)})}else{if("object"==typeof a)return g=c.extend(!0,{},c.inputmask.defaults,a),f(g.alias,a),l=k(),0==l.length?this:
this.each(function(){n(c.extend(!0,{},l),q).mask(this)});if(void 0==a)return this.each(function(){var a=c(this).attr("data-inputmask");if(a&&""!=a)try{var a=a.replace(RegExp("'","g"),'"'),b=c.parseJSON("{"+a+"}");c.extend(!0,b,d);g=c.extend(!0,{},c.inputmask.defaults,b);f(g.alias,b);g.alias=void 0;c(this).inputmask(g)}catch(e){}})}return this})})(jQuery);
(function(c){c.extend(c.inputmask.defaults.definitions,{A:{validator:"[A-Za-z]",cardinality:1,casing:"upper"},"#":{validator:"[A-Za-z\u0410-\u044f\u0401\u04510-9]",cardinality:1,casing:"upper"}});c.extend(c.inputmask.defaults.aliases,{url:{mask:"ir",placeholder:"",separator:"",defaultPrefix:"http://",regex:{urlpre1:/[fh]/,urlpre2:/(ft|ht)/,urlpre3:/(ftp|htt)/,urlpre4:/(ftp:|http|ftps)/,urlpre5:/(ftp:\/|ftps:|http:|https)/,urlpre6:/(ftp:\/\/|ftps:\/|http:\/|https:)/,urlpre7:/(ftp:\/\/|ftps:\/\/|http:\/\/|https:\/)/,
urlpre8:/(ftp:\/\/|ftps:\/\/|http:\/\/|https:\/\/)/},definitions:{i:{validator:function(a,d,e,c,b){return!0},cardinality:8,prevalidator:function(){for(var a=[],d=0;8>d;d++)a[d]=function(){var a=d;return{validator:function(d,b,c,k,y){if(y.regex["urlpre"+(a+1)]){var n=d;0<a+1-d.length&&(n=b.join("").substring(0,a+1-d.length)+""+n);d=y.regex["urlpre"+(a+1)].test(n);if(!k&&!d){c-=a;for(k=0;k<y.defaultPrefix.length;k++)b[c]=y.defaultPrefix[k],c++;for(k=0;k<n.length-1;k++)b[c]=n[k],c++;return{pos:c}}return d}return!1},
cardinality:a}}();return a}()},r:{validator:".",cardinality:50}},insertMode:!1,autoUnmask:!1},ip:{mask:["[[x]y]z.[[x]y]z.[[x]y]z.x[yz]","[[x]y]z.[[x]y]z.[[x]y]z.[[x]y][z]"],definitions:{x:{validator:"[012]",cardinality:1,definitionSymbol:"i"},y:{validator:function(a,d,e,c,b){a=-1<e-1&&"."!=d[e-1]?d[e-1]+a:"0"+a;return/2[0-5]|[01][0-9]/.test(a)},cardinality:1,definitionSymbol:"i"},z:{validator:function(a,d,e,c,b){-1<e-1&&"."!=d[e-1]?(a=d[e-1]+a,a=-1<e-2&&"."!=d[e-2]?d[e-2]+a:"0"+a):a="00"+a;return/25[0-5]|2[0-4][0-9]|[01][0-9][0-9]/.test(a)},
cardinality:1,definitionSymbol:"i"}}}})})(jQuery);
(function(c){c.extend(c.inputmask.defaults.definitions,{h:{validator:"[01][0-9]|2[0-3]",cardinality:2,prevalidator:[{validator:"[0-2]",cardinality:1}]},s:{validator:"[0-5][0-9]",cardinality:2,prevalidator:[{validator:"[0-5]",cardinality:1}]},d:{validator:"0[1-9]|[12][0-9]|3[01]",cardinality:2,prevalidator:[{validator:"[0-3]",cardinality:1}]},m:{validator:"0[1-9]|1[012]",cardinality:2,prevalidator:[{validator:"[01]",cardinality:1}]},y:{validator:"(19|20)\\d{2}",cardinality:4,prevalidator:[{validator:"[12]",
cardinality:1},{validator:"(19|20)",cardinality:2},{validator:"(19|20)\\d",cardinality:3}]}});c.extend(c.inputmask.defaults.aliases,{"dd/mm/yyyy":{mask:"1/2/y",placeholder:"dd/mm/yyyy",regex:{val1pre:/[0-3]/,val1:/0[1-9]|[12][0-9]|3[01]/,val2pre:function(a){a=c.inputmask.escapeRegex.call(this,a);return RegExp("((0[1-9]|[12][0-9]|3[01])"+a+"[01])")},val2:function(a){a=c.inputmask.escapeRegex.call(this,a);return RegExp("((0[1-9]|[12][0-9])"+a+"(0[1-9]|1[012]))|(30"+a+"(0[13-9]|1[012]))|(31"+a+"(0[13578]|1[02]))")}},
leapday:"29/02/",separator:"/",yearrange:{minyear:1900,maxyear:2099},isInYearRange:function(a,d,e){var c=parseInt(a.concat(d.toString().slice(a.length)));a=parseInt(a.concat(e.toString().slice(a.length)));return(NaN!=c?d<=c&&c<=e:!1)||(NaN!=a?d<=a&&a<=e:!1)},determinebaseyear:function(a,d,e){var c=(new Date).getFullYear();if(a>c)return a;if(d<c){for(var c=d.toString().slice(0,2),b=d.toString().slice(2,4);d<c+e;)c--;d=c+b;return a>d?a:d}return c},onKeyUp:function(a,d,e){d=c(this);a.ctrlKey&&a.keyCode==
e.keyCode.RIGHT&&(a=new Date,d.val(a.getDate().toString()+(a.getMonth()+1).toString()+a.getFullYear().toString()))},definitions:{1:{validator:function(a,d,c,f,b){var h=b.regex.val1.test(a);return f||h||a.charAt(1)!=b.separator&&-1=="-./".indexOf(a.charAt(1))||!(h=b.regex.val1.test("0"+a.charAt(0)))?h:(d[c-1]="0",{pos:c,c:a.charAt(0)})},cardinality:2,prevalidator:[{validator:function(a,d,c,f,b){var h=b.regex.val1pre.test(a);return f||h||!(h=b.regex.val1.test("0"+a))?h:(d[c]="0",c++,{pos:c})},cardinality:1}]},
2:{validator:function(a,d,c,f,b){var h=d.join("").substr(0,3),k=b.regex.val2(b.separator).test(h+a);return f||k||a.charAt(1)!=b.separator&&-1=="-./".indexOf(a.charAt(1))||!(k=b.regex.val2(b.separator).test(h+"0"+a.charAt(0)))?k:(d[c-1]="0",{pos:c,c:a.charAt(0)})},cardinality:2,prevalidator:[{validator:function(a,d,c,f,b){var h=d.join("").substr(0,3),k=b.regex.val2pre(b.separator).test(h+a);return f||k||!(k=b.regex.val2(b.separator).test(h+"0"+a))?k:(d[c]="0",c++,{pos:c})},cardinality:1}]},y:{validator:function(a,
d,c,f,b){if(b.isInYearRange(a,b.yearrange.minyear,b.yearrange.maxyear)){if(d.join("").substr(0,6)!=b.leapday)return!0;a=parseInt(a,10);return 0===a%4?0===a%100?0===a%400?!0:!1:!0:!1}return!1},cardinality:4,prevalidator:[{validator:function(a,d,c,f,b){var h=b.isInYearRange(a,b.yearrange.minyear,b.yearrange.maxyear);if(!f&&!h){f=b.determinebaseyear(b.yearrange.minyear,b.yearrange.maxyear,a+"0").toString().slice(0,1);if(h=b.isInYearRange(f+a,b.yearrange.minyear,b.yearrange.maxyear))return d[c++]=f[0],
{pos:c};f=b.determinebaseyear(b.yearrange.minyear,b.yearrange.maxyear,a+"0").toString().slice(0,2);if(h=b.isInYearRange(f+a,b.yearrange.minyear,b.yearrange.maxyear))return d[c++]=f[0],d[c++]=f[1],{pos:c}}return h},cardinality:1},{validator:function(a,d,c,f,b){var h=b.isInYearRange(a,b.yearrange.minyear,b.yearrange.maxyear);if(!f&&!h){f=b.determinebaseyear(b.yearrange.minyear,b.yearrange.maxyear,a).toString().slice(0,2);if(h=b.isInYearRange(a[0]+f[1]+a[1],b.yearrange.minyear,b.yearrange.maxyear))return d[c++]=
f[1],{pos:c};f=b.determinebaseyear(b.yearrange.minyear,b.yearrange.maxyear,a).toString().slice(0,2);b.isInYearRange(f+a,b.yearrange.minyear,b.yearrange.maxyear)?d.join("").substr(0,6)!=b.leapday?h=!0:(b=parseInt(a,10),h=0===b%4?0===b%100?0===b%400?!0:!1:!0:!1):h=!1;if(h)return d[c-1]=f[0],d[c++]=f[1],d[c++]=a[0],{pos:c}}return h},cardinality:2},{validator:function(a,d,c,f,b){return b.isInYearRange(a,b.yearrange.minyear,b.yearrange.maxyear)},cardinality:3}]}},insertMode:!1,autoUnmask:!1},"mm/dd/yyyy":{placeholder:"mm/dd/yyyy",
alias:"dd/mm/yyyy",regex:{val2pre:function(a){a=c.inputmask.escapeRegex.call(this,a);return RegExp("((0[13-9]|1[012])"+a+"[0-3])|(02"+a+"[0-2])")},val2:function(a){a=c.inputmask.escapeRegex.call(this,a);return RegExp("((0[1-9]|1[012])"+a+"(0[1-9]|[12][0-9]))|((0[13-9]|1[012])"+a+"30)|((0[13578]|1[02])"+a+"31)")},val1pre:/[01]/,val1:/0[1-9]|1[012]/},leapday:"02/29/",onKeyUp:function(a,d,e){d=c(this);a.ctrlKey&&a.keyCode==e.keyCode.RIGHT&&(a=new Date,d.val((a.getMonth()+1).toString()+a.getDate().toString()+
a.getFullYear().toString()))}},"yyyy/mm/dd":{mask:"y/1/2",placeholder:"yyyy/mm/dd",alias:"mm/dd/yyyy",leapday:"/02/29",onKeyUp:function(a,d,e){d=c(this);a.ctrlKey&&a.keyCode==e.keyCode.RIGHT&&(a=new Date,d.val(a.getFullYear().toString()+(a.getMonth()+1).toString()+a.getDate().toString()))},definitions:{2:{validator:function(a,d,c,f,b){var h=d.join("").substr(5,3),k=b.regex.val2(b.separator).test(h+a);if(!(f||k||a.charAt(1)!=b.separator&&-1=="-./".indexOf(a.charAt(1)))&&(k=b.regex.val2(b.separator).test(h+
"0"+a.charAt(0))))return d[c-1]="0",{pos:c,c:a.charAt(0)};if(k){if(d.join("").substr(4,4)+a!=b.leapday)return!0;a=parseInt(d.join("").substr(0,4),10);return 0===a%4?0===a%100?0===a%400?!0:!1:!0:!1}return k},cardinality:2,prevalidator:[{validator:function(a,d,c,f,b){var h=d.join("").substr(5,3),k=b.regex.val2pre(b.separator).test(h+a);return f||k||!(k=b.regex.val2(b.separator).test(h+"0"+a))?k:(d[c]="0",c++,{pos:c})},cardinality:1}]}}},"dd.mm.yyyy":{mask:"1.2.y",placeholder:"dd.mm.yyyy",leapday:"29.02.",
separator:".",alias:"dd/mm/yyyy"},"dd-mm-yyyy":{mask:"1-2-y",placeholder:"dd-mm-yyyy",leapday:"29-02-",separator:"-",alias:"dd/mm/yyyy"},"mm.dd.yyyy":{mask:"1.2.y",placeholder:"mm.dd.yyyy",leapday:"02.29.",separator:".",alias:"mm/dd/yyyy"},"mm-dd-yyyy":{mask:"1-2-y",placeholder:"mm-dd-yyyy",leapday:"02-29-",separator:"-",alias:"mm/dd/yyyy"},"yyyy.mm.dd":{mask:"y.1.2",placeholder:"yyyy.mm.dd",leapday:".02.29",separator:".",alias:"yyyy/mm/dd"},"yyyy-mm-dd":{mask:"y-1-2",placeholder:"yyyy-mm-dd",leapday:"-02-29",
separator:"-",alias:"yyyy/mm/dd"},datetime:{mask:"1/2/y h:s",placeholder:"dd/mm/yyyy hh:mm",alias:"dd/mm/yyyy",regex:{hrspre:/[012]/,hrs24:/2[0-9]|1[3-9]/,hrs:/[01][0-9]|2[0-3]/,ampm:/^[a|p|A|P][m|M]/},timeseparator:":",hourFormat:"24",definitions:{h:{validator:function(a,d,c,f,b){var h=b.regex.hrs.test(a);return f||h||a.charAt(1)!=b.timeseparator&&-1=="-.:".indexOf(a.charAt(1))||!(h=b.regex.hrs.test("0"+a.charAt(0)))?h&&"24"!==b.hourFormat&&b.regex.hrs24.test(a)?(a=parseInt(a,10),d[c+5]=24==a?"a":
"p",d[c+6]="m",a-=12,10>a?(d[c]=a.toString(),d[c-1]="0"):(d[c]=a.toString().charAt(1),d[c-1]=a.toString().charAt(0)),{pos:c,c:d[c]}):h:(d[c-1]="0",d[c]=a.charAt(0),c++,{pos:c})},cardinality:2,prevalidator:[{validator:function(a,d,c,f,b){var h=b.regex.hrspre.test(a);return f||h||!(h=b.regex.hrs.test("0"+a))?h:(d[c]="0",c++,{pos:c})},cardinality:1}]},t:{validator:function(a,c,e,f,b){return b.regex.ampm.test(a+"m")},casing:"lower",cardinality:1}},insertMode:!1,autoUnmask:!1},datetime12:{mask:"1/2/y h:s t\\m",
placeholder:"dd/mm/yyyy hh:mm xm",alias:"datetime",hourFormat:"12"},"hh:mm t":{mask:"h:s t\\m",placeholder:"hh:mm xm",alias:"datetime",hourFormat:"12"},"h:s t":{mask:"h:s t\\m",placeholder:"hh:mm xm",alias:"datetime",hourFormat:"12"},"hh:mm:ss":{mask:"h:s:s",autoUnmask:!1},"hh:mm":{mask:"h:s",autoUnmask:!1},date:{alias:"dd/mm/yyyy"},"mm/yyyy":{mask:"1/y",placeholder:"mm/yyyy",leapday:"donotuse",separator:"/",alias:"mm/dd/yyyy"}})})(jQuery);
(function(c){c.extend(c.inputmask.defaults.aliases,{decimal:{mask:"~",placeholder:"",repeat:"*",greedy:!1,numericInput:!1,isNumeric:!0,digits:"*",groupSeparator:"",radixPoint:".",groupSize:3,autoGroup:!1,allowPlus:!0,allowMinus:!0,integerDigits:"*",defaultValue:"",prefix:"",suffix:"",getMaskLength:function(a,d,e,f,b){var h=a.length;d||("*"==e?h=f.length+1:1<e&&(h+=a.length*(e-1)));a=c.inputmask.escapeRegex.call(this,b.groupSeparator);b=c.inputmask.escapeRegex.call(this,b.radixPoint);f=f.join("");
b=f.replace(RegExp(a,"g"),"").replace(RegExp(b),"");return h+(f.length-b.length)},postFormat:function(a,d,e,f){if(""==f.groupSeparator)return d;var b=a.slice();c.inArray(f.radixPoint,a);e||b.splice(d,0,"?");b=b.join("");if(f.autoGroup||e&&-1!=b.indexOf(f.groupSeparator)){for(var h=c.inputmask.escapeRegex.call(this,f.groupSeparator),b=b.replace(RegExp(h,"g"),""),h=b.split(f.radixPoint),b=h[0],k=RegExp("([-+]?[\\d?]+)([\\d?]{"+f.groupSize+"})");k.test(b);)b=b.replace(k,"$1"+f.groupSeparator+"$2"),b=
b.replace(f.groupSeparator+f.groupSeparator,f.groupSeparator);1<h.length&&(b+=f.radixPoint+h[1])}a.length=b.length;f=0;for(h=b.length;f<h;f++)a[f]=b.charAt(f);b=c.inArray("?",a);e||a.splice(b,1);return e?d:b},regex:{number:function(a){var d=c.inputmask.escapeRegex.call(this,a.groupSeparator),e=c.inputmask.escapeRegex.call(this,a.radixPoint),f=isNaN(a.digits)?a.digits:"{0,"+a.digits+"}";return RegExp("^"+("["+(a.allowPlus?"+":"")+(a.allowMinus?"-":"")+"]?")+"(\\d+|\\d{1,"+a.groupSize+"}(("+d+"\\d{"+
a.groupSize+"})?)+)("+e+"\\d"+f+")?$")}},onKeyDown:function(a,d,e){var f=c(this);if(a.keyCode==e.keyCode.TAB){if(a=c.inArray(e.radixPoint,d),-1!=a){for(var b=f.data("_inputmask").masksets,f=f.data("_inputmask").activeMasksetIndex,h=1;h<=e.digits&&h<e.getMaskLength(b[f]._buffer,b[f].greedy,b[f].repeat,d,e);h++)if(void 0==d[a+h]||""==d[a+h])d[a+h]="0";this._valueSet(d.join(""))}}else if(a.keyCode==e.keyCode.DELETE||a.keyCode==e.keyCode.BACKSPACE)return e.postFormat(d,0,!0,e),this._valueSet(d.join("")),
!0},definitions:{"~":{validator:function(a,d,e,f,b){if(""==a)return!1;if(!f&&1>=e&&"0"===d[0]&&/[\d-]/.test(a)&&1==d.length)return d[0]="",{pos:0};var h=f?d.slice(0,e):d.slice();h.splice(e,0,a);var h=h.join(""),k=c.inputmask.escapeRegex.call(this,b.groupSeparator),h=h.replace(RegExp(k,"g"),""),k=b.regex.number(b).test(h);if(!k&&(h+="0",k=b.regex.number(b).test(h),!k)){k=h.lastIndexOf(b.groupSeparator);for(i=h.length-k;3>=i;i++)h+="0";k=b.regex.number(b).test(h);if(!k&&!f&&a==b.radixPoint&&(k=b.regex.number(b).test("0"+
h+"0")))return d[e]="0",e++,{pos:e}}return!1==k||f||a==b.radixPoint?k:{pos:b.postFormat(d,e,!1,b)}},cardinality:1,prevalidator:null}},insertMode:!0,autoUnmask:!1},integer:{regex:{number:function(a){var d=c.inputmask.escapeRegex.call(this,a.groupSeparator);return RegExp("^"+(a.allowPlus||a.allowMinus?"["+(a.allowPlus?"+":"")+(a.allowMinus?"-":"")+"]?":"")+"(\\d+|\\d{1,"+a.groupSize+"}(("+d+"\\d{"+a.groupSize+"})?)+)$")}},alias:"decimal"}})})(jQuery);
(function(c){c.extend(c.inputmask.defaults.aliases,{Regex:{mask:"r",greedy:!1,repeat:"*",regex:null,regexTokens:null,tokenizer:/\[\^?]?(?:[^\\\]]+|\\[\S\s]?)*]?|\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9][0-9]*|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)|\((?:\?[:=!]?)?|(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??|[^.?*+^${[()|\\]+|./g,quantifierFilter:/[0-9]+[^,]/,definitions:{r:{validator:function(a,c,e,f,b){function h(){this.matches=[];this.isLiteral=this.isQuantifier=this.isGroup=!1}function k(){var a=
new h,c,d=[];for(b.regexTokens=[];c=b.tokenizer.exec(b.regex);)switch(c=c[0],c.charAt(0)){case "[":case "\\":0<d.length?d[d.length-1].matches.push(c):a.matches.push(c);break;case "(":!a.isGroup&&0<a.matches.length&&b.regexTokens.push(a);a=new h;a.isGroup=!0;d.push(a);break;case ")":c=d.pop();0<d.length?d[d.length-1].matches.push(c):(b.regexTokens.push(c),a=new h);break;case "{":var e=new h;e.isQuantifier=!0;e.matches.push(c);0<d.length?d[d.length-1].matches.push(e):a.matches.push(e);break;default:e=
new h,e.isLiteral=!0,e.matches.push(c),0<d.length?d[d.length-1].matches.push(e):a.matches.push(e)}0<a.matches.length&&b.regexTokens.push(a)}function y(a,c){var d=!1;c&&(n+="(",g++);for(var e=0;e<a.matches.length;e++){var f=a.matches[e];if(!0==f.isGroup)d=y(f,!0);else if(!0==f.isQuantifier){for(var f=f.matches[0],h=b.quantifierFilter.exec(f)[0].replace("}",""),h=n+"{1,"+h+"}",k=0;k<g;k++)h+=")";d=RegExp("^("+h+")$");d=d.test(w);n+=f}else if(!0==f.isLiteral){for(var f=f.matches[0],h=n,R="",k=0;k<g;k++)R+=
")";for(k=0;k<f.length&&!(h=(h+f[k]).replace(/\|$/,""),d=RegExp("^("+h+R+")$"),d=d.test(w));k++);n+=f}else{n+=f;h=n.replace(/\|$/,"");for(k=0;k<g;k++)h+=")";d=RegExp("^("+h+")$");d=d.test(w)}if(d)break}c&&(n+=")",g--);return d}null==b.regexTokens&&k();f=c.slice();var n="";c=!1;var g=0;f.splice(e,0,a);var w=f.join("");for(a=0;a<b.regexTokens.length&&!(h=b.regexTokens[a],c=y(h,h.isGroup));a++);return c},cardinality:1}}}})})(jQuery);
(function(c){c.extend(c.inputmask.defaults.aliases,{phone:{url:"phone-codes/phone-codes.json",mask:function(a){a.definitions={p:{validator:function(){return!1},cardinality:1},"#":{validator:"[0-9]",cardinality:1}};var d=[];c.ajax({url:a.url,async:!1,dataType:"json",success:function(a){d=a}});d.splice(0,0,"+p(ppp)ppp-pppp");return d}}})})(jQuery);
;
/*! howler.js v2.2.3 | (c) 2013-2020, James Simpson of GoldFire Studios | MIT License | howlerjs.com */
!function(){"use strict";var e=function(){this.init()};e.prototype={init:function(){var e=this||n;return e._counter=1e3,e._html5AudioPool=[],e.html5PoolSize=10,e._codecs={},e._howls=[],e._muted=!1,e._volume=1,e._canPlayEvent="canplaythrough",e._navigator="undefined"!=typeof window&&window.navigator?window.navigator:null,e.masterGain=null,e.noAudio=!1,e.usingWebAudio=!0,e.autoSuspend=!0,e.ctx=null,e.autoUnlock=!0,e._setup(),e},volume:function(e){var o=this||n;if(e=parseFloat(e),o.ctx||_(),void 0!==e&&e>=0&&e<=1){if(o._volume=e,o._muted)return o;o.usingWebAudio&&o.masterGain.gain.setValueAtTime(e,n.ctx.currentTime);for(var t=0;t<o._howls.length;t++)if(!o._howls[t]._webAudio)for(var r=o._howls[t]._getSoundIds(),a=0;a<r.length;a++){var u=o._howls[t]._soundById(r[a]);u&&u._node&&(u._node.volume=u._volume*e)}return o}return o._volume},mute:function(e){var o=this||n;o.ctx||_(),o._muted=e,o.usingWebAudio&&o.masterGain.gain.setValueAtTime(e?0:o._volume,n.ctx.currentTime);for(var t=0;t<o._howls.length;t++)if(!o._howls[t]._webAudio)for(var r=o._howls[t]._getSoundIds(),a=0;a<r.length;a++){var u=o._howls[t]._soundById(r[a]);u&&u._node&&(u._node.muted=!!e||u._muted)}return o},stop:function(){for(var e=this||n,o=0;o<e._howls.length;o++)e._howls[o].stop();return e},unload:function(){for(var e=this||n,o=e._howls.length-1;o>=0;o--)e._howls[o].unload();return e.usingWebAudio&&e.ctx&&void 0!==e.ctx.close&&(e.ctx.close(),e.ctx=null,_()),e},codecs:function(e){return(this||n)._codecs[e.replace(/^x-/,"")]},_setup:function(){var e=this||n;if(e.state=e.ctx?e.ctx.state||"suspended":"suspended",e._autoSuspend(),!e.usingWebAudio)if("undefined"!=typeof Audio)try{var o=new Audio;void 0===o.oncanplaythrough&&(e._canPlayEvent="canplay")}catch(n){e.noAudio=!0}else e.noAudio=!0;try{var o=new Audio;o.muted&&(e.noAudio=!0)}catch(e){}return e.noAudio||e._setupCodecs(),e},_setupCodecs:function(){var e=this||n,o=null;try{o="undefined"!=typeof Audio?new Audio:null}catch(n){return e}if(!o||"function"!=typeof o.canPlayType)return e;var t=o.canPlayType("audio/mpeg;").replace(/^no$/,""),r=e._navigator?e._navigator.userAgent:"",a=r.match(/OPR\/([0-6].)/g),u=a&&parseInt(a[0].split("/")[1],10)<33,d=-1!==r.indexOf("Safari")&&-1===r.indexOf("Chrome"),i=r.match(/Version\/(.*?) /),_=d&&i&&parseInt(i[1],10)<15;return e._codecs={mp3:!(u||!t&&!o.canPlayType("audio/mp3;").replace(/^no$/,"")),mpeg:!!t,opus:!!o.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,""),ogg:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),oga:!!o.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),wav:!!(o.canPlayType('audio/wav; codecs="1"')||o.canPlayType("audio/wav")).replace(/^no$/,""),aac:!!o.canPlayType("audio/aac;").replace(/^no$/,""),caf:!!o.canPlayType("audio/x-caf;").replace(/^no$/,""),m4a:!!(o.canPlayType("audio/x-m4a;")||o.canPlayType("audio/m4a;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),m4b:!!(o.canPlayType("audio/x-m4b;")||o.canPlayType("audio/m4b;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),mp4:!!(o.canPlayType("audio/x-mp4;")||o.canPlayType("audio/mp4;")||o.canPlayType("audio/aac;")).replace(/^no$/,""),weba:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),webm:!(_||!o.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,"")),dolby:!!o.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/,""),flac:!!(o.canPlayType("audio/x-flac;")||o.canPlayType("audio/flac;")).replace(/^no$/,"")},e},_unlockAudio:function(){var e=this||n;if(!e._audioUnlocked&&e.ctx){e._audioUnlocked=!1,e.autoUnlock=!1,e._mobileUnloaded||44100===e.ctx.sampleRate||(e._mobileUnloaded=!0,e.unload()),e._scratchBuffer=e.ctx.createBuffer(1,1,22050);var o=function(n){for(;e._html5AudioPool.length<e.html5PoolSize;)try{var t=new Audio;t._unlocked=!0,e._releaseHtml5Audio(t)}catch(n){e.noAudio=!0;break}for(var r=0;r<e._howls.length;r++)if(!e._howls[r]._webAudio)for(var a=e._howls[r]._getSoundIds(),u=0;u<a.length;u++){var d=e._howls[r]._soundById(a[u]);d&&d._node&&!d._node._unlocked&&(d._node._unlocked=!0,d._node.load())}e._autoResume();var i=e.ctx.createBufferSource();i.buffer=e._scratchBuffer,i.connect(e.ctx.destination),void 0===i.start?i.noteOn(0):i.start(0),"function"==typeof e.ctx.resume&&e.ctx.resume(),i.onended=function(){i.disconnect(0),e._audioUnlocked=!0,document.removeEventListener("touchstart",o,!0),document.removeEventListener("touchend",o,!0),document.removeEventListener("click",o,!0),document.removeEventListener("keydown",o,!0);for(var n=0;n<e._howls.length;n++)e._howls[n]._emit("unlock")}};return document.addEventListener("touchstart",o,!0),document.addEventListener("touchend",o,!0),document.addEventListener("click",o,!0),document.addEventListener("keydown",o,!0),e}},_obtainHtml5Audio:function(){var e=this||n;if(e._html5AudioPool.length)return e._html5AudioPool.pop();var o=(new Audio).play();return o&&"undefined"!=typeof Promise&&(o instanceof Promise||"function"==typeof o.then)&&o.catch(function(){console.warn("HTML5 Audio pool exhausted, returning potentially locked audio object.")}),new Audio},_releaseHtml5Audio:function(e){var o=this||n;return e._unlocked&&o._html5AudioPool.push(e),o},_autoSuspend:function(){var e=this;if(e.autoSuspend&&e.ctx&&void 0!==e.ctx.suspend&&n.usingWebAudio){for(var o=0;o<e._howls.length;o++)if(e._howls[o]._webAudio)for(var t=0;t<e._howls[o]._sounds.length;t++)if(!e._howls[o]._sounds[t]._paused)return e;return e._suspendTimer&&clearTimeout(e._suspendTimer),e._suspendTimer=setTimeout(function(){if(e.autoSuspend){e._suspendTimer=null,e.state="suspending";var n=function(){e.state="suspended",e._resumeAfterSuspend&&(delete e._resumeAfterSuspend,e._autoResume())};e.ctx.suspend().then(n,n)}},3e4),e}},_autoResume:function(){var e=this;if(e.ctx&&void 0!==e.ctx.resume&&n.usingWebAudio)return"running"===e.state&&"interrupted"!==e.ctx.state&&e._suspendTimer?(clearTimeout(e._suspendTimer),e._suspendTimer=null):"suspended"===e.state||"running"===e.state&&"interrupted"===e.ctx.state?(e.ctx.resume().then(function(){e.state="running";for(var n=0;n<e._howls.length;n++)e._howls[n]._emit("resume")}),e._suspendTimer&&(clearTimeout(e._suspendTimer),e._suspendTimer=null)):"suspending"===e.state&&(e._resumeAfterSuspend=!0),e}};var n=new e,o=function(e){var n=this;if(!e.src||0===e.src.length)return void console.error("An array of source files must be passed with any new Howl.");n.init(e)};o.prototype={init:function(e){var o=this;return n.ctx||_(),o._autoplay=e.autoplay||!1,o._format="string"!=typeof e.format?e.format:[e.format],o._html5=e.html5||!1,o._muted=e.mute||!1,o._loop=e.loop||!1,o._pool=e.pool||5,o._preload="boolean"!=typeof e.preload&&"metadata"!==e.preload||e.preload,o._rate=e.rate||1,o._sprite=e.sprite||{},o._src="string"!=typeof e.src?e.src:[e.src],o._volume=void 0!==e.volume?e.volume:1,o._xhr={method:e.xhr&&e.xhr.method?e.xhr.method:"GET",headers:e.xhr&&e.xhr.headers?e.xhr.headers:null,withCredentials:!(!e.xhr||!e.xhr.withCredentials)&&e.xhr.withCredentials},o._duration=0,o._state="unloaded",o._sounds=[],o._endTimers={},o._queue=[],o._playLock=!1,o._onend=e.onend?[{fn:e.onend}]:[],o._onfade=e.onfade?[{fn:e.onfade}]:[],o._onload=e.onload?[{fn:e.onload}]:[],o._onloaderror=e.onloaderror?[{fn:e.onloaderror}]:[],o._onplayerror=e.onplayerror?[{fn:e.onplayerror}]:[],o._onpause=e.onpause?[{fn:e.onpause}]:[],o._onplay=e.onplay?[{fn:e.onplay}]:[],o._onstop=e.onstop?[{fn:e.onstop}]:[],o._onmute=e.onmute?[{fn:e.onmute}]:[],o._onvolume=e.onvolume?[{fn:e.onvolume}]:[],o._onrate=e.onrate?[{fn:e.onrate}]:[],o._onseek=e.onseek?[{fn:e.onseek}]:[],o._onunlock=e.onunlock?[{fn:e.onunlock}]:[],o._onresume=[],o._webAudio=n.usingWebAudio&&!o._html5,void 0!==n.ctx&&n.ctx&&n.autoUnlock&&n._unlockAudio(),n._howls.push(o),o._autoplay&&o._queue.push({event:"play",action:function(){o.play()}}),o._preload&&"none"!==o._preload&&o.load(),o},load:function(){var e=this,o=null;if(n.noAudio)return void e._emit("loaderror",null,"No audio support.");"string"==typeof e._src&&(e._src=[e._src]);for(var r=0;r<e._src.length;r++){var u,d;if(e._format&&e._format[r])u=e._format[r];else{if("string"!=typeof(d=e._src[r])){e._emit("loaderror",null,"Non-string found in selected audio sources - ignoring.");continue}u=/^data:audio\/([^;,]+);/i.exec(d),u||(u=/\.([^.]+)$/.exec(d.split("?",1)[0])),u&&(u=u[1].toLowerCase())}if(u||console.warn('No file extension was found. Consider using the "format" property or specify an extension.'),u&&n.codecs(u)){o=e._src[r];break}}return o?(e._src=o,e._state="loading","https:"===window.location.protocol&&"http:"===o.slice(0,5)&&(e._html5=!0,e._webAudio=!1),new t(e),e._webAudio&&a(e),e):void e._emit("loaderror",null,"No codec support for selected audio sources.")},play:function(e,o){var t=this,r=null;if("number"==typeof e)r=e,e=null;else{if("string"==typeof e&&"loaded"===t._state&&!t._sprite[e])return null;if(void 0===e&&(e="__default",!t._playLock)){for(var a=0,u=0;u<t._sounds.length;u++)t._sounds[u]._paused&&!t._sounds[u]._ended&&(a++,r=t._sounds[u]._id);1===a?e=null:r=null}}var d=r?t._soundById(r):t._inactiveSound();if(!d)return null;if(r&&!e&&(e=d._sprite||"__default"),"loaded"!==t._state){d._sprite=e,d._ended=!1;var i=d._id;return t._queue.push({event:"play",action:function(){t.play(i)}}),i}if(r&&!d._paused)return o||t._loadQueue("play"),d._id;t._webAudio&&n._autoResume();var _=Math.max(0,d._seek>0?d._seek:t._sprite[e][0]/1e3),s=Math.max(0,(t._sprite[e][0]+t._sprite[e][1])/1e3-_),l=1e3*s/Math.abs(d._rate),c=t._sprite[e][0]/1e3,f=(t._sprite[e][0]+t._sprite[e][1])/1e3;d._sprite=e,d._ended=!1;var p=function(){d._paused=!1,d._seek=_,d._start=c,d._stop=f,d._loop=!(!d._loop&&!t._sprite[e][2])};if(_>=f)return void t._ended(d);var m=d._node;if(t._webAudio){var v=function(){t._playLock=!1,p(),t._refreshBuffer(d);var e=d._muted||t._muted?0:d._volume;m.gain.setValueAtTime(e,n.ctx.currentTime),d._playStart=n.ctx.currentTime,void 0===m.bufferSource.start?d._loop?m.bufferSource.noteGrainOn(0,_,86400):m.bufferSource.noteGrainOn(0,_,s):d._loop?m.bufferSource.start(0,_,86400):m.bufferSource.start(0,_,s),l!==1/0&&(t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l)),o||setTimeout(function(){t._emit("play",d._id),t._loadQueue()},0)};"running"===n.state&&"interrupted"!==n.ctx.state?v():(t._playLock=!0,t.once("resume",v),t._clearTimer(d._id))}else{var h=function(){m.currentTime=_,m.muted=d._muted||t._muted||n._muted||m.muted,m.volume=d._volume*n.volume(),m.playbackRate=d._rate;try{var r=m.play();if(r&&"undefined"!=typeof Promise&&(r instanceof Promise||"function"==typeof r.then)?(t._playLock=!0,p(),r.then(function(){t._playLock=!1,m._unlocked=!0,o?t._loadQueue():t._emit("play",d._id)}).catch(function(){t._playLock=!1,t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction."),d._ended=!0,d._paused=!0})):o||(t._playLock=!1,p(),t._emit("play",d._id)),m.playbackRate=d._rate,m.paused)return void t._emit("playerror",d._id,"Playback was unable to start. This is most commonly an issue on mobile devices and Chrome where playback was not within a user interaction.");"__default"!==e||d._loop?t._endTimers[d._id]=setTimeout(t._ended.bind(t,d),l):(t._endTimers[d._id]=function(){t._ended(d),m.removeEventListener("ended",t._endTimers[d._id],!1)},m.addEventListener("ended",t._endTimers[d._id],!1))}catch(e){t._emit("playerror",d._id,e)}};"data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"===m.src&&(m.src=t._src,m.load());var y=window&&window.ejecta||!m.readyState&&n._navigator.isCocoonJS;if(m.readyState>=3||y)h();else{t._playLock=!0,t._state="loading";var g=function(){t._state="loaded",h(),m.removeEventListener(n._canPlayEvent,g,!1)};m.addEventListener(n._canPlayEvent,g,!1),t._clearTimer(d._id)}}return d._id},pause:function(e){var n=this;if("loaded"!==n._state||n._playLock)return n._queue.push({event:"pause",action:function(){n.pause(e)}}),n;for(var o=n._getSoundIds(e),t=0;t<o.length;t++){n._clearTimer(o[t]);var r=n._soundById(o[t]);if(r&&!r._paused&&(r._seek=n.seek(o[t]),r._rateSeek=0,r._paused=!0,n._stopFade(o[t]),r._node))if(n._webAudio){if(!r._node.bufferSource)continue;void 0===r._node.bufferSource.stop?r._node.bufferSource.noteOff(0):r._node.bufferSource.stop(0),n._cleanBuffer(r._node)}else isNaN(r._node.duration)&&r._node.duration!==1/0||r._node.pause();arguments[1]||n._emit("pause",r?r._id:null)}return n},stop:function(e,n){var o=this;if("loaded"!==o._state||o._playLock)return o._queue.push({event:"stop",action:function(){o.stop(e)}}),o;for(var t=o._getSoundIds(e),r=0;r<t.length;r++){o._clearTimer(t[r]);var a=o._soundById(t[r]);a&&(a._seek=a._start||0,a._rateSeek=0,a._paused=!0,a._ended=!0,o._stopFade(t[r]),a._node&&(o._webAudio?a._node.bufferSource&&(void 0===a._node.bufferSource.stop?a._node.bufferSource.noteOff(0):a._node.bufferSource.stop(0),o._cleanBuffer(a._node)):isNaN(a._node.duration)&&a._node.duration!==1/0||(a._node.currentTime=a._start||0,a._node.pause(),a._node.duration===1/0&&o._clearSound(a._node))),n||o._emit("stop",a._id))}return o},mute:function(e,o){var t=this;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"mute",action:function(){t.mute(e,o)}}),t;if(void 0===o){if("boolean"!=typeof e)return t._muted;t._muted=e}for(var r=t._getSoundIds(o),a=0;a<r.length;a++){var u=t._soundById(r[a]);u&&(u._muted=e,u._interval&&t._stopFade(u._id),t._webAudio&&u._node?u._node.gain.setValueAtTime(e?0:u._volume,n.ctx.currentTime):u._node&&(u._node.muted=!!n._muted||e),t._emit("mute",u._id))}return t},volume:function(){var e,o,t=this,r=arguments;if(0===r.length)return t._volume;if(1===r.length||2===r.length&&void 0===r[1]){t._getSoundIds().indexOf(r[0])>=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else r.length>=2&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var a;if(!(void 0!==e&&e>=0&&e<=1))return a=o?t._soundById(o):t._sounds[0],a?a._volume:0;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"volume",action:function(){t.volume.apply(t,r)}}),t;void 0===o&&(t._volume=e),o=t._getSoundIds(o);for(var u=0;u<o.length;u++)(a=t._soundById(o[u]))&&(a._volume=e,r[2]||t._stopFade(o[u]),t._webAudio&&a._node&&!a._muted?a._node.gain.setValueAtTime(e,n.ctx.currentTime):a._node&&!a._muted&&(a._node.volume=e*n.volume()),t._emit("volume",a._id));return t},fade:function(e,o,t,r){var a=this;if("loaded"!==a._state||a._playLock)return a._queue.push({event:"fade",action:function(){a.fade(e,o,t,r)}}),a;e=Math.min(Math.max(0,parseFloat(e)),1),o=Math.min(Math.max(0,parseFloat(o)),1),t=parseFloat(t),a.volume(e,r);for(var u=a._getSoundIds(r),d=0;d<u.length;d++){var i=a._soundById(u[d]);if(i){if(r||a._stopFade(u[d]),a._webAudio&&!i._muted){var _=n.ctx.currentTime,s=_+t/1e3;i._volume=e,i._node.gain.setValueAtTime(e,_),i._node.gain.linearRampToValueAtTime(o,s)}a._startFadeInterval(i,e,o,t,u[d],void 0===r)}}return a},_startFadeInterval:function(e,n,o,t,r,a){var u=this,d=n,i=o-n,_=Math.abs(i/.01),s=Math.max(4,_>0?t/_:t),l=Date.now();e._fadeTo=o,e._interval=setInterval(function(){var r=(Date.now()-l)/t;l=Date.now(),d+=i*r,d=Math.round(100*d)/100,d=i<0?Math.max(o,d):Math.min(o,d),u._webAudio?e._volume=d:u.volume(d,e._id,!0),a&&(u._volume=d),(o<n&&d<=o||o>n&&d>=o)&&(clearInterval(e._interval),e._interval=null,e._fadeTo=null,u.volume(o,e._id),u._emit("fade",e._id))},s)},_stopFade:function(e){var o=this,t=o._soundById(e);return t&&t._interval&&(o._webAudio&&t._node.gain.cancelScheduledValues(n.ctx.currentTime),clearInterval(t._interval),t._interval=null,o.volume(t._fadeTo,e),t._fadeTo=null,o._emit("fade",e)),o},loop:function(){var e,n,o,t=this,r=arguments;if(0===r.length)return t._loop;if(1===r.length){if("boolean"!=typeof r[0])return!!(o=t._soundById(parseInt(r[0],10)))&&o._loop;e=r[0],t._loop=e}else 2===r.length&&(e=r[0],n=parseInt(r[1],10));for(var a=t._getSoundIds(n),u=0;u<a.length;u++)(o=t._soundById(a[u]))&&(o._loop=e,t._webAudio&&o._node&&o._node.bufferSource&&(o._node.bufferSource.loop=e,e&&(o._node.bufferSource.loopStart=o._start||0,o._node.bufferSource.loopEnd=o._stop,t.playing(a[u])&&(t.pause(a[u],!0),t.play(a[u],!0)))));return t},rate:function(){var e,o,t=this,r=arguments;if(0===r.length)o=t._sounds[0]._id;else if(1===r.length){var a=t._getSoundIds(),u=a.indexOf(r[0]);u>=0?o=parseInt(r[0],10):e=parseFloat(r[0])}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));var d;if("number"!=typeof e)return d=t._soundById(o),d?d._rate:t._rate;if("loaded"!==t._state||t._playLock)return t._queue.push({event:"rate",action:function(){t.rate.apply(t,r)}}),t;void 0===o&&(t._rate=e),o=t._getSoundIds(o);for(var i=0;i<o.length;i++)if(d=t._soundById(o[i])){t.playing(o[i])&&(d._rateSeek=t.seek(o[i]),d._playStart=t._webAudio?n.ctx.currentTime:d._playStart),d._rate=e,t._webAudio&&d._node&&d._node.bufferSource?d._node.bufferSource.playbackRate.setValueAtTime(e,n.ctx.currentTime):d._node&&(d._node.playbackRate=e);var _=t.seek(o[i]),s=(t._sprite[d._sprite][0]+t._sprite[d._sprite][1])/1e3-_,l=1e3*s/Math.abs(d._rate);!t._endTimers[o[i]]&&d._paused||(t._clearTimer(o[i]),t._endTimers[o[i]]=setTimeout(t._ended.bind(t,d),l)),t._emit("rate",d._id)}return t},seek:function(){var e,o,t=this,r=arguments;if(0===r.length)t._sounds.length&&(o=t._sounds[0]._id);else if(1===r.length){var a=t._getSoundIds(),u=a.indexOf(r[0]);u>=0?o=parseInt(r[0],10):t._sounds.length&&(o=t._sounds[0]._id,e=parseFloat(r[0]))}else 2===r.length&&(e=parseFloat(r[0]),o=parseInt(r[1],10));if(void 0===o)return 0;if("number"==typeof e&&("loaded"!==t._state||t._playLock))return t._queue.push({event:"seek",action:function(){t.seek.apply(t,r)}}),t;var d=t._soundById(o);if(d){if(!("number"==typeof e&&e>=0)){if(t._webAudio){var i=t.playing(o)?n.ctx.currentTime-d._playStart:0,_=d._rateSeek?d._rateSeek-d._seek:0;return d._seek+(_+i*Math.abs(d._rate))}return d._node.currentTime}var s=t.playing(o);s&&t.pause(o,!0),d._seek=e,d._ended=!1,t._clearTimer(o),t._webAudio||!d._node||isNaN(d._node.duration)||(d._node.currentTime=e);var l=function(){s&&t.play(o,!0),t._emit("seek",o)};if(s&&!t._webAudio){var c=function(){t._playLock?setTimeout(c,0):l()};setTimeout(c,0)}else l()}return t},playing:function(e){var n=this;if("number"==typeof e){var o=n._soundById(e);return!!o&&!o._paused}for(var t=0;t<n._sounds.length;t++)if(!n._sounds[t]._paused)return!0;return!1},duration:function(e){var n=this,o=n._duration,t=n._soundById(e);return t&&(o=n._sprite[t._sprite][1]/1e3),o},state:function(){return this._state},unload:function(){for(var e=this,o=e._sounds,t=0;t<o.length;t++)o[t]._paused||e.stop(o[t]._id),e._webAudio||(e._clearSound(o[t]._node),o[t]._node.removeEventListener("error",o[t]._errorFn,!1),o[t]._node.removeEventListener(n._canPlayEvent,o[t]._loadFn,!1),o[t]._node.removeEventListener("ended",o[t]._endFn,!1),n._releaseHtml5Audio(o[t]._node)),delete o[t]._node,e._clearTimer(o[t]._id);var a=n._howls.indexOf(e);a>=0&&n._howls.splice(a,1);var u=!0;for(t=0;t<n._howls.length;t++)if(n._howls[t]._src===e._src||e._src.indexOf(n._howls[t]._src)>=0){u=!1;break}return r&&u&&delete r[e._src],n.noAudio=!1,e._state="unloaded",e._sounds=[],e=null,null},on:function(e,n,o,t){var r=this,a=r["_on"+e];return"function"==typeof n&&a.push(t?{id:o,fn:n,once:t}:{id:o,fn:n}),r},off:function(e,n,o){var t=this,r=t["_on"+e],a=0;if("number"==typeof n&&(o=n,n=null),n||o)for(a=0;a<r.length;a++){var u=o===r[a].id;if(n===r[a].fn&&u||!n&&u){r.splice(a,1);break}}else if(e)t["_on"+e]=[];else{var d=Object.keys(t);for(a=0;a<d.length;a++)0===d[a].indexOf("_on")&&Array.isArray(t[d[a]])&&(t[d[a]]=[])}return t},once:function(e,n,o){var t=this;return t.on(e,n,o,1),t},_emit:function(e,n,o){for(var t=this,r=t["_on"+e],a=r.length-1;a>=0;a--)r[a].id&&r[a].id!==n&&"load"!==e||(setTimeout(function(e){e.call(this,n,o)}.bind(t,r[a].fn),0),r[a].once&&t.off(e,r[a].fn,r[a].id));return t._loadQueue(e),t},_loadQueue:function(e){var n=this;if(n._queue.length>0){var o=n._queue[0];o.event===e&&(n._queue.shift(),n._loadQueue()),e||o.action()}return n},_ended:function(e){var o=this,t=e._sprite;if(!o._webAudio&&e._node&&!e._node.paused&&!e._node.ended&&e._node.currentTime<e._stop)return setTimeout(o._ended.bind(o,e),100),o;var r=!(!e._loop&&!o._sprite[t][2]);if(o._emit("end",e._id),!o._webAudio&&r&&o.stop(e._id,!0).play(e._id),o._webAudio&&r){o._emit("play",e._id),e._seek=e._start||0,e._rateSeek=0,e._playStart=n.ctx.currentTime;var a=1e3*(e._stop-e._start)/Math.abs(e._rate);o._endTimers[e._id]=setTimeout(o._ended.bind(o,e),a)}return o._webAudio&&!r&&(e._paused=!0,e._ended=!0,e._seek=e._start||0,e._rateSeek=0,o._clearTimer(e._id),o._cleanBuffer(e._node),n._autoSuspend()),o._webAudio||r||o.stop(e._id,!0),o},_clearTimer:function(e){var n=this;if(n._endTimers[e]){if("function"!=typeof n._endTimers[e])clearTimeout(n._endTimers[e]);else{var o=n._soundById(e);o&&o._node&&o._node.removeEventListener("ended",n._endTimers[e],!1)}delete n._endTimers[e]}return n},_soundById:function(e){for(var n=this,o=0;o<n._sounds.length;o++)if(e===n._sounds[o]._id)return n._sounds[o];return null},_inactiveSound:function(){var e=this;e._drain();for(var n=0;n<e._sounds.length;n++)if(e._sounds[n]._ended)return e._sounds[n].reset();return new t(e)},_drain:function(){var e=this,n=e._pool,o=0,t=0;if(!(e._sounds.length<n)){for(t=0;t<e._sounds.length;t++)e._sounds[t]._ended&&o++;for(t=e._sounds.length-1;t>=0;t--){if(o<=n)return;e._sounds[t]._ended&&(e._webAudio&&e._sounds[t]._node&&e._sounds[t]._node.disconnect(0),e._sounds.splice(t,1),o--)}}},_getSoundIds:function(e){var n=this;if(void 0===e){for(var o=[],t=0;t<n._sounds.length;t++)o.push(n._sounds[t]._id);return o}return[e]},_refreshBuffer:function(e){var o=this;return e._node.bufferSource=n.ctx.createBufferSource(),e._node.bufferSource.buffer=r[o._src],e._panner?e._node.bufferSource.connect(e._panner):e._node.bufferSource.connect(e._node),e._node.bufferSource.loop=e._loop,e._loop&&(e._node.bufferSource.loopStart=e._start||0,e._node.bufferSource.loopEnd=e._stop||0),e._node.bufferSource.playbackRate.setValueAtTime(e._rate,n.ctx.currentTime),o},_cleanBuffer:function(e){var o=this,t=n._navigator&&n._navigator.vendor.indexOf("Apple")>=0;if(n._scratchBuffer&&e.bufferSource&&(e.bufferSource.onended=null,e.bufferSource.disconnect(0),t))try{e.bufferSource.buffer=n._scratchBuffer}catch(e){}return e.bufferSource=null,o},_clearSound:function(e){/MSIE |Trident\//.test(n._navigator&&n._navigator.userAgent)||(e.src="data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA")}};var t=function(e){this._parent=e,this.init()};t.prototype={init:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,o._sounds.push(e),e.create(),e},create:function(){var e=this,o=e._parent,t=n._muted||e._muted||e._parent._muted?0:e._volume;return o._webAudio?(e._node=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),e._node.gain.setValueAtTime(t,n.ctx.currentTime),e._node.paused=!0,e._node.connect(n.masterGain)):n.noAudio||(e._node=n._obtainHtml5Audio(),e._errorFn=e._errorListener.bind(e),e._node.addEventListener("error",e._errorFn,!1),e._loadFn=e._loadListener.bind(e),e._node.addEventListener(n._canPlayEvent,e._loadFn,!1),e._endFn=e._endListener.bind(e),e._node.addEventListener("ended",e._endFn,!1),e._node.src=o._src,e._node.preload=!0===o._preload?"auto":o._preload,e._node.volume=t*n.volume(),e._node.load()),e},reset:function(){var e=this,o=e._parent;return e._muted=o._muted,e._loop=o._loop,e._volume=o._volume,e._rate=o._rate,e._seek=0,e._rateSeek=0,e._paused=!0,e._ended=!0,e._sprite="__default",e._id=++n._counter,e},_errorListener:function(){var e=this;e._parent._emit("loaderror",e._id,e._node.error?e._node.error.code:0),e._node.removeEventListener("error",e._errorFn,!1)},_loadListener:function(){var e=this,o=e._parent;o._duration=Math.ceil(10*e._node.duration)/10,0===Object.keys(o._sprite).length&&(o._sprite={__default:[0,1e3*o._duration]}),"loaded"!==o._state&&(o._state="loaded",o._emit("load"),o._loadQueue()),e._node.removeEventListener(n._canPlayEvent,e._loadFn,!1)},_endListener:function(){var e=this,n=e._parent;n._duration===1/0&&(n._duration=Math.ceil(10*e._node.duration)/10,n._sprite.__default[1]===1/0&&(n._sprite.__default[1]=1e3*n._duration),n._ended(e)),e._node.removeEventListener("ended",e._endFn,!1)}};var r={},a=function(e){var n=e._src;if(r[n])return e._duration=r[n].duration,void i(e);if(/^data:[^;]+;base64,/.test(n)){for(var o=atob(n.split(",")[1]),t=new Uint8Array(o.length),a=0;a<o.length;++a)t[a]=o.charCodeAt(a);d(t.buffer,e)}else{var _=new XMLHttpRequest;_.open(e._xhr.method,n,!0),_.withCredentials=e._xhr.withCredentials,_.responseType="arraybuffer",e._xhr.headers&&Object.keys(e._xhr.headers).forEach(function(n){_.setRequestHeader(n,e._xhr.headers[n])}),_.onload=function(){var n=(_.status+"")[0];if("0"!==n&&"2"!==n&&"3"!==n)return void e._emit("loaderror",null,"Failed loading audio file with status: "+_.status+".");d(_.response,e)},_.onerror=function(){e._webAudio&&(e._html5=!0,e._webAudio=!1,e._sounds=[],delete r[n],e.load())},u(_)}},u=function(e){try{e.send()}catch(n){e.onerror()}},d=function(e,o){var t=function(){o._emit("loaderror",null,"Decoding audio data failed.")},a=function(e){e&&o._sounds.length>0?(r[o._src]=e,i(o,e)):t()};"undefined"!=typeof Promise&&1===n.ctx.decodeAudioData.length?n.ctx.decodeAudioData(e).then(a).catch(t):n.ctx.decodeAudioData(e,a,t)},i=function(e,n){n&&!e._duration&&(e._duration=n.duration),0===Object.keys(e._sprite).length&&(e._sprite={__default:[0,1e3*e._duration]}),"loaded"!==e._state&&(e._state="loaded",e._emit("load"),e._loadQueue())},_=function(){if(n.usingWebAudio){try{"undefined"!=typeof AudioContext?n.ctx=new AudioContext:"undefined"!=typeof webkitAudioContext?n.ctx=new webkitAudioContext:n.usingWebAudio=!1}catch(e){n.usingWebAudio=!1}n.ctx||(n.usingWebAudio=!1);var e=/iP(hone|od|ad)/.test(n._navigator&&n._navigator.platform),o=n._navigator&&n._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/),t=o?parseInt(o[1],10):null;if(e&&t&&t<9){var r=/safari/.test(n._navigator&&n._navigator.userAgent.toLowerCase());n._navigator&&!r&&(n.usingWebAudio=!1)}n.usingWebAudio&&(n.masterGain=void 0===n.ctx.createGain?n.ctx.createGainNode():n.ctx.createGain(),n.masterGain.gain.setValueAtTime(n._muted?0:n._volume,n.ctx.currentTime),n.masterGain.connect(n.ctx.destination)),n._setup()}};"function"==typeof define&&define.amd&&define([],function(){return{Howler:n,Howl:o}}),"undefined"!=typeof exports&&(exports.Howler=n,exports.Howl=o),"undefined"!=typeof global?(global.HowlerGlobal=e,global.Howler=n,global.Howl=o,global.Sound=t):"undefined"!=typeof window&&(window.HowlerGlobal=e,window.Howler=n,window.Howl=o,window.Sound=t)}();;
/*
 * jQuery.upload v1.0.2
 *
 * Copyright (c) 2010 lagos
 * Dual licensed under the MIT and GPL licenses.
 *
 * http://lagoscript.org
 */
(function(b){function m(e){return b.map(n(e),function(d){return'<input type="hidden" name="'+d.name+'" value="'+d.value+'"/>'}).join("")}function n(e){function d(c,f){a.push({name:c,value:f})}if(b.isArray(e))return e;var a=[];if(typeof e==="object")b.each(e,function(c){b.isArray(this)?b.each(this,function(){d(c,this)}):d(c,b.isFunction(this)?this():this)});else typeof e==="string"&&b.each(e.split("&"),function(){var c=b.map(this.split("="),function(f){return decodeURIComponent(f.replace(/\+/g," "))});
d(c[0],c[1])});return a}function o(e,d){var a;a=b(e).contents().get(0);if(b.isXMLDoc(a)||a.XMLDocument)return a.XMLDocument||a;a=b(a).find("body").html();switch(d){case "xml":a=a;if(window.DOMParser)a=(new DOMParser).parseFromString(a,"application/xml");else{var c=new ActiveXObject("Microsoft.XMLDOM");c.async=false;c.loadXML(a);a=c}break;case "json":a=window.eval("("+a+")");break}return a}var p=0;b.fn.upload=function(e,d,a,c){var f=this,g,j,h;h="jquery_upload"+ ++p;var k=b('<iframe name="'+h+'" style="position:absolute;top:-9999px" />').appendTo("body"),
i='<form target="'+h+'" method="post" enctype="multipart/form-data" />';if(b.isFunction(d)){c=a;a=d;d={}}j=b("input:checkbox",this);h=b("input:checked",this);i=f.wrapAll(i).parent("form").attr("action",e);j.removeAttr("checked");h.attr("checked",true);g=(g=m(d))?b(g).appendTo(i):null;i.submit(function(){k.load(function(){var l=o(this,c),q=b("input:checked",f);i.after(f).remove();j.removeAttr("checked");q.attr("checked",true);g&&g.remove();setTimeout(function(){k.remove();c==="script"&&b.globalEval(l);
a&&a.call(f,l)},0)})}).submit();return this}})(jQuery);
;
/**
* maxChar jQuery plugin
* @author Mitch Wilson
* @version 0.3.0
* @requires jQuery 1.3.2 (only version tested)
* @see http://mitchwilson.net/2009/08/03/new-jquery-plugin-maxchar/
* @param {Boolean}	debug				Specify whether to send message updates to the Firebug console. Default is false.
* @param {String}	indicator 			Specify alternate indicator element by id. Default is indicator created dynamically.
* @param {String}	label				Specify a default label displayed when input element is not in focus. Default is blank.
* @param {String}	pluralMessage 		Set the plural form of the remaining count message. Default is ' remaining'.
* @param {Number}	rate 				Set the update rate in milliseconds. Default is 200.
* @param {String}	singularMessage 	Set the singular form of the remaining count message. Default is ' remaining'.
* @param {String}	spaceBeforeMessage 	Set spacing in front of (to the left of) the indicator message. Default is ' '.
* @param {Boolean}	truncate			Truncate submitted value down to limit on form submit. Default is true.
* @description Enforces max character limit on any input or textarea HTML element and provides user feedback and many options. 
* New features added in 0.3.0 are: 
* 1) Feature change: Displays negative characters when past limit rather than truncating characters in form input.
* 2) New option: truncate - If true, on form submit truncates submitted value down specified by limit. Does not change (respects) user text in form field. Default is true.
* 3) Bug fixes: Fixed serveral issues related to removing over-the-limit characters in the form field.
*/

(function ($) {
    var methods = {
        init: function (limit, options) {
            // Start the timer that updates indicator.
            function start() {
                data.timer = setInterval(function () { update(data.limit) }, settings.rate);
            }

            // Stop the timer that updates the indicator.
            function stop() {
                if (data.timer != null) {
                    clearInterval(data.timer);
                    data.timer = null;
                }
            }

            // Update the indicator.
            function update(limit) {
                if ((limit == null) || (limit == 0)) {
                    currentMessage = '';
                } else {
                    var remaining = limit - target.val().length;
                    // Update remaining count and message.
                    if (remaining == 1) {
                        currentMessage = remaining + settings.singularMessage;
                    } else {
                        currentMessage = remaining + settings.pluralMessage;
                    }
                }
                // Update indicator.
                data.indicator.text(currentMessage);
                log(currentMessage);
            }

            function handleFocus() {
                if (data.timer == null) {
                    if (settings.label) {
                        data.indicator.fadeOut(function () { data.indicator.text('') }).fadeIn(function () { start() });
                    } else {
                        start();
                    }
                }
            }

            function handleBlur() {
                // Stop timer that updates count and the indicator message.
                stop();
                // Update view.
                if (settings.label) {
                    data.indicator.fadeOut(function () { data.indicator.text(settings.label) }).fadeIn();
                }
            }

            function truncateFormSubmit() {
                if ((data.limit != null) && (data.limit > 0)) {
                    target.val(target.val().slice(0, data.limit));
                }
            }

            function getIndicator(id) {
                // Get indicator element in the dom.
                var indicator = jQuery('#' + id);
                // If indicator element does not already exist in the dom, create it.
                if (indicator.length == 0) {
                    target.after(spaceBeforeMessage + '<span id="' + id + '"></span>');
                    indicator = jQuery('#' + id)
                }
                // Return reference to indicator element.
                return indicator;
            }

            // Create helper functions.
            function log(message) {
                // Display 
                if (settings.debug) {
                    try {
                        if (console) {
                            console.log(message);
                        }
                    } catch (e) {
                        // Do nothing on error.
                    }
                }
            }

            // Define default settings and override w/ options.	
            settings = jQuery.extend({
                debug: false,
                indicator: 'indicator',
                label: '',
                pluralMessage: ' remaining',
                rate: 200,
                singularMessage: ' remaining',
                spaceBeforeMessage: ' ',
                truncate: true
            }, options);

            // Get maxChar target element.

            var target = jQuery(this); // Must get target first, since it is used in setting other local variables.

            var data = {
                settings: settings,
                currentMessage: '',
                indicator: getIndicator(settings.indicator),
                limit: limit,
                remaining: limit,
                timer: null
            }
            target.data('maxChar', data);

            if (settings.label) {
                data.indicator.text(settings.label);
            } else {
                update(data.limit);
            }

            // When user focuses on the target element, do the following.
            target.bind('focus.maxchar', handleFocus);

            // When user removes focus from the target element, do the following.
            target.bind('blur.maxchar', handleBlur);

            // Truncate submitted value down to limit on form submit.
            if (settings.truncate) {
                var form_id = '#' + jQuery(this).closest("form").attr("id");
                jQuery(form_id).bind('submit.maxchar', truncateFormSubmit);
            }
        },
        destroy: function () {
            // unbind all events
            jQuery(this).unbind('.maxchar');
            var data = jQuery(this).data('maxChar');
            if (data != null) {
                clearInterval(data.timer);
                data.indicator.text('');
            }
        }
    };

    $.extend($.fn, {
        maxChar: function (method) {
            // Method calling logic
            if (methods[method]) {
                return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
            } else if (typeof method === 'object' || !method) {
                return methods.init.apply(this, arguments);
            } else {
                $.error('Method ' + method + ' does not exist on jQuery.maxChar');
            }
        }
    });

})(jQuery);;
/*
 * MapToolbar
 * a literal object
 *  - act as a container that will share one or more Feature instance
 *  - act as a namespace
 */

var MapToolbar = {
    numFences: 0,
    updateWhileDragging: true,

    // reorder index of a poly markers array
    reindex: function (markers) {
    	for (var i = 0 ; i < markers.length; i++) {
    		markers[i].index = i;
    	}
    },

    // get point at middle distance between 2 point
    getMidPoint: function(){
	    var lat = (arguments[0].lat + arguments[1].lat) / 2;
	    var lng = (arguments[0].lng + arguments[1].lng) / 2;
	    return L.latLng(lat, lng);
    },

    //current edited feature
    currentFeature: null,

    // add a point to a polygon
    addPoint: function (location, poly, index, isExisting) {
        // determine type of polygon adding?
	    // e can be a click event or a latLng object
    	var path = poly.getLatLngs();
    	var points = poly.points;
    	if (path.length > 2) {
    		path.splice(path.length - 1, 1); // remove last element temporarily (bing will re-add it)
    	}
    	var markers = (poly.markers) ? poly.markers : [];
    	var index = (typeof index != "undefined") ? index : points.length;
    	var image = L.icon({
    		iconUrl: '/content/images/marker-edition.png',
    		iconSize: [9, 9],
			iconAnchor: [5, 5]
    	});
    	var imageover = L.icon({
    		iconUrl: '/content/images/marker-edition-over.png',
    		iconSize: [9, 9],
    		iconAnchor: [5, 5]
    	});
    	var marker = L.marker(location, {
    		draggable: true,
			icon: image
    	});
		tracking.map.addLayer(marker);

		if (MapToolbar.isSelected(MapToolbar.buttons.$shape)) {
            marker.gps_type = 'polygon';
		} else if (MapToolbar.isSelected(MapToolbar.buttons.$line)) {
            marker.gps_type = 'line';
		} else if (MapToolbar.isSelected(MapToolbar.buttons.$circle)) {
			marker.gps_type = 'circle';
		}
        marker.index = index;

        if (!isExisting) {
        	path.splice(index, 0, location); // add location to polygon/polyline path
        	points.splice(index, 0, location);
        }

        markers.splice(index, 0, marker); // add markers to array
	    //path.insertAt(index, e); // add point to polygon/line path
	    //markers.insertAt(index, marker);
	    
	    if (arguments[2] != null) {
		    MapToolbar.reindex(markers);
	    }

        if (MapToolbar.isSelected(MapToolbar.buttons.$circle)) {
            if (index == 1 ) {
                // complete the circle
                var p1 = poly.markers[0];
                var p2 = poly.markers[1];
                // calculate radius
                var center = L.latLng(p1.getLatLng().lat, p1.getLatLng().lng);
                var rad = L.latLng(p2.getLatLng().lat, p2.getLatLng().lng);

            	//poly.setMap(null);
                console.log(poly);
                circle = MapToolbar.createCirclePaths(center, rad, tracking.state.bounds, poly);
                poly.circle = circle;
                //path = polyPaths;
                if (!tracking.map.hasLayer(poly.circle)) {
                	tracking.map.addLayer(poly.circle);
                }
                //poly.setMap(map);

                // done with circle
                //MapToolbar.currentFeature = null;
                //MapToolbar.select('hand_b');
                MapToolbar.select(null);
            }
        } else {
        	if ((poly.getLatLngs().length > 0) && (!tracking.map.hasLayer(poly))) {
        		tracking.map.addLayer(poly);
        	}
        	poly.setLatLngs(poly.points.slice(0));
        }
        marker.on('click', function (e) {
        	console.log('clicko');
        	var marker = e.target;
            if ((marker.gps_type != null) && (marker.gps_type == 'circle')) {
                // you can't remove a circle's points
                return;
            }
            if ((marker.gps_type != null) && (marker.gps_type == 'polygon')) {
                // a polygon must have at least 3 points
                //console.log(markers.getLength());
                if (markers.length <= 3) {
                    return;
                }
            }
            if ((marker.gps_type != null) && (marker.gps_type == 'line')) {
                // a line must have a least two points
                if (markers.length <= 2) {
                    return;
                }
            }

            // when this polygon's point is clicked, remove it
            // TODO: only remove it if the polygon has been completed, otherwise mark polygon as complete
            tracking.map.removeLayer(marker);
            markers.splice(marker.index, 1);
	    	//markers.removeAt(marker.index);
            path.splice(marker.index, 1);
            poly.points.splice(marker.index, 1);
		    //path.removeAt(marker.index);
		    MapToolbar.reindex(markers);
		    if(markers.length == 0){
			    MapToolbar.removeFeature(poly.id);
		    } else {
		    	//poly.setLatLngs(path);
		    	poly.setLatLngs(poly.points.slice(0));
		    }
	    });

        marker.on('drag', function (e) {
        	console.log('drago');
	    	var marker = e.target;
	    	if (!MapToolbar.updateWhileDragging)
	    		return;
	    	path[marker.index] = marker.getLatLng(); // reposition the point
	    	poly.points[marker.index] = marker.getLatLng();
	    	var position = marker.getLatLng(), p;
	    	// if the type is a circle, don't be adding points, simply recalculate the polygon circle via the distance between the two points
	    	if ((marker.gps_type != null) && (marker.gps_type == 'circle') && (poly.points.length >= 2)) {
	    		// set center and radius
	    		poly.circle.setLatLng(poly.points[0]);
	    		poly.circle.setRadius(poly.points[0].distanceTo(poly.points[1]));
	    		tracking.state.bounds.extend(poly.circle.getBounds());
	    		//polyPaths = MapToolbar.createCirclePaths(poly.points[0], poly.points[1], tracking.state.bounds);
	    		//poly.setLatLngs(polyPaths);
	    		return;
	    	}
	    	poly.setLatLngs(poly.points.slice(0));
	    });
        marker.on('dragend', function (e) {
        	console.log('dragend');
        	console.log(e);
        	var marker = e.target;
        	marker.isDragging = false;
            // path is an array of points
            // when this point has been dragged and dropped
	    	path[marker.index] = marker.getLatLng(); // reposition the point
	    	var position = marker.getLatLng(), p;
	    	poly.points[marker.index] = position;
            // if the type is a circle, don't be adding points, simply recalculate the polygon circle via the distance between the two points
	    	if ((marker.gps_type != null) && (marker.gps_type == 'circle')) {
	    		// set center and radius
	    		poly.circle.setLatLng(poly.points[0]);
	    		poly.circle.setRadius(poly.points[0].distanceTo(poly.points[1]));
	    		tracking.state.bounds.extend(poly.circle.getBounds());
            	//polyPaths = MapToolbar.createCirclePaths(poly.points[0], poly.points[1], tracking.state.bounds);
            	//poly.setLatLngs(polyPaths);
                return;
            }

		    // get previous point
            var prevIndex = marker.index-1;
            if ((marker.gps_type != null) && (marker.gps_type != 'line')) {
                prevIndex = prevIndex >= 0 ? prevIndex : (poly.points.length-1); // loop over to length if editing a polygon
            }
            if (marker.index != prevIndex) {
    		    if(typeof poly.points[prevIndex] != "undefined") {
                    // if there's a previous point, add a midpoint between previous point and newly dragged location
    			    var m1 = poly.points[prevIndex];
				    p = MapToolbar.getMidPoint(position, m1); // get the midpoint of the previous point
				    MapToolbar.addPoint(p, poly, marker.index);	// add a point to the polygon at the midpoint
    		    }
            }

		    // get next point
            var nextIndex = marker.index+1;
            if ((marker.gps_type != null) && (marker.gps_type != 'line')) {
                nextIndex = nextIndex > (poly.points.length-1) ? 0 : nextIndex; // loop over to start if editing a polygon
            }
            if (nextIndex != marker.index)  {
    		    if(typeof poly.points[nextIndex] != "undefined") {
                    // if there's a next point, add a midpoint between next point and newly dragged location
    			    var m2 = poly.points[nextIndex];
				    p = MapToolbar.getMidPoint(position, m2);
				    MapToolbar.addPoint(p, poly, marker.index+1);
    		    }
            }
	    });

        marker.on('dragstart', function (e) {
        	marker.isDragging = true;
        });
        marker.on('mouseover', function (e) {
        	if (!e.target.isDragging) {
        		e.target.setIcon(imageover);
        	}
		});
		marker.on('mouseout', function (e) {
			if (!e.target.isDragging) {
				e.target.setIcon(image);
			}
	    });	    
    },

    clearEmptyCurrentFeature: function() {
        // check if current feature edit is empty to auto-remove
        if (MapToolbar.currentFeature != null) {
            var curr = MapToolbar.currentFeature;
            if (curr.markers != null) {
                if (curr.markers.length == 0) {
                    --MapToolbar[curr.fence_type+'Counter']; // reset feature counter
                    MapToolbar.removeFeature(curr.id);
                }
            }
        }
    },

    // append a DOM node to $featureTable
    addFeatureEntry: function(name, color, id) {
        $('#geofence-none').hide();

        // called when a polyline, polygon, or placemark is added to the map
        // adds a row to the feature table describing the feature added
        MapToolbar.numFences++; //<input type="checkbox" id="fence' + MapToolbar.numFences + '" checked="checked" />
        var li = $('<li><span class="highlight-segment" data-for="fence' + MapToolbar.numFences + '" data-segment="' + name + '" data-segmentid="' + id + '"><span class="color" style="background-color: ' + color + '"></span> ' + name + '</span>');
        li.append('<div class="edit"><a href="#" class="remove-feature" data-segment="' + name + '" data-segment-id="' + id + '">' + tracking.strings.DELETE + '</a></div></li>');
        $('ul#add-geofence-sections').append(li);

    	return {li: li, desc: null, color: null};
    },

    // edition buttons
    buttons: {
    	//$hand: null,
    	$shape: null,
    	$line: null,
    	//$placemark: null,
        $circle: null
    },

    // click event for line and shape and circle edition
    polyClickEvent: null,

    // click event for line and shape and circle edition
    //clickEventUpdate: null,

    // an array of predefined colors
    colors:[["red", "#fc6355"], ["turquoise", "#55d7d7"], ["green","#00e13c"], ["pink", "#e14f9e"], ["blue", "#5781fc"], ["purple", "#7e55fc"], ["yellow", "#fcf357"]],

    colorIndex: 0,

    //contains list of overlay that were added to the map
    //and that are displayed on the sidebar
    $featureTable: document.getElementById("featuretbody"),

    Feature: function(type, id, points){
    	if(type == "shape" || type == "line"){
    	    this['poly'](type, id, points);
    	} else {
    	    this[type](id, points);
    	}
    },

    //contains reference for all features added on the map
    features:{
    	placemarkTab: {},
    	lineTab: {'count': 0},
    	shapeTab: {'count': 0},
        circleTab: {'count': 0},
    	overlayTab:{}
    },

    getColor: function(named) {
  		return this.colors[(this.colorIndex++) % this.colors.length][named ? 0 : 1];
	},

    getIcon: function(color) {
		//var icon = new google.maps.MarkerImage("http://google.com/mapfiles/ms/micons/" + color + ".png",
    	var icon = '/content/images/marker-' + color + '.png';
		return icon;
    },

    //instanciate a new Feature instance and create a reference
    initFeature: function(type, id, points){
		new MapToolbar.Feature(type, id, points);
    },

	//check if a toolbar button is selected
	isSelected: function(el){
       if ( el == null)
            return false;
       return $(el).hasClass('selected');
	   //return (el.className == "selected");
	},

    //the map DOM node container
    //map: "map",

    placemarkCounter: 0,
    lineCounter:0,
    shapeCounter:0,
    circleCounter:0,

    //remove click events used for poly edition/update
    removeClickEvent: function(){
    },

    removeAllFeatures: function() {
        for(var name in MapToolbar.features) {
            var tab = MapToolbar.features[name];
            if (tab == null)
                continue;

            for(var featureName in tab) {
                var feature = tab[featureName];
                if (feature == null)
                    continue;
                if (feature.fence_type == null)
                    continue;
                feature.markers.forEach(function (marker, index) {
                	marker.remove();
                });
                if (feature.circle != null) {
                	feature.circle.remove();
                }
                feature.$el.li.remove();
                feature.remove();
            }

            delete MapToolbar.features[name];
        }
        MapToolbar.select(null);
        MapToolbar.currentFeature = null;
        MapToolbar.numFences = 0;
        MapToolbar.features = {
    	    placemarkTab: {},
    	    lineTab: {'count': 0},
    	    shapeTab: {'count': 0},
            circleTab: {'count': 0},
    	    overlayTab:{}
        }
        MapToolbar.placemarkCounter = 0;
        MapToolbar.lineCounter = 0;
        MapToolbar.shapeCounter = 0;
        MapToolbar.circleCounter = 0;
        $('#geofence-none').show();
    },

    // remove feature from map
    removeFeature : function(id){
	    var type  = id.split(' #')[0].toLowerCase();
	    var feature = MapToolbar.features[type+'Tab'][id];

        if (feature != null) {
            feature.$el.li.remove();
        }
        //remove the li
	    //virer l'element DOM
	    //feature.$el.row.parentNode.removeChild(feature.$el.row);
	    //virer la rÃ©fÃ©rence dans MapToolbar.features
        delete MapToolbar.features[type + 'Tab'][id];
        if (feature != null) {
            switch (type) {
                case "placemark":
                    feature.remove();
                    break;
                default:
                    for (var i = 0; i < feature.markers.length; i++) {
                        var marker = feature.markers[i];
                        marker.remove();
                    }
                    if (feature.circle != null) {
                        if (tracking.map.hasLayer(feature.circle)) {
                            tracking.map.removeLayer(feature.circle);
                        }
                    }
                    feature.remove();
                    break;
            }
        }
        MapToolbar.features[type +'Tab'].count -= 1;
        // have all sections been removed?
        if ((MapToolbar.features['circleTab'].count == 0) && (MapToolbar.features['lineTab'].count == 0)&&(MapToolbar.features['shapeTab'].count == 0)) {
            $('#geofence-none').show();
        }

	    //MapToolbar.select('hand_b');
        MapToolbar.select(null);
    },
    //toolbar buttons selection
    select: function (buttonId){
	    //MapToolbar.currentFeature = null;
	   // MapToolbar.buttons.$hand.className="unselected";
	    //MapToolbar.buttons.$shape.className="unselected";
        $(MapToolbar.buttons.$shape).removeClass('selected').addClass('unselected');//className="unselected";
        $(MapToolbar.buttons.$circle).removeClass('selected').addClass('unselected');//.className="unselected";
	    $(MapToolbar.buttons.$line).removeClass('selected').addClass('unselected');//.className="unselected";
	    //MapToolbar.buttons.$placemark.className="unselected";
        if (buttonId == null)
            return;
        $('.tool-' + buttonId).removeClass('unselected').addClass('selected');
	    //document.getElementById(buttonId).className="selected";
    },
    getSegmentInfo: function (featureName) {
        var type = featureName.split(' #')[0].toLowerCase();
        if (type == 'shape' || type == 'line' || type == 'circle') {
            var poly = MapToolbar.features[type + 'Tab'][featureName];
            if (type == 'circle') {
            	console.log(poly.markers);
                var p1 = poly.markers[0];
                var p2 = poly.markers[1];
                return {
                    Shape: type,
                    Center: { Lat: p1.getLatLng().lat.toFixed(6), Lng: p1.getLatLng().lng.toFixed(6) },
                    Radius: { Lat: p2.getLatLng().lat.toFixed(6), Lng: p2.getLatLng().lng.toFixed(6) },
                    Color: poly.strokeColor,
                    Name: featureName
                };
            } else {
                var points = MapToolbar.getSegmentPoints(featureName);
                var segmentPoints = [];
                console.log(points);
                for (var i = 0; i < points.length; i++) {
                	var elem = points[i];
                	console.log(elem);
                	segmentPoints.push({ Lat: elem.lat.toFixed(6), Lng: elem.lng.toFixed(6) });
                }
                return {
                    Shape: type,
                    Points: segmentPoints,
                    Color: poly.strokeColor,
                    Name: featureName
                };
            }

        }
    },
    getSegmentPoints: function(featureName) {
        var type = featureName.split(' #')[0].toLowerCase();
    	if(type == 'shape' || type=='line' || type=='circle') {
            var feat = MapToolbar.features[type + 'Tab'][featureName];
    		// get bounds for feature
            console.log(feat);
            var points = feat.getLatLngs()[0];
            return points;
        }
        return null;
    },
    setMapCenter: function(featureName){
    	var type = featureName.split(' #')[0].toLowerCase();
    	if(type == 'shape' || type=='line' || type=='circle'){
    		MapToolbar.currentFeature = MapToolbar.features[type + 'Tab'][featureName];
    		var point = MapToolbar.currentFeature.getLatLngs()[0];
            // get bounds for feature
    		var points = MapToolbar.currentFeature.getLatLngs();
			// todo: needed?
            //tempBounds = new google.maps.LatLngBounds();
            //if (points.getLength() > 0) {
            //    points.forEach(function(elem, index) {
            //        tempBounds.extend(elem);
            //    });
            //    if (tempBounds.isEmpty()) {
            //        // set the map center to be global center
            //        tracking.map.setCenter(new google.maps.LatLng(0, 0));
            //        tracking.map.setZoom(2);
            //    } else {
            //        tracking.map.fitBounds(tempBounds);
            //    }
            //}

    	}else if(type == 'placemark'){
    		MapToolbar.currentFeature = null;
 		    var point = MapToolbar.features[type + 'Tab'][featureName].getLatLng();
    	}
    	//MapToolbar.select(type + '_b');
        //MapToolbar.select(type);
	    //map.setCenter(point);
    },
    //select hand button
    stopEditing: function() {
      this.removeClickEvent();
      //this.select("hand_b");
      this.select(null);
    },
    //change marker icon
    updateMarker: function(marker, cells, color) {
      if (color) {
      	marker.setOptions({ icon: MapToolbar.getIcon(color) });
      }
      var latlng = marker.getLatLng();
      cells.desc.innerHTML = "(" + Math.round(latlng.b * 100) / 100 + ", " + Math.round(latlng.c * 100) / 100 + ")";
    },

    createCirclePaths: function (center, radius, bounds, poly) {
    	var radiusMeters = center.distanceTo(radius);
    	var circle = L.circle(center, {
    		radius: radiusMeters,
    		color: poly.options.color,
    		fillColor: poly.options.fillColor,
    		opacity: poly.options.opacity,
    		fillOpacity: poly.options.fillOpacity,
    		weight: poly.options.weight
    	}).addTo(tracking.map);
    	// adding to map is required to get the bounds
    	tracking.state.bounds.extend(circle.getBounds());
    	//var circle = Microsoft.Maps.SpatialMath.getRegularPolygon(center, Microsoft.Maps.SpatialMath.getDistanceTo(center, radius, Microsoft.Maps.SpatialMath.DistanceUnits.Meters), 45, Microsoft.Maps.SpatialMath.DistanceUnits.Meters);
    	//tracking.state.bounds = Microsoft.Maps.SpatialMath.Geometry.bounds(tracking.state.bounds, Microsoft.Maps.LocationRect.fromLocations(circle));
    	return circle;
    },

    distance: function(lat1,lon1,lat2,lon2) {
    	var R = 6371; // km (change this constant to get miles)
    	var dLat = (lat2-lat1) * Math.PI / 180;
    	var dLon = (lon2-lon1) * Math.PI / 180;
    	var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
    		Math.cos(lat1 * Math.PI / 180 ) * Math.cos(lat2 * Math.PI / 180 ) *
    		Math.sin(dLon/2) * Math.sin(dLon/2);
    	var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    	var d = R * c;
//        	if (d>1) return Math.round(d)+"km";
//        	else if (d<=1) return Math.round(d*1000)+"m";
    	//return Math.round(d*1000);
        return d;
    }
}

MapToolbar.Feature.prototype.circle = function(segmentId, points) {
	// create a new circle
	if (points == null) {
		points = [];
	}
	console.log('create circle with segment Id ' + segmentId);
	console.log(points);
    var color = MapToolbar.getColor(false),
			path = points.slice(0),
			poly,
			self = this,
			el = "circle";

	poly = self.createShape( {strokeWeight: 3, fillColor: color, strokeColor: color }, path );

	poly.markers = [];
	poly.points = points;
    poly.fence_type = 'circle';
	if(MapToolbar.isSelected(document.getElementById(el))) return;

    MapToolbar.clearEmptyCurrentFeature();

	MapToolbar.select(el);
	MapToolbar.currentFeature = poly;
	
	if(!poly.$el){
		++MapToolbar['circleCounter'];
        poly.id = 'Circle #'+ MapToolbar['circleCounter'];
		poly.$el = MapToolbar.addFeatureEntry(poly.id, color, segmentId);  	// add DOM node in sidebar
		MapToolbar.features['circleTab'][poly.id] = poly;		 	// add a reference
        MapToolbar.features['circleTab'].count += 1;
	}
	
	if (points.length == 2) {
		MapToolbar.addPoint(points[0], poly, 0, true);
		MapToolbar.addPoint(points[1], poly, 1, true);
	}
	if ((poly.getLatLngs().length > 0) && (!tracking.map.hasLayer(poly))) {
		//tracking.map.addLayer(poly);
	}
};

MapToolbar.Feature.prototype.poly = function (type, segmentId, points) {
	// create a new polygon (line or polygon)
	if (points == null) {
		points = [];
	}
    console.log('create poly of type ' + type + ' with segment Id ' + segmentId);
	var color = MapToolbar.getColor(false),
			path = points,
			poly,
			self = this,
			el = type;

	if (type == "shape") {
		poly = self.createShape({ strokeWeight: 3, fillColor: color, strokeColor: color }, path);
	} else if ((type == "line") || (type == 'circle')) {
		poly = self.createLine({ strokeWeight: 3, strokeColor: color }, path);
	}
    var poly_type = 'Line';
    if (type == 'shape') {
    	poly_type = 'Shape';
    }

    poly.markers = []; // todo: populate markers here
    poly.points = points;
    poly.fence_type = poly_type.toLowerCase();
	if(MapToolbar.isSelected(document.getElementById(el))) return;

    MapToolbar.clearEmptyCurrentFeature();

	MapToolbar.select(el);
	MapToolbar.currentFeature = poly;
	
	if(!poly.$el){
		++MapToolbar[type+"Counter"];
		poly.id = poly_type + ' #'+ MapToolbar[type+"Counter"];
		poly.$el = MapToolbar.addFeatureEntry(poly.id, color, segmentId);  	// add DOM node in sidebar
		MapToolbar.features[type+"Tab"][poly.id] = poly;		 			// add a referecence
        MapToolbar.features[type+'Tab'].count += 1;
	}
	for (var i = 0; i < points.length; i++) {
		MapToolbar.addPoint(points[i], poly, i, true);
	}
	if ((poly.getLatLngs().length > 0) && (!tracking.map.hasLayer(poly))) {
		tracking.map.addLayer(poly);
	}
}

MapToolbar.Feature.prototype.placemark = function() {
    // create a placemark listener
	var marker,
	self = this;
  	if(MapToolbar.isSelected(MapToolbar.buttons.$placemark)) return;
  	MapToolbar.select("placemark_b");
	//@TODO
	tracking.map.once('click', function(arg) {
	  //if (arg && arg.latlng) {
	    MapToolbar.select("hand_b");
	    self.createMarker(arg.latlng, true);
	  //}
	});
}

MapToolbar.Feature.prototype.createMarker = function(point) {
    // place a marker at clicked location
	var color = MapToolbar.getColor(true),
		marker = L.marker(point, {
			title: "Hello World!",
			draggable: true,
		});
	    
	tracking.map.addLayer(marker);

	++MapToolbar["placemarkCounter"];
	marker.id = 'placemark_'+ MapToolbar["placemarkCounter"];
	marker.$el = MapToolbar.addFeatureEntry(marker.id, color);	       // add DOM node in sidebar
	MapToolbar.updateMarker(marker, marker.$el, color);        // set placemark color
	MapToolbar.features['placemarkTab'][marker.id] = marker;		 		   // add a referecence
	google.maps.event.addListener(marker, "dragend", function() {
		MapToolbar.updateMarker(marker, marker.$el);
	});
	return marker;
}



MapToolbar.Feature.prototype.createShape = function(opts, path) {
	var poly;
	if (path == undefined) {
		path = [];
	}
	poly = L.polygon(path, {
	    weight: opts.strokeWeight,
	    fillColor: opts.fillColor,
		fillOpacity: 0.15,
		color: opts.strokeColor,
		opacity: 0.35
	});
	return poly;
}

MapToolbar.Feature.prototype.createCircle = function (opts, path) {
	var poly;
	if (path == undefined) {
		path = [];
	}
	poly = L.circle(path[0], {
		weight: opts.strokeWeight,
		fillColor: opts.fillColor,
		fillOpacity: 0.15,
		color: opts.strokeColor,
		opacity: 0.35
	});
	return poly;
}

MapToolbar.Feature.prototype.createLine = function (opts, path) {
	var poly = L.polyline(path, {
		weight: opts.strokeWeight,
	    color: opts.strokeColor
	}), self = this;
    //poly.setPath(new google.maps.MVCArray(path));
	return poly;
}



;
/// <reference path="jquery-3.3.1.min.js" />
// firebug console helper (for IE/non-Firebug)
if (!window.console) console = {};
console.debug = console.debug || function () { };
console.log = console.log || function () { };
console.warn = console.warn || function () { };
console.error = console.error || function () { };
console.info = console.info || function () { };

var { el, mount, svg, setChildren, text, setAttr, setStyle } = redom;

(function (tracking, $j, undefined) {
    /// <param name="$j" type="jQuery" />
    // public variables
    tracking.map = null;
    tracking.domain = '';
    tracking.directionsService = null;
    tracking.overlay = null;
    tracking.keys = {};
    tracking.layers = {
        traffic: null,
        weather: null,
        clouds: null
    };
    tracking.geocoder = null;
    tracking.audio = {
        emergency: null,
        isEmergencyPlaying: false,
        text: null,
        isTextPlaying: false
    };
    tracking.devices = {
        DPLUS: 3, // ems satcom
        SKYWAVE_IDP: [26, 58, 60, 154, 157],
        SKYWAVE_M2M: [39, 308, 309],
        SKYWAVE_IDP_DUAL_MODE: [49, 59, 70, 106, 367],
        SKYWAVE_IDP_CELL_ONLY: [157],
        SKYWAVE_7XX: 59,
        SKYWAVE_782: 106,
        SKYWAVE_ST9100: 367,
        SKYWAVE_782_CELL: 157,
        SKYWAVE_SG7100: 70,
        MOBILE: [18, 45, 56], // iPhone, Android, WP8
        GSATMICRO: [34, 275],
        GSATMICRO_GSM: [118, 276],
        ISATMARKER: 163,
        GSAT_MICROS: [34, 118, 123, 163, 275, 276],
        NAL: [2, 76, 83, 84, 85, 124, 156, 164, 304, 305, 306],
        NAL_SHOUT_TS: 85,
        NAL_SHOUT_NANO: 84,
        NAL_GSM: [76, 164, 306],
        HUGHES: [166, 167, 168, 169, 170],
        QUAKE_AIC: [111],
        QUECLINK: [19, 73, 79, 80, 81, 82, 94, 95, 107, 130, 140, 142, 143],
        FLIGHTCELL: [28, 261, 262],
		QUECLINK_GB100: 143,
        QUECLINK_GL200: 79,
        QUECLINK_GL300: 80,
        QUECLINK_GL300W: 130,
        QUECLINK_GL500: 81,
		QUECLINK_GL520: 142,
        QUECLINK_GV500: 82,
        QUECLINK_GV200: 73,
        QUECLINK_GT300: 95,
        QUECLINK_GT301: 140,
        QUECLINK_GV300: 107,
        QUECLINK_GV600W: 153,
        GOTEK_PRIMELITE: 94,
        SPOT: [35, 151, 152, 159, 171, 172, 173, 174, 175, 176, 347, 351, 353, 354],
        INMARSAT_C: [41, 234, 235, 236],
        GTTS: [51, 100, 101],
        GTTS_2000BI: 51,
        GTTS_2000B: 101,
        GTTS_3000: 100,
        DAN_TRACKER: 125, // LTT10
        IRIDIUM_EDGE: 129,
		IRIDIUM_EXTREME: 16,
        TM3000: 13,
        CALAMP: [37, 232, 231, 230, 229, 228, 227, 226, 225, 224, 223, 222, 221, 220, 219, 218, 217, 216, 215, 214, 213, 212, 211, 210, 209, 208, 207, 206, 205, 204, 203, 202, 201, 200, 199, 198, 197, 196],
        INREACH: [46, 271, 272, 273, 274],
		GEOPRO: 57,
        //GARMIN_FLEET_590: 65,
        LT100: 135,
        LT501: 155,
        GSE_FBB: [9, 66],
        BEAM: 42,
        EDGE_SOLAR: 356,
        BIVY_STICK: 368
    };
    tracking.preferences = { // include defaults for intellisense
        PREFERENCE_SPEED: 0,
        PREFERENCE_LAT_LNG: 1,
        PREFERENCE_FUEL_UNIT: 1,
        PREFERENCE_MOROCCO_OVERLAY: false,
        PREFERENCE_REMOVE_ROADS: false,
        PREFERENCE_GROUP_POSITIONS: true,
        PREFERENCE_ALPHA_POSITIONS: true,
        PREFERENCE_EMERGENCY_AUDIO: false
    };
    tracking.templates = {
        asset: null,
        assetGroup: null,
        geofence: null,
        place: null,
        module: null,
        sharedView: null
    };
    tracking.strings = {
        MSG_MESSAGE_SENDING_ERROR: '',
        MSG_MESSAGE_SENT_SUCCESS: '',
        MSG_MESSAGE_SENT_ERROR: '',
        MSG_ADD_GROUP_ERROR: '',
        MSG_QUERY_GROUPS_ERROR: '',
        MSG_QUERY_USERS_ERROR: '',
        MSG_SINGLE_ASSET_ERROR: '',
        MSG_ASSET_QUERY_ERROR: '',
        MSG_ADD_ASSET_ERROR: '',
        MSG_ADD_ASSET_SUCCESS: '',
        MSG_EDIT_ASSET_SUCCESS: '',
        MSG_EDIT_ASSET_ERROR: '',
        MSG_EDIT_ASSET_FAILURE: '',
        MSG_EDIT_GROUP_SUCCESS: '',
        MSG_EDIT_GROUP_ERROR: '',
        MSG_EDIT_GROUP_FAILURE: '',
        MSG_DELETE_GROUP_ERROR: '',
        MSG_DELETE_ASSET_ERROR: '',
        MSG_ADD_GEOFENCE_NO_SEGMENTS: '',
        MSG_ADD_GEOFENCE_ERROR: '',
        MSG_EDIT_GEOFENCE_ERROR: '',
        MSG_DELETE_GEOFENCE_ERROR: '',
        MSG_INTERVAL_ERROR: '',
        MSG_SET_OUTPUT_PIN_ERROR: '',
        MSG_DPLUS_INTERVAL_ERROR: '',
        MSG_DPLUS_INTERVAL_SUCCESS: '',
        MSG_DPLUS_REPORT_ERROR: '',
        MSG_DPLUS_REPORT_SUCCESS: '',
        MSG_ADD_POSITION_SUCCESS: '',
        MSG_ADD_POSITION_ERROR: '',
        MSG_ADD_FILLUP_SUCCESS: '',
        MSG_ADD_FILLUP_ERROR: '',
        MSG_DELETE_FILLUP_ERROR: '',
        MSG_REFUEL_HISTORY_ERROR: '',
        MSG_LOAD_POSITION_ERROR: '',
        MSG_CONFIRM_NOTIFICATION_ERROR: '',
        MSG_DELETE_PLACE_ERROR: '',
        DELETE: '',
        CANCEL: '',
        FUEL_IMPERIAL: '',
        FUEL_LITRES: '',
        FUEL_US: '',
        ASSET_COLOR: '',
        ASSET_UNIQUEID: '',
        MSG_VALID_IMEI: '',
        MSG_ASSETS_AND_POSITIONS: '',
        MSG_ASSETS_SHOWN_LIVE: '',
        ADD_GEOFENCE: '',
        EDIT_ASSET: '',
        MSG_DELETE_GROUP_CONFIRM: '',
        VEHICLE_FIELDS: '',
        SOFTWARE_VERSION: '',
        VEHICLE_MAKE_AND_MODEL: '',
        VEHICLE_PURCHASE_DATE: '',
        VEHICLE_VIN: '',
        VESSEL_FIELDS: '',
        VESSEL_CLASS: '',
        VESSEL_TONNAGE: '',
        VESSEL_FLAG_REGISTRY: '',
        VESSEL_IMO_NUMBER: '',
        VESSEL_CALL_SIGN: '',
        VESSEL_NAME: '',
        NOTES: '',
        POSITION_INDEX: '',
        POSITION_NEXT: '',
        POSITION_PREV: '',
        PLAYBACK_STOP: '',
        PLAYBACK_PLAY: '',
        EXTRA: '',
        FIRST: '',
        LAST: ''
    };
    tracking.language = 'en';
    tracking.notification = {
        isConnected: false,
        isReconnecting: false,
        reconnectInterval: null,
        reconnectSeconds: 10,
        reconnectLeft: 10
    };
    tracking.NOTIFICATION_TYPES = ['positions', 'events', 'alerts', 'messages', 'status'];
    tracking.NOTIFICATION_PREFIXES = {
        positions: {
            prefix: 'op',
            classes: ['op-10', 'op-20', 'op-30', 'op-40', 'op-50', 'op-60', 'op-70', 'op-80', 'op-90', 'op-100']
        },
        alerts: {
            prefix: 'oa',
            classes: ['oa-10', 'oa-20', 'oa-30', 'oa-40', 'oa-50', 'oa-60', 'oa-70', 'oa-80', 'oa-90', 'oa-100']
        },
            events: {
            prefix: 'oe',
            classes: ['oe-10', 'oe-20', 'oe-30', 'oe-40', 'oe-50', 'oe-60', 'oe-70', 'oe-80', 'oe-90', 'oe-100']
        },
        messages: {
            prefix: 'om',
            classes: ['om-10', 'om-20', 'om-30', 'om-40', 'om-50', 'om-60', 'om-70', 'om-80', 'om-90', 'om-100']
        },
        status: {
            prefix: 'os',
            classes: ['os-10', 'os-20', 'os-30', 'os-40', 'os-50', 'os-60', 'os-70', 'os-80', 'os-90', 'os-100']
        },
    };
    tracking.notificationLists = {
        positions: null,
        events: null,
        chat: null,
        messages: null,
        alerts: null,
        status: null,
        activity: null,
        positionsList: null,
        eventsList: null,
        chatList: null,
        messagesList: null,
        alertsList: null,
        statusList: null,
        activityList: null
    };

    tracking.data = {
        softwarePackages: null,
        wizards: {
            sharedView: null
        },
        search: {
            assets: null,
            places: null,
            fences: null,
            sharedViews: null,
            liveActivity: null,
            historyActivity: null
        },
        pending: {
            live: false,
            events: false
        },
        currentHeight: null,
        previousZoom: null,
        visible: {
            assets: [],
            groups: [],
            places: [],
            fences: [],
            trips: []
        },
        domNodes: {
            assets: {},
            assetNotificationMenu: null,
            tripNotificationMenu: null,
            groups: {},
            groupContents: {},
            groupColors: {},
            assetsLive: {},
            assetsHistory: {},
            places: {},
            journeys: {},
            sharedViews: {},
            trips: {},
            fences: {},
            dialogs: {},
            infoDialogs: {},
            modals: {},
            map: null,
            content: {
                base: null
            },
            mapTools: {
                container: null,
                currentTime: null,
                visibleSummary: null
            },
            mapMode: {
                container: null,
                current: null,
                from: null,
                to: null
            },
            nav: {
                primary: null,
                toggle: null,
                utility: null
            },
            panels: {
                primary: null,
                secondary: null
            },
            simpleBars: {
                primary: null,
                secondary: null
            },
            filter: {
                assetResults: null,
                fenceResults: null,
                placeResults: null,
                journeyResults: null,
                sharedViewResults: null
            },
            mouseTooltip: {
                content: null,
                title: null,
                show: false,
                position: { x: 0, y: 0},
                reference: null
            },
            eventListingById: {},
            positionListingById: {},
            messageListingById: {},
            activityListingById: {},
            sharedView: {
                eventListingById: {},
                positionListingById: {},
                messageListingById: {},
                activityListingById: {}
            }
        },
        assets: [],
        assetsById: {},
        markers: [],
        markersByAssetId: {},
        fenceMarkers: [],
        groups: [],
        groupsById: {},
        devices: [],
        devicesById: {},
        gatewayAccounts: [],
        fences: [],
        fencesById: {},
        places: [],
        placesById: {},
        journeys: [],
        journeysById: {},
        sharedViews: [],
        sharedViewsById: {},
        users: null,
        attributes: [],
        attributeGroups: [],
        assetUsers: [],
        assetForms: [],
        fenceUsers: [],
        assetGroupUsers: [],
        placeUsers: [],
        placeMarkers: [],
        live: {
            isInitialized: false,
            latestPosition: null,
            latestPositions: [],
            latestPositionsByAssetId: {},
            positions: [],
            normalizedPositions: [],
            positionsByAssetId: {},
            normalizedPositionsByAssetId: {},
            events: [],
            normalizedEvents: [],
            eventIds: [],
            normalizedEventIds: {},
            normalizedEventsByAssetId: {},
            markers: [],
            markersByAssetId: {},
            markersByPositionId: {},
            messageCounts: [],
            messageCountsByAssetId: {},
            messages: [],
            normalizedMessages: [],
            messagesByAssetId: {},
            normalizedMessagesByAssetId: {},
            eventFilters: ['all'],
            unreadNotifications: {
                positions: [],
                alerts: [],
                events: [],
                status: [],
                messages: []
            },
            notificationTimesByAssetId: {},
            mapLinesByAssetId: {}
        },
        history: {
            isLimited: false,
            isLoadedLimitedData: false,
            limitedData: null,
            latestPosition: null,
            positions: [],
            normalizedPositions: [],
            positionsByAssetId: {},
            normalizedPositionsByAssetId: {},
            events: [],
            normalizedEvents: [],
            normalizedEventIds: {},
            normalizedEventsByAssetId: {},
            markers: [],
            markersByAssetId: {},
            markersByPositionId: {},
            messageCounts: [],
            messageCountsByAssetId: {},
            assetIdsWithResults: {},
            messages: [],
            normalizedMessages: [],
            messagesByAssetId: {},
            normalizedMessagesByAssetId: {},
            fromDate: null,
            toDate: null,
            eventFilters: ['all'],
            mapLinesByAssetId: {},
            markerClustersByAssetId: {}
        },
        activityById: {},
        activityByAssetId: {},
        positionsById: {},
        positionsByAssetId: {},
        eventsById: {},
        messagesById: {},
        trips: {
            positions: [],
            positionsByTripId: {},
            normalizedPositionsByTripId: {},
            events: [],
            eventsByTripId: {},
            normalizedEvents: [],
            normalizedEventsByTripId: {},
            normalizedEventIds: {},
            markers: [],
            markersByTripId: {},
            markersByPositionId: {}, // multiple trips can include the same position
            messageCounts: [],
            messageCountsByTripId: {},
            messages: [],
            normalizedMessages: [],
            messagesByTripId: {},
            normalizedMessagesByTripId: {},
            tripIdsWithResults: {},
            mapLinesByTripId: {},
            markerClustersByTripId: {}
        },
        sharedView: {
            isLimited: false,
            isLoadedLimitedData: false,
            limitedData: null,
            current: null,
            temp: null,
            events: [],
            positions: [],
            positionsByAssetId: {},
            normalizedPositions: [],
            normalizedPositionsByAssetId: {},
            normalizedEvents: [],
            //normalizedEventsBySharedViewId: {},
            markers: [],
            //markersBySharedViewId: {},
            markersByPositionId: {},
            messageCounts: {
                FromMobile: 0,
                ToMobile: 0,
                FromMobileChats: 0,
                ToMobileChats: 0,
                FromMobileDevice: 0,
                ToMobileDevice: 0
            },
            //messageCountsBySharedViewId: {},
            isMessagesLoaded: false,
            normalizedMessages: [],
            //normalizedMessagesBySharedViewId: {},
            mapLinesByAssetId: {},
            markerClustersByAssetId: {},
            trips: {
                markers: []
            },
            placeMarkers: [],
            fenceMarkers: [],
            priorMapType: null,
            priorIsSatelliteLabelOverlayEnabled: true,
            // separate cached containers from tracking.data on purpose
            positionsById: {},
            eventsById: {},
            messagesById: {},
            activityById: {},
            fromDate: null,
            toDate: null
        },
        sharedViewUndo: null,
        behaviorAnalysis: {
            positions: [],
            events: [],
            markers: []
        },
        followAssets: {
            positions: [],
            events: [],
            markers: []
        },
        snapToRoadsQueue: [],
        tileLoadedQueue: [],
        waypoints: [],
        events: [],
        pendingEvents: [],
        alerts: [],
        eventFilters: ['all'],
        eventTypes: [],
        lastEventId: null,
        lastAlertId: null,
        queryingNotifications: false,
        lastNotificationId: null,
        quickMessages: [],
        messaging: {
            inbox: null,
            outbox: null
        },
        validation: {
            addGroup: null,
            editAsset: null,
            editGroup: null,
            geofence: null,
            //place: null,
            dPlusQuery: null,
            dPlusInterval: null,
            idpAvlGeofence: null,
            idpAvlParameters: null,
            idpCoreParameters: null,
            idpIoParameters: null,
            idpMetersSet: null
        },
        radar: {
            isActive: false,
            timer: null
        },
        radarAustralia: {
            isActive: false,
            timer: null,
            basetime: null,
            issuetime: null
        },
        worldIR: {
            isActive: false,
            timer: null
        },
        maritime: { isActive: false },
        oil: { isActive: false },
        clouds: { isActive: false },
        weather: {
            geoJSON: null,
            gettingData: false,
            layer: null,
            isActive: false,
            request: null
        },
        iridium: { isActive: false },
        iridiumnext: { isActive: false },
        globalstar: { isActive: false },
        inmarsat: { isActive: false },
        orbcomm: { isActive: false },
        geostationary: { isActive: false },
        traffic: { isActive: false },
        activeLayers: [],
        ruler: {
            isOpen: false,
            points: [],
            polygon: null
        },
        routing: {
            isOpen: false,
            start: null,
            end: null,
            waypoints: [],
            isUsingMap: false,
            mapClickDestination: 'waypoint'
        },
        zoom: {
            isActive: false,
            manager: null
        },
        drawing: {
            isActive: false,
            manager: null
        },
        searchPositionResults: null,
        searchPlaceResults: null,
        searchRouteResults: null,
        playback: {
            assetId: null,
            index: 0,
            positions: []
        },
        satelliteMarkers: [],
        satelliteOrbits: [],
        satelliteIndex: [],
        mapLayers: {
            positions: null,
            fences: null,
            places: null,
            waypoints: null,
            other: null,

            normal: L.layerGroup(),
            sharedView: L.layerGroup()

            //normal: {
            //    assets: {
            //        live: L.layerGroup(),
            //        history: L.layerGroup(),
            //        trips: L.layerGroup()
            //    },
            //    fences: L.layerGroup(),
            //    places: L.layerGroup(),
            //    waypoints: L.layerGroup(),
            //    other: L.layerGroup()
            //},
            //sharedView: {
            //    assets: {
            //        history: L.layerGroup(),
            //        trips: L.layerGroup()
            //    },
            //    fences: L.layerGroup(),
            //    places: L.layerGroup(),
            //    other: L.layerGroup()
            //}
        },
        isSatelliteLabelOverlayEnabled: true,
        routeLine: null,
        routeLineEncoded: null,
        routeLinesEncoded: null,
        routePolygon: null,
        systemNotifications: [],
        // TODO optimize these as lookup tables instead of arrays
        MESSAGES_TEXT: [
            0, 10, 11
        ],
        MESSAGE_IDP_COMMAND_RESPONSE: 76,
        MSG_IDP_UPDATER_TERMINAL_INFO_RESPONSE: 165,
        MSG_IDP_UPDATER_STATE_RESPONSE: 166,
        EVENTS_TEXT_MESSAGE: [
            51,  // text message
            52  // text message status
        ],
        EVENTS_EMERGENCY: [
            9,      // emergency on
            124,    // emergency off
            126,    // help
            278,    // help cancel
            29,     // panic button
            291,    // fall down
            271     // man down
        ],
        EVENTS_ALERT: [
            14,     // alert triggered
            16      // alert no longer triggered
        ],
        EVENTS_STATUS: [
            47,     // moving start
            48,     // moving stop
            1,      // start
            2,      // stop
            21,     // ignition on
            22,     // ignition off
            32,     // idling
            98,     // idling stop
            5,      // speeding start
            6,      // speeding stop
            3,      // power on
            4,      // power off
            41,     // dwelling start
            97,     // dwelling stop
            45,     // towing start
            46,     // towing stop
            19,     // on main power
            20,     // on backup power
            35,     // antenna cut start
            129,    // antenna cut stop
            36,     // gps jamming start
            37,     // gps jamming end
            38,     // cell jamming start
            39,     // cell jamming end
            //9,      // emergency on
            //124,    // emergency off
            //126,    // help
            //278,    // cancel help
            //271,    // man down
            //15,     // check in
            //10,     // reset
            133,     // low battery
            305,    // immobilizer enabled
            306     // immobilizer disabled
        ],
        tooltips: [],
        MAP_TYPES: {
            'roadlight': 4,
            'roaddark': 5,
            'road': 0,
            'satellite': 1,
            'satellitealt': 6,
            'openstreetmap': 3,
            'terrain': 2
        }
    };
    tracking.panels = {
        //LIVE: 0,
        //HISTORY: 1,
        ASSETS: 0,
        BEHAVIOR_ANALYSIS: 2,
        GEOFENCES: 3,
        PLACES: 4,
        ADD_ITEM: 5,
        FOLLOW_ASSETS: 6,
        JOURNEYS: 10,
        SHARED_VIEWS: 12
    };
    tracking.mapModes = {
        LIVE: 0,
        HISTORY: 1
    };
    tracking.viewModes = {
        NORMAL: 0,
	    SHARED_VIEW: 1
    };
    tracking.dataGroups = {
        NORMAL_LIVE: 0,
	    NORMAL_HISTORY: 1,
	    JOURNEY_HISTORY: 2,
	    SHARED_VIEW_HISTORY: 3,
        SHARED_VIEW_JOURNEY: 4
    };
    tracking.sortModes = {
        ALPHABETICAL: 0,
        CUSTOM: 1,
        COLOR: 2,
        MANUFACTURER: 3,
        DEVICE_TYPE: 4
    },
    tracking.sortDirections = {
        ASC: 0,
        DESC: 1
    },
    tracking.state = {
        isSafari: !!navigator.userAgent.match(/Version\/[\d\.]+.*Safari/),
        isMobile: /iPhone|iPad|iPod|Android|Blackberry|Fennec|Mobi/i.test(navigator.userAgent),
        liveFollow: {
            isActive: false,
            asset: null,
            assets: [],
            groups: []
        },
        isAddingAsset: false,
        isEditingGeofence: false,
        isEditingAssetGroup: false,
        isInLiveMode: true,
        activeMode: null,
        activeMapMode: null,
        activeViewMode: tracking.viewModes.NORMAL,
        hasQueriedHistory: false,
        hasQueriedLive: false,
        portalLoadedEpoch: null,
        activePanel: tracking.panels.ASSETS,
        mapClickQueue: [],
        mapClickHandlers: {
            PLACE: 0,
            POSITION: 1,
            POSITION_ADD: 2,
            RULER: 3,
            ROUTING: 4,
            GEOFENCE: 5
        },
        isChoosingPosition: false,
        isAddingPosition: false,
        isChoosingPlace: false,
        isFirstLoad: true,
        isInPlaybackMode: false,
        openWindow: null,
        openPanels: {
            primary: true,
            secondary: false,
        },
        selectedMarker: null,
        chosenLocation: null,
        chosenLocations: [],
        isSnapToRoadsQueueRunning: false,
        isSnapToRoadsQueueProcessing: false,
        isPreviewingBuffer: false,
        bounds: null,
        boundsItems: [],
        zoomTimeout: null,
        userLocation: {
            position: null,
            marker: null,
            circle: null,
            watch: null,
            zoomToPosition: false,
            options: {
                enableHighAccuracy: true,
                maximumAge: 60 * 1000 * 5,
                timeout: 30 * 1000
            }
        },
        promises: {
            getGroupsAndAssets: null,
            getAssetUsers: null,
            getLatestPositions: null,
            getNotificationsAndAlerts: null
        },
        currentMapType: null,
        nextFocus: null
    };
    tracking.user = {
        isAdmin: false,
        isAnonymous: false,
        showWalkthrough: false,
        canEditAssets: false,
        canAddAssets: false,
        canDeleteAssets: false,
        canEditAssetGroups: false,
        canEditAlerts: false,
        canEditGeofences: false,
        canEditPlaces: false,
        canSendCommands: false,
        canEditJourneys: false,
        canEditSharedViews: false,
        dateFormat: 'M/D/YYYY h:mm:ss A',
        hiddenPreferences: {
            fences: [],
            groups: [],
            assets: [],
            places: []
        },
        displayPreferences: {
            version: 2,
            hideAllAssets: false, // special snowflake
            expandedGroups: [],
            hiddenFences: [],
            hiddenAssets: [],
            hiddenPlaces: [],
            visibleTrips: [],
            customSort: {},
            sortMode: {
                assets: tracking.sortModes.ALPHABETICAL,
                places: tracking.sortModes.ALPHABETICAL,
                fences: tracking.sortModes.ALPHABETICAL,
                journeys: tracking.sortModes.ALPHABETICAL,
                sharedViews: tracking.sortModes.ALPHABETICAL
            },
            sortDirection: {
                assets: tracking.sortDirections.ASC,
                places: tracking.sortDirections.ASC,
                fences: tracking.sortDirections.ASC,
                journeys: tracking.sortDirections.ASC,
                sharedViews: tracking.sortDirections.ASC
            }
        },
        tickOffset: 0
    };
    tracking.options = {
        isDemo: false,
        useStaticSubdomains: true,
        disablePositionPopup: false,
        disableMapTypeSelection: false,
        disablePanels: false,
        disableLayerSelection: false,
        disableHeader: false,
        isCompact: false,
        showAssetId: '',
        showAssetUniqueId: '',
        defaultMode: '',
        defaultMapType: '',
        defaultZoom: 14,
        maximumZoom: 18,
        minimumZoom: 3,
        maxBounds: L.latLngBounds([[90, -360], [-90, 360]]),
        showPositionId: '',
        showSharedViewId: null,
        maxLiveTrailPositions: 10,
        radarRefreshMinutes: 3,
        worldIRRefreshMinutes: 30,
        showSidePanel: false,
        useFenceDocuments: false,
        placeAssetPhotosInPositionDialog: false,
        assetMenuExclude: '',
        driverFieldsExclude: '',
        hideAlertTriggeredEvents: false,
        hideEmergencyEvents: false,
        keys: {
            weatherUnderground: '',
            thunderForest: '',
            mapQuest: ''
        },
        enableGeocoding: true,
        baseLayers: {
            road: {
                enabled: true,
                url: '',
                minZoom: 1,
                maxZoom: 22
            },
            roadlight: {
                enabled: false,
                url: '',
                minZoom: 1,
                maxZoom: 22
            },
            roaddark: {
                enabled: false,
                url: '',
                minZoom: 1,
                maxZoom: 22
            },
            satellitelabels: {
                enabled: false,
                url: '',
                minZoom: 3,
                maxZoom: 22
            },
            satellitenolabels: {
                enabled: false,
                url: '',
                minZoom: 3,
                maxZoom: 22
            },
            satellitealt: {
                enabled: false
            },
            terrain: {
                enabled: false,
                url: '',
                minZoom: 1,
                maxZoom: 22,
                subdomains: ['a', 'b', 'c']
            },
            openstreetmap: {
                enabled: false,
                url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png'
            }
        },
        bounceOnEvents: [292, 293],
        bounceForDuration: 10000,
        enableShoutConfigOta: false,
        defaultColors: {
            //assets: '#8dc63f',
            //fences: '#8dc63f',
            //places: '#8dc63f'
        },
        allowAnonymousMessaging: false,
        dateRangeMin: null,
        dateRangeMax: null,
        defaultIconSet: 'default'
    };
    tracking.intervals = {
        history: null,
        positionStatus: null,
        playbackInterval: null,
        mapLayers: null,
        satelliteInterval: [],
        notification: null,
        notificationNum: 0,
        emergency: null,
        version: null,
        snapToRoadsInterval: null,
        currentTime: null,
        cleanupExpiredData: null
    };
    tracking.throttles = {
        updateLiveAssets: null,
        queryLatestNotifications: null,
        queryLatestEvents: null,
        updatePositionStatus: null,
        emergencyAlert: null
    };
    tracking.notificationConnection = null;
    tracking.notificationService = null;
    tracking.dialogs = {
        gsatmicro: null,
        closeButton: null
    };
    // end public variables


    // private variables
    var points = [];
    var polygon = null; // polygon fence associated with Asset
    var assetId = '';
    var markers = [];
    //var fenceMarkers = [];
    var fenceOverrides = [];
    var waypointMarkers = [];
    var locations = [];

    // methods
    tracking.querySharedViewData = querySharedViewData;
    tracking.testAis = startAisOverlay;
    tracking.testLoadUp = testLoadup;
    tracking.testSequential = testSequential;
    tracking.testSemiSequential = testSemiSequential;

    tracking.testAudio = function () {
        tracking.throttles.emergencyAlert();
    };

    tracking.cleanupData = cleanupExpiredLiveData;

    tracking.openRunReportModal = openRunReportModal;

    tracking.testSplit = splitPathAcrossAntiMeridian;

    tracking.switchViewMode = switchViewMode;

    tracking.resizeInfoWindow = function () {
        resizeInfoWindow();
    };

    tracking.setOptions = function (options) {
        $j.extend(true, tracking, options);
    };

    tracking.decodePolyline = function (encoded) {
        var decoded = decode_polyline(encoded);
        console.log(decoded);
        return decoded;
    };

    tracking.updateLive = function () {
        updateLiveAssets();
        //queryLatestEvents(eventId);
    };

    tracking.toggleEvents = function () {
        toggleEventContainer();
    };

    tracking.log = function (msg) {
        var m;
        if (typeof (console) === "undefined") {
            return;
        }
        m = "[" + new Date().toISOString() + "] Track: " + msg;
        if (console.debug) {
            console.debug(m);
        } else if (console.log) {
            console.log(m);
        }
    };

    tracking.reverseGeocode = reverseGeocode;

    tracking.addressSearch = function (address, callback) {
        var data = { address: address };
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/AddressSearch'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                console.log('address search success');
                var result = msg.d;
                if (result) {
                    console.log(result);
                    if (result.Success === true) {
                        callback(true, result.Result);
                    } else {
                        callback(false);
                    }
                }
            },
            error: function (xhr, status, error) {
                callback(false);
            }
        });
    };

    tracking.clearBounds = function () {
        tracking.state.bounds = L.latLngBounds();
        tracking.state.boundsItems = [];
    };

    tracking.extendBounds = function (location) {
        if (tracking.state.bounds === null) {
            return;
        }
        tracking.state.boundsItems.push(location);
        if (tracking.state.bounds.isValid()) {
            if (tracking.state.bounds.contains(location)) {
                return;
            }
        }
        tracking.state.bounds.extend(location);
    };

    tracking.openSecondaryPanel = openSecondaryPanel;

    tracking.init = function () {
        tracking.log('Initialize Tracking.');

        L.Icon.Default.imagePath = '/content/images/';

        tracking.statusTitles = {
            'speeding': tracking.strings.SPEEDING,
            'towing': tracking.strings.TOWING,
            'moving': tracking.strings.MOVING,
            'stationary': tracking.strings.STATIONARY,
            'immobilized': tracking.strings.IMMOBILIZED,
            'idling': tracking.strings.IDLING,
            'ignition-on': tracking.strings.IGNITION_ON,
            'ignition-off': tracking.strings.IGNITION_OFF,
            'antenna-cut': tracking.strings.ANTENNA_CUT,
            'gps-jammed': tracking.strings.GPS_JAMMED,
            'low-power-mode': tracking.strings.IN_LOW_POWER_MODE,
            'cell-jammed': tracking.strings.CELL_JAMMED,
            'backup-power': tracking.strings.ON_BACKUP_POWER
        };

        // initialize dom references
        tracking.data.domNodes.map = document.getElementById('map');
        tracking.data.domNodes.panels.primary = document.getElementById('panel-primary');
        tracking.data.domNodes.panels.secondary = document.getElementById('panel-secondary');
        tracking.data.domNodes.simpleBars.primary = new SimpleBar(document.getElementById('panel-content-wrapper'));
        tracking.data.domNodes.simpleBars.secondary = new SimpleBar(document.getElementById('panel-secondary-content-wrapper'));
        tracking.data.domNodes.nav.utility = document.getElementById('nav-utility');
        tracking.data.domNodes.nav.primary = document.getElementById('nav-primary');
        tracking.data.domNodes.nav.toggle = document.getElementById('nav-toggle');
        tracking.data.domNodes.content.base = document.getElementById('base-content');
        tracking.data.domNodes.filter.assetResults = document.getElementById('filter-asset-results');
        tracking.data.domNodes.filter.fenceResults = document.getElementById('filter-fence-results');
        tracking.data.domNodes.filter.placeResults = document.getElementById('filter-place-results');
        tracking.data.domNodes.filter.sharedViewResults = document.getElementById('filter-shared-view-results');
        tracking.data.domNodes.mapTools.container = document.getElementById('map-functions');
        tracking.data.domNodes.mapTools.currentTime = document.getElementById('map-time');
        tracking.data.domNodes.mapTools.currentTimeCompact = document.getElementById('map-time-compact');
        tracking.data.domNodes.moduleName = document.getElementById('module-name');
        tracking.data.domNodes.assetsModeLive = document.getElementById('assets-panel-mode-live');
        tracking.data.domNodes.assetsModeHistory = document.getElementById('assets-panel-mode-history');
        tracking.data.domNodes.mapMode.container = document.getElementById('map-mode');
        tracking.data.domNodes.mapMode.show = document.getElementById('btnGo');
        tracking.data.domNodes.mapMode.from = document.getElementById('map-mode-details-history-from');
        tracking.data.domNodes.mapMode.to = document.getElementById('map-mode-details-history-to');
        tracking.data.domNodes.mapMode.liveLoaded = document.getElementById('map-mode-details-live-from');
        tracking.data.domNodes.mapMode.visibleAssetsLive = document.getElementById('visible-assets-live');
        tracking.data.domNodes.mapMode.visibleAssetsHistory = document.getElementById('visible-assets-history');
        tracking.data.domNodes.mapMode.visiblePositionsHistory = document.getElementById('visible-positions-history');
        tracking.data.domNodes.mapMode.modeLiveButton = document.getElementById('history-live');
        tracking.data.domNodes.mapMode.excessData = document.getElementById('map-mode-excess-data');
        tracking.data.domNodes.mapMode.dateRange = document.getElementById('form-history-date-range');
        updateUserTime();
        tracking.data.domNodes.mapTools.visibleSummary = document.getElementById('map-visible-summary');
        tracking.data.domNodes.assetNotificationMenu = document.getElementById('asset-notification-menu');
        tracking.data.domNodes.assetNotificationMenu.parentNode.removeChild(tracking.data.domNodes.assetNotificationMenu);
        tracking.data.domNodes.assetNotificationMenu.classList.remove('toggle-content');
        tracking.data.domNodes.tripNotificationMenu = tracking.data.domNodes.assetNotificationMenu.cloneNode(true);
        tracking.data.domNodes.followLiveStatus = document.getElementById('live-follow-status');
        tracking.options.defaultColor = window.getComputedStyle(document.getElementById('nav-toggle')).backgroundColor;
        tracking.state.openPanels.primary = (window.screen.width > 768 && !tracking.data.domNodes.panels.primary.classList.contains('is-closed')) || tracking.data.domNodes.panels.primary.classList.contains('is-opened');

        tracking.walkthrough.initialize();

        var button = document.getElementById('toggle-nav-primary');
        if (tracking.data.domNodes.panels.primary.style.display !== 'flex') {
            button.classList.remove('active');
            button.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#expand');
        }

        if (tracking.options.disableMapTypeSelection) {
            var mapType = document.getElementById('map-type-select');
            mapType.classList.add('d-none');
        }

        if (tracking.options.disableLayerSelection) {
            var mapType = document.getElementById('map-layers-select');
            mapType.classList.add('d-none');
        }

        if (tracking.options.disablePanels) {
            var eventControl = document.getElementById('event-control-btn');
            var alertsControl = document.getElementById('alerts-control-btn');
            eventControl.classList.add('d-none');
            alertsControl.classList.add('d-none');
        }

        if (tracking.user.isAnonymous && !tracking.options.showSidePanel) {
            closePrimaryPanel();
        }
        if (tracking.options.disableHeader) {
            $j('#head').addClass('anonymous');
        }

        if (tracking.options.isCompact) {
            // this need to be done after all data load or history view change needs to check for first load
            tracking.data.domNodes.mapTools.container.classList.add('is-closed');
            tracking.data.domNodes.mapMode.container.classList.add('is-minimized');
            tracking.data.domNodes.mapMode.dateRange.classList.remove('is-visible');
        }

        var layerList = document.getElementById('map-layers-list');
        if (layerList.querySelectorAll('a').length === 0){
            layerList.classList.add('disabled');
        }

        tracking.dialogs.closeButton = {
            buttonType: 'secondary',
            icons: { primary: 'ui-icon-close' },
            text: tracking.strings.CANCEL,
            click: function () { document.getElementById('panel-dialog-back').click(); }
        };

        tracking.dialogs.closePanelButton = {
            buttonType: 'secondary',
            icons: { primary: 'ui-icon-close' },
            text: tracking.strings.CANCEL,
            click: function () {
                closeSecondaryPanel();
            }
        };

        tracking.state.promises.getGroupsAndAssets = queryGroupsAndAssets();
        tracking.state.promises.getAssetUsers = queryUsersForAssets();

        $.fn.dataTable.moment(tracking.user.dateFormat);
        $.datepicker.parseDate = function (format, value) {
            return moment(value, format).toDate();
        };
        $.datepicker.formatDate = function (format, value) {
            return moment(value).format(format);
        };

        // handlebars templates must be precompiled
        tracking.templates.asset = Handlebars.templates.asset;
        tracking.templates.assetGroup = Handlebars.templates.assetGroup;
        tracking.templates.place = Handlebars.templates.place;
        tracking.templates.geofence = Handlebars.templates.geofence;
        tracking.templates.trip = Handlebars.templates.trip;
        tracking.templates.sharedView = Handlebars.templates.sharedView;

        tracking.throttles.queryLatestNotifications = _.throttle(queryLatestNotifications, 5000);
        tracking.throttles.updateLiveAssets = _.throttle(updateLiveAssets, 5000);
        tracking.throttles.queryLatestEvents = _.throttle(queryLatestEvents, 5000);
        tracking.throttles.updatePositionStatus = _.throttle(updatePositionStatus, 3000);
        tracking.throttles.resizeApp = _.throttle(resizeApp, 100);
        tracking.throttles.bufferGeofence = _.throttle(previewGeofenceBuffer, 1000);
        tracking.throttles.emergencyAlert = _.throttle(alertUserOfEmergency, 2000, { trailing: false });

        //$j(".multiselect").multiselect({ sortable: true, searchable: false });

        tracking.intervals.version = setInterval(checkVersion, 1000 * 60 * 10);
        tracking.intervals.emergency = setInterval(checkEmergencyNotifications, 1000 * 10 * 1);
        tracking.intervals.text = setInterval(checkTextNotifications, 1000 * 60 * 1);
        tracking.intervals.currentTime = setInterval(updateUserTime, 1000 * 5 * 1);
        tracking.intervals.cleanupExpiredData = setInterval(cleanupExpiredLiveData, 1000 * 60 * 30);

        var sharedViewPwdStrength = new PasswordStrengthMeter(document.getElementById('SharedViewPasswordStrength'), tracking.strings.PASSWORD_INSECURE);

        $j('#fence-tooltip').tooltip({
            track: true,
            content: 'Nice!',
            position: {
                my: 'left+15 bottom+15',
                //                at: 'right center',
                of: '#panel'
            }
        });

        if (tracking.preferences.PREFERENCE_EMERGENCY_AUDIO) {
            initEmergencyAudio();
        }

        $j('#event-panel-container').resizable({ handles: 'n', minHeight: 100 }).bind('resize', function (e, ui) {
            $j(this).css('top', 'auto');
            $j('#event-panel-events,#event-panel-alerts', this).css('max-height', $j(this).height() - 45);
            tracking.throttles.resizeApp(true);
        });
        $j('#event-control-btn').on('click', 'a', function (e) {
            e.preventDefault();
            toggleEventContainer(false);
        });
        $j('#alerts-control-btn').on('click', 'a', function (e) {
            e.preventDefault();
            toggleEventContainer(true);
        });

        initializeFormValidation();
        //$('#event-panel-tabs').on('tabsbeforeactivate', function (event, ui) {
        //    if (ui.newTab[0].id === 'event-panel-close')
        //        toggleEventContainer();
        //});
        //$j('#event-panel-tabs').on('click', 'li', function (e) {
        //    e.stopPropagation();
        //});
        //$j('#event-panel-tabs').on('click', 'ul', function (e) {
        //    e.preventDefault();
        //    toggleEventContainer();
        //});

        $('#dialog-functions').on('click', '.list-container .alert-acknowledge-icon', function (e) {
            var listing = this.parentNode.parentNode.parentNode;
            var location = listing.querySelector('.event-location');
            if (location !== null) {
                if (!location.classList.contains('is-visible')) {
                    location.classList.add('is-visible');
                }
            }
            var details = listing.querySelector('.event-details');
            $(details).collapse('show');

            var textarea = listing.querySelector('textarea');
            if (textarea !== undefined && textarea !== null) {
                textarea.focus();
            }

            tracking.data.domNodes.simpleBars.secondary.recalculate();
        });

        $('.list-container').on('hide.bs.collapse', function (e) {
            e.target.previousElementSibling.querySelector('.list-item-details use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#expand');
        });
        $('.list-container').on('show.bs.collapse', function (e) {
            e.target.previousElementSibling.querySelector('.list-item-details use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#collapse');
        });

        $('#dialog-functions').on('click', '.list-container .alert-details, .list-container .event-details-header a', function (e) {
            // TODO handle multiple in listing
            e.preventDefault();
            var header = this.parentNode;
            var details = header.nextElementSibling;
            var listing = this.parentNode.parentNode;
            var icon = header.querySelector('.alert-details');
            // what is this
            var location = listing.querySelector('.event-location');
            if (location !== null) {
                if (location.classList.contains('is-visible')) {
                    location.classList.remove('is-visible');
                } else {
                    location.classList.add('is-visible');
                }
            }
            //var details = listing.querySelector('.event-details');
            if (details !== null) {
                if (details.classList.contains('is-visible')) {
                    details.classList.remove('is-visible');
                    icon.classList.remove('active');
                    icon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#expand');
                } else {
                    details.classList.add('is-visible');
                    icon.classList.add('active');
                    icon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#collapse');
                }
            }
            tracking.data.domNodes.simpleBars.secondary.recalculate();
        });

        $j('#event-panel-container').on('click', '#event-panel-close', function (e) {
            e.preventDefault();
            toggleEventContainer();
        });
        $j('#event-panel-container').on('click', '.panel-header a', function (e) {
            e.preventDefault();
            toggleEventContainer();
        });
        $j('#event-panel-container').on('click', '.panel-header a', function (e) {
            e.preventDefault();
            toggleEventContainer();
        });



        $j('#event-panel-container').on('click', '#ClearEvents', function (e) {
            e.preventDefault();
            populateEvents([]);
        });

        // drag to zoom key events
        $j(document).on('keydown', function (event) {
            tracking.keys[event.which] = true;
            if (event.which === 68) { // ctrl+alt+D
                if (event.ctrlKey && event.altKey) {
                    populateDiagnostics();
                    $j('#map_diagnostics').toggle();
                }
            }
            if(event.which === 17) {
                // while held, it will trigger repeatedly
                if(!tracking.data.zoom.isActive) {
                    // start a timer to clear it after a few seconds, since the user may have
                    // used a control combination for a browser shortcut that has no associated keyup
                    enableDragZoom();
                    tracking.state.zoomTimeout = setTimeout(disableDragZoom, 3000);
                } else {
                    // reset the timeout
                    clearTimeout(tracking.state.zoomTimeout);
                    tracking.state.zoomTimeout = setTimeout(disableDragZoom, 3000);
                }
            }
            if (event.which === 13 && event.ctrlKey) {  // ctrl+enter
                if (document.activeElement === document.getElementById('IDPCommand')) {
                    // auto submit the command
                    idpSendCommand();
                }
            }
        });

        $(document).on('keyup', function (event) {
            delete tracking.keys[event.which];
            if (event.which === 17 && tracking.data.zoom.isActive) {
                disableDragZoom();
            }
        });

        $(window).on('resize', resizeApp);

        $('#map-functions').on('click', 'button', function (e) {
            e.preventDefault();
            $(this).bsTooltip('hide');
        });

        $('#map-mode').on('click', '#map-mode-minimize', function (e) {
            e.preventDefault();
            if (tracking.data.domNodes.mapMode.container.classList.contains('is-minimized')) {
                tracking.data.domNodes.mapMode.container.classList.remove('is-minimized');
                this.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#angle-up');
            } else {
                tracking.data.domNodes.mapMode.container.classList.add('is-minimized');
                this.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#angle-down');
            }
        });

        $('#map-functions').on('click', '#map-functions-minimize', function (e) {
            e.preventDefault();
            if (tracking.data.domNodes.mapTools.container.classList.contains('is-minimized')) {
                tracking.data.domNodes.mapTools.container.classList.remove('is-minimized');
                this.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#minus');
            } else {
                tracking.data.domNodes.mapTools.container.classList.add('is-minimized');
                this.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#square');
            }
        });

        $('#map-functions').on('click', '#map-functions-close', function (e) {
            e.preventDefault();
            if (tracking.data.domNodes.mapTools.container.classList.contains('is-closed')) {
                tracking.data.domNodes.mapTools.container.classList.remove('is-closed');
                this.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#times');
            } else {
                tracking.data.domNodes.mapTools.container.classList.add('is-closed');
                this.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#external-link');
            }
        });

        $j('#map-functions').on('click', '#map-zoom-in', function (e) {
            e.preventDefault();
			tracking.map.zoomIn();
        });
        $j('#map-functions').on('click', '#map-zoom-out', function (e) {
            e.preventDefault();
			tracking.map.zoomOut();
        });
        $('#map-functions').on('click', '#map-legend', function (e) {
            e.preventDefault();
            $(tracking.data.domNodes.modals.mapLegend).modal('show');
        });

        $('#map-type').on('click', '#map-type-labels-show', function (e) {
            if (this.checked) {
                tracking.data.isSatelliteLabelOverlayEnabled = true;
                changeMapType(tracking.state.currentMapType);
            } else {
                tracking.data.isSatelliteLabelOverlayEnabled = false;
                changeMapType(tracking.state.currentMapType);
            }
        });

        $j('#map-type').on('click', '.road', function (e) {
            e.preventDefault();
            changeMapType('road');
        });

        $j('#map-type').on('click', '.roadlight', function (e) {
            e.preventDefault();
            changeMapType('roadlight');
        });

        $j('#map-type').on('click', '.roaddark', function (e) {
            e.preventDefault();
            changeMapType('roaddark');
        });

        $j('#map-type').on('click', '.satellite', function (e) {
            e.preventDefault();
            changeMapType('satellite');
        });

        $j('#map-type').on('click', '.satellitealt', function (e) {
            e.preventDefault();
            changeMapType('satellitealt');
        });

        $j('#map-type').on('click', '.openstreetmap', function (e) {
            e.preventDefault();
            changeMapType('openstreetmap');
        });

        $j('#map-type').on('click', '.terrain', function (e) {
            e.preventDefault();
            changeMapType('terrain');
        });

        $j('#map-type').on('click', '.bing', function (e) {
            e.preventDefault();
            changeMapType('bing');
        });

        $j('#map_type').on('click', '#map_type_select', function (e) {
            e.preventDefault();
            if ($j('#map_type_list:visible').length > 0) {
                collapseMapType();
            } else {
                expandMapType();
            }
        });

        $j('#map_layers').on('click', '#map_layers_select', function(e) {
            e.preventDefault();
            if ($j('#map_layers_list:visible').length > 0) {
                collapseMapLayers();
            } else {
                expandMapLayers();
            }
        });

        $('#map-layers').on('click', '#map-layers-list', function (e) {
            e.stopPropagation();
        });

        $('#map-type').on('click', '#map-type-list', function (e) {
            e.stopPropagation();
        });

        $j('#map-layers').on('click', '.layer.iridium', function (e) {
            e.preventDefault();
            if (!tracking.data.iridium.isActive) {
                enableLayerIridium();
            } else {
                disableLayerIridium();
            }
        });
        $j('#map-layers').on('click', '.layer.iridiumnext', function (e) {
            e.preventDefault();
            if (!tracking.data.iridiumnext.isActive) {
                enableLayerIridiumNext();
            } else {
                disableLayerIridiumNext();
            }
        });

        $j('#map-layers').on('click', '.layer.custom', function (e) {
            e.preventDefault();
            var layerId = $j(this).data('layerId');
            if (_.indexOf(tracking.data.activeLayers, layerId) === -1) {
                enableCustomLayer(layerId);
            } else {
                disableCustomLayer(layerId);
            }
        });

        $j('#map-layers').on('click', '.layer.customize', function (e) {
            e.preventDefault();
            window.location = '/Settings/ListMapLayers';
        });

        $j('#map-layers').on('click', '.layer.globalstar', function (e) {
            e.preventDefault();
            if (!tracking.data.globalstar.isActive) {
                enableLayerGlobalstar();
            } else {
                disableLayerGlobalstar();
            }
        });

        $j('#map-layers').on('click', '.layer.inmarsat', function (e) {
            e.preventDefault();
            if (!tracking.data.inmarsat.isActive) {
                enableLayerInmarsat();
            } else {
                disableLayerInmarsat();
            }
        });

        $j('#map-layers').on('click', '.layer.orbcomm', function (e) {
            e.preventDefault();
            if (!tracking.data.orbcomm.isActive) {
                enableLayerOrbcomm();
            } else {
                disableLayerOrbcomm();
            }
        });

        $j('#map-layers').on('click', '.layer.geostationary', function (e) {
            e.preventDefault();
            if (!tracking.data.geostationary.isActive) {
                enableLayerGeostationary();
            } else {
                disableLayerGeostationary();
            }
        });

        $j('#map-layers').on('click', '.layer.radar', function(e) {
            e.preventDefault();
            if (!tracking.data.radar.isActive) {
                enableRadar();
            } else {
                disableRadar();
            }
        });

        $j('#map-layers').on('click', '.layer.radar-australia', function (e) {
            e.preventDefault();
            if (!tracking.data.radarAustralia.isActive) {
                //enableRadarAustralia(false, enableRadarAustraliaActive);
                enableRadarAustraliaActive();
            } else {
                disableRadarAustralia();
            }
        });

        $j('#map-layers').on('click', '.layer.worldIR', function (e) {
            e.preventDefault();
            if (!tracking.data.worldIR.isActive) {
                enableWorldIR();
            } else {
                disableWorldIR();
            }
        });

        $j('#map-layers').on('click', '.layer.oil', function (e) {
            e.preventDefault();
            if (!tracking.data.oil.isActive) {
                enableLayerOil();
            } else {
                disableLayerOil();
            }
        });

        $j('#map-layers').on('click', '.layer.maritime', function(e) {
            e.preventDefault();
            if (!tracking.data.maritime.isActive) {
                enableLayerMaritime();
            } else {
                disableLayerMaritime();
            }
        });

        $j('#map-layers').on('click', '.layer.traffic', function(e) {
            e.preventDefault();
            if (!tracking.data.traffic.isActive) {
                enableLayerTraffic();
            } else {
                disableLayerTraffic();
            }
        });

        $j('#map-layers').on('click', '.layer.clouds', function(e) {
            e.preventDefault();
            if (!tracking.data.clouds.isActive) {
                enableLayerClouds();
            } else {
                disableLayerClouds();
            }
        });

        $j('#map-layers').on('click', '.layer.weather', function(e) {
            e.preventDefault();
            if (!tracking.data.weather.isActive) {
                enableLayerWeather(true);
            } else {
                disableLayerWeather();
            }
        });

        $j('#root').on('mouseleave', '#map_layers', function(e) {
            // clear previous timeout, settimeout to half a second
            tracking.intervals.mapLayers = setTimeout(collapseMapLayers, 1000);
        });

        $j('#root').on('mouseleave', '#map_type', function (e) {
            // clear previous timeout, settimeout to half a second
            tracking.intervals.mapType = setTimeout(collapseMapType, 1000);
        });

        $j('#root').on('mouseenter', '#map_layers', function(e) {
            clearTimeout(tracking.intervals.mapLayers);
        });

        $j('#root').on('mouseenter', '#map_type', function (e) {
            clearTimeout(tracking.intervals.mapType);
        });

        // label enabling/disabling
        $j('#labels-output,#labels-input-digital').on('click', 'input[type=checkbox]', function(e) {
            var isChecked = $j(this).prop('checked');
            $j(this).parent().parent().parent().next('input').prop('disabled', !isChecked);
        });

        $j('#labels-input-analog').on('change', 'select.behavior', function (e) {
            toggleAnalogIOBehavior(this);
        });

        var closeButton = {
            icons: { primary: 'ui-icon-close' },
            text: tracking.strings.CANCEL,
            click: function () {
                document.getElementById('panel-dialog-back').click();
            },
            buttonType: 'secondary'
        };

        tracking.data.domNodes.modals.mapLegend = document.getElementById('map-legend-modal');
        tracking.data.domNodes.modals.uploadFile = document.getElementById('upload-file-modal');
        $(tracking.data.domNodes.modals.uploadFile).on('change', '#UploadGeofencesMerge', function (e) {
            if (this.checked) {
                $('#UploadGeofenceMergeSingle').show();
                $('#MergeGeofenceName').prop('disabled', false);
            } else {
                $('#UploadGeofenceMergeSingle').hide();
                $('#MergeGeofenceName').prop('disabled', true);
            }
        });
        $(tracking.data.domNodes.modals.uploadFile).on('click', '#UploadKML', function (e) {
            e.preventDefault();

            toggleLoadingMessage(true, 'upload-kml');
            var status = document.getElementById('upload-file-status');
            var button = this;
            button.disabled = true;
            if (!uploadFile('fileKML',
                '/KML/UploadKML',
                {},
                function (res) {
                    button.disabled = false;
                    toggleLoadingMessage(false, 'upload-kml');
                    if (res === '' || res === 'empty') { // plugin does not handle empty result properly
                        return;
                    }
                    res = JSON.parse(res);
                    console.log(res);
                    if (res.Success === true) {
                        $('#UploadFileGeofencesMerge').hide();
                        $('#UploadFilePreview').show();
                        var places = $('#UploadFilePlaces ul').empty();
                        var geofences = $('#UploadFileGeofences ul').eq(0).empty();
                        var hasItems = false;
                        var i;
                        var li;
                        if (res.Places !== null) {
                            if (res.Places.length === 0) {
                                places.append($('<li>').text(tracking.strings.UPLOAD_FILE_NO_PLACES));
                            }
                            for (i = 0; i < res.Places.length; i++) {
                                hasItems = true;
                                var place = res.Places[i];
                                li = $('<li>')
                                    .append($('<div class="custom-control custom-checkbox">')
                                        .append($('<input class="custom-control-input" type="checkbox" checked="checked" id="Place' + i + '">').data('place', place))
                                        .append($('<label class="custom-control-label" for="Place' + i + '">').text(place.Name))
                                    );
                                places.append(li);
                            }
                        }
                        if (res.Fences !== null) {
                            if (res.Fences.length === 0) {
                                geofences.append($('<li>').text(tracking.strings.UPLOAD_FILE_NO_GEOFENCES));
                            }

                            for (i = 0; i < res.Fences.length; i++) {
                                hasItems = true;
                                var geofence = res.Fences[i];
                                li = $('<li>')
                                    .append($('<div class="custom-control custom-checkbox">')
                                        .append($('<input class="custom-control-input" type="checkbox" checked="checked" id="Geofence' + i + '">').data('geofence', geofence))
                                        .append($('<label class="custom-control-label" for="Geofence' + i + '">').text(geofence.Name))
                                    );
                                geofences.append(li);
                            }
                            if (res.Fences.length > 1) {
                                $('#UploadFileGeofencesMerge').show();
                            }
                        }
                        if (hasItems) {
                            $('#ImportPlacesAndFences')[0].disabled = false;
                        } else {
                            $('#ImportPlacesAndFences')[0].disabled = true;
                        }
                    } else {
                        formShowErrorMessage(status, res.ErrorMessage);
                    }
                }
            )) {
                button.disabled = false;
                toggleLoadingMessage(false, 'upload-kml');
                formShowErrorMessage(status, tracking.strings.MSG_UPLOAD_FILE_CHOOSE);
            }
        });
        $(tracking.data.domNodes.modals.uploadFile).on('click', '#ImportPlacesAndFences', function (e) {
            e.preventDefault();

            var modal = $(tracking.data.domNodes.modals.uploadFile);
            var status = document.getElementById('upload-file-status');
            var button = this;

            var places = [];
            $j('#UploadFilePlaces input:checked').each(function (index, elem) {
                var place = $j(this).data('place');
                if (place !== undefined) {
                    places.push(place);
                }
            });

            var fences = [];
            $j('#UploadFileGeofences input:checked').each(function (index, elem) {
                var fence = $j(this).data('geofence');
                if (fence !== undefined) {
                    fences.push(fence);
                }
            });

            var request = {
                Places: places,
                Fences: fences,
                MergeFences: $('#UploadGeofencesMerge').prop('checked'),
                FenceName: $('#MergeGeofenceName').val()
            };
            var data = { request: request };

            handleAjaxFormSubmission('UploadPlacesAndGeofences', data, button, status, null, tracking.strings.MSG_IMPORT_ERROR, function (result) {
                var i;
                if (result.Fences !== null) {
                    for (i = 0; i < result.Fences.length; i++) {
                        var fence = result.Fences[i];
                        processNewFence(fence);
                        if (tracking.data.fenceUsers !== null) {
                            tracking.data.fenceUsers.push({ FenceId: fence.Id, UserIds: [] });
                        }
                    }
                }
                if (result.Places !== null) {
                    for (i = 0; i < result.Places.length; i++) {
                        var place = result.Places[i];
                        processNewPlace(place);
                        if (tracking.data.placeUsers !== null) {
                            tracking.data.placeUsers.push({ PlaceId: place.Id, UserIds: [] });
                        }
                    }
                }

                modal.modal('hide');
            });
        });
        if (tracking.user.isAnonymous) {
            $('#upload-file').prop('disabled', true);
            $('.dropdown-item[data-action="upload-file"]').addClass('disabled');
        }
        //$j('#map-functions').on('click', '#upload-file', function (e) {
        //    e.preventDefault();

        //    document.getElementById('ImportPlacesAndFences').disabled = true;
        //    var status = document.getElementById('upload-file-status');
        //    clearStatusMessage(status);
        //    $(tracking.data.domNodes.modals.uploadFile).modal('show');
        //    $('#UploadGeofencesMerge').prop('checked', false);
        //    $('#MergeGeofenceName').val('');
        //    $j('#UploadFilePreview,#UploadFileGeofencesMerge').hide();
        //});

        var acknowledgeAlertButtons = [
            {
                id: 'AcknowledgeDialog',
                text: tracking.strings.ACKNOWLEDGE,
                click: function () {
                    acknowledgeAssetAlert(this, true);
                }
            },
            {
                id: 'AcknowledgeAltDialog',
                buttonType: 'secondary',
                class: 'alert-acknowledge-alt',
                text: tracking.strings.ACKNOWLEDGE_ALT,
                click: function () {
                    acknowledgeAssetAlert(this, true);
                }
            },
            closeButton
        ];
        tracking.data.domNodes.dialogs.acknowledgeAlert = document.getElementById('acknowledge-alert-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.acknowledgeAlert, acknowledgeAlertButtons);
        $(tracking.data.domNodes.dialogs.acknowledgeAlert).on('click', '#alert-prev', function (e) {
            e.preventDefault();
            openAlertInDialog(-1);
        });
        $(tracking.data.domNodes.dialogs.acknowledgeAlert).on('click', '#alert-next', function (e) {
            e.preventDefault();
            openAlertInDialog(1);
        });

        tracking.data.domNodes.modals.eventFilter = document.getElementById('event-filter-modal');
        $(tracking.data.domNodes.modals.eventFilter).on('click', '#event-filter-filter', function (e) {
            var eventFilters = [];
            var allChecked = $('#event-filter-all').prop('checked');
            if (allChecked) {
                eventFilters.push('all');
            } else {
                var items = $('#event-filter-modal .custom-control-input').not('#event-filter-all');
                items.each(function () {
                    if ($(this).prop('checked')) {
                        eventFilters.push(parseInt($(this).val()));
                    }
                });
            }

            if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                tracking.data.live.eventFilters = eventFilters;
            } else if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
                tracking.data.history.eventFilters = eventFilters;
            }

            $(tracking.data.domNodes.modals.eventFilter).modal('hide');

            $('#event-data').DataTable().draw();
        });

        $('#event-filter-modal').on('click', '#event-filter-none', function (e) {
            e.preventDefault();
            $('#event-filter-modal input').prop('checked', false).prop('disabled', false);
        });

        $('#event-filter-modal').on('change', '#event-filter-all', function (e) {
            var checked = $(this).prop('checked');
            if (checked) {
                $('#event-filter-modal input').prop('checked', true).prop('disabled', true);
                $(this).prop('disabled', false);
            } else {
                $('#event-filter-modal input').prop('disabled', false);
            }
        });

        $('#event-panel-container').on('click', '#FilterEvents', function (e) {
            e.preventDefault();
            $('#event-filter-modal').modal('show');
            // populate li items appopriately...
        });

        tracking.data.domNodes.infoDialogs.eventDetails = document.getElementById('event-details-dialog');
        $j(tracking.data.domNodes.infoDialogs.eventDetails).dialog({
            autoOpen: false,
            resizable: true,
            modal: false,
            width: 400,
            create: function () {
                createDialogTitlebar(this);
                replaceDialogButtons(this, $(this).dialog('option', 'buttons'));
            }
        });

        //tracking.data.domNodes.infoDialogs.placeInformation = document.getElementById('place-information-dialog');
        //$(tracking.data.domNodes.infoDialogs.placeInformation).dialog({
        //    autoOpen: false,
        //    resizable: false,
        //    modal: false,
        //    maxWidth: 375,
        //    create: function (event, ui) {
        //        createDialogTitlebar(this);
        //        replaceDialogButtons(this, $(this).dialog('option', 'buttons'));
        //        $(this).closest('.ui-dialog')
        //            .draggable('option', 'containment', '#base-content')
        //            .attr('id', 'dialog-place-information');
        //    }
        //});
        //tracking.data.domNodes.infoDialogs.geofenceInformation = document.getElementById('geofence-information-dialog');
        //$(tracking.data.domNodes.infoDialogs.geofenceInformation).dialog({
        //    autoOpen: false,
        //    resizable: false,
        //    modal: false,
        //    maxWidth: 375,
        //    create: function (event, ui) {
        //        createDialogTitlebar(this);
        //        replaceDialogButtons(this, $(this).dialog('option', 'buttons'));
        //        $(this).closest('.ui-dialog')
        //            .draggable('option', 'containment', '#base-content')
        //            .attr('id', 'dialog-geofence-information');
        //    }
        //});

        tracking.data.domNodes.infoDialogs.sharedViewInformation = document.getElementById('shared-view-information-dialog');
        tracking.data.domNodes.infoDialogs.sharedViewInformation.manualCloseCallback = function () {
            deselectSharedView();
        }
        $(tracking.data.domNodes.infoDialogs.sharedViewInformation).dialog({
            autoOpen: false,
            resizable: false,
            modal: false,
            width: 350,
            maxWidth: 400,
            minHeight: 0,
            create: function (event, ui) {
                // undocumented call to allow html in title (for icon) - can't be shared for some reason
                $(tracking.data.domNodes.infoDialogs.sharedViewInformation).data("uiDialog")._title = function (title) {
                    setChildren(title[0], this.options.title);
                };
                
                createDialogTitlebar(this);
                replaceDialogButtons(this, $(this).dialog('option', 'buttons'));
                $(this).closest('.ui-dialog')
                    .draggable('option', 'containment', '#base-content')
                    .attr('id', 'dialog-shared-view-information');
                initializeSharedViewInformationDialog();
            },
            close: function (event, ui) {
                //if (tracking.data.sharedView.current !== null && !tracking.state.openPanels.secondary) {
                //    deselectSharedView();
                //}                
            },
            open: function (event, ui) {
                //if (this.getAttribute('data-has-collapse')) {
                //    if (this.getAttribute('data-collapsed') === 'true') {
                        
                //    }
                //}
            }
        });

        tracking.data.domNodes.infoDialogs.positionHistory = document.getElementById('position-history-dialog');
        $(tracking.data.domNodes.infoDialogs.positionHistory).dialog({
            autoOpen: false,
            resizable: false,
            modal: false,
            maxWidth: 375,
            create: function (event, ui) {
                // undocumented call to allow html in title (for icon) - can't be shared for some reason
                $(tracking.data.domNodes.infoDialogs.positionHistory).data("uiDialog")._title = function (title) {
                    setChildren(title[0], this.options.title);
                };

                createDialogTitlebar(this);
                replaceDialogButtons(this, $(this).dialog('option', 'buttons'));
                $(this).closest('.ui-dialog')
                    .draggable('option', 'containment', '#base-content')
                    .attr('id', 'dialog-position-history');
            }
        });

        tracking.data.domNodes.infoDialogs.mapItemInformation = document.getElementById('map-item-information-dialog');
        $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog({
            autoOpen: false,
            resizable: false,
            modal: false,
            //width: 375,
            maxWidth: 375,
            minHeight: 0,
            open: function (event, ui) {
                // start listening for left/right key presses
                if (tracking.state.activeMapMode !== tracking.mapModes.LIVE) {
                    // todo: check if mapInformation is for an asset
                    $(document).off('keydown.map-item-information-dialog').on('keydown.map-item-information-dialog', function (e) {
                        switch (e.which) {
                            case 37: // left
                                playbackPrevPosition();
                                e.preventDefault();
                                e.stopPropagation();
                                break;
                            case 39: // right
                                playbackNextPosition();
                                e.preventDefault();
                                e.stopPropagation();
                                break;
                        }
                    });
                }
            },
            create: function (event, ui) {
                // undocumented call to allow html in title (for icon) - can't be shared for some reason
                $(tracking.data.domNodes.infoDialogs.mapItemInformation).data("uiDialog")._title = function (title) {
                    setChildren(title[0], this.options.title);
                };

                createDialogTitlebar(this);
                replaceDialogButtons(this, $(this).dialog('option', 'buttons'));
                $(this).closest('.ui-dialog')
                    .draggable('option', 'containment', '#base-content')
                    .attr('id', 'dialog-map-item-information'); // give it an id for styling
                $(tracking.data.domNodes.infoDialogs.mapItemInformation).bsTooltip({ selector: '.estimated', placement: 'top' });
            }
        });

        $(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', '#playback-first', function (e) {
            e.preventDefault();
            playbackFirstPosition();
        });
        $(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', '#playback-prev', function (e) {
            e.preventDefault();
            playbackPrevPosition();
        });
        $(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', '#playback-next', function (e) {
            e.preventDefault();
            playbackNextPosition();
        });
        $(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', '#playback-last', function (e) {
            e.preventDefault();
            playbackLastPosition();
        });
        $(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', '#playback', function (e) {
            e.preventDefault();
            playbackClick();
        });

        $(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', '#WaypointMarkComplete', function (e) {
            e.preventDefault();

            var waypoint = findWaypointById(parseInt(this.getAttribute('data-waypoint-id')));
            waypointMarkComplete(waypoint);
        });

        $(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', '#WaypointRequestETAUpdate', function (e) {
            e.preventDefault();
            var button = $(this).prop('disabled', true);
            var waypointId = $(this).data('waypointId');
            var waypoint = findWaypointById(waypointId);

            if (waypoint == null) {
                return;
            }
            var asset = findAssetById(waypoint.AssetId);

            if (asset == null) {
                return;
            }
            toggleLoadingMessage(true, 'waypoint-eta');
            var dataPost = {
                assetId: asset.Id
            };
            $.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/SendGarminStopETARequest'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            $('#WaypointRequestETASuccess').show().fadeOut(5000);
                        } else {
                            utility.handleWebServiceError(tracking.strings.WAYPOINT_ETA_ERROR);
                        }
                    }
                    toggleLoadingMessage(false, 'waypoint-eta');
                    button.prop('disabled', false);
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.WAYPOINT_ETA_ERROR);
                    toggleLoadingMessage(false, 'waypoint-eta');
                    button.prop('disabled', false);
                }
            });

        });

        $(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', '#WaypointRemoveRoute', function (e) {
            e.preventDefault();

            var waypoint = findWaypointById(parseInt(this.getAttribute('data-waypoint-id')));
            waypointClearRoute(waypoint);
        });

        $(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', '#WaypointGetRoute', function (e) {
            e.preventDefault();

            var waypoint = findWaypointById(parseInt(this.getAttribute('data-waypoint-id')));
            waypointGetRoute(waypoint);
        });

        $j('#alert-data,#message-alerts').on('click', 'button.AlertAcknowledge', function (e) {
            e.preventDefault();
            var eventId = parseInt($j(this).data('alert'));
            var event = _.find(tracking.data.alerts, function (alert) { return alert.Id === eventId; });
            if (event !== undefined) {
                openAcknowledgeAlertDialog(null, event);
                //openAlertsRequiringAcknowledgementDialog(event);
            }
        });

        $('#panel-secondary').on('click', 'button.alert-acknowledge,button.alert-acknowledge-alt', function (e) {
            e.preventDefault();
            acknowledgeAssetAlert(this, false);
        });

        $j(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', 'button.AlertAcknowledge', function (e) {
            e.preventDefault();

            acknowledgeAssetAlert(this, false);
        });

        $j(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', 'button.AlertAcknowledgeAlt', function (e) {
            e.preventDefault();

            acknowledgeAssetAlert(this, false);
        });

        function addTemporaryPositionMarker(position, asset, message) {
            var positionMarker = tracking.data.live.markersByPositionId[position.Id];
            var isMarkerAdded = false;
            if (positionMarker === undefined) {
                // create the marker
                positionMarker = addPositionMarkerToPoint(L.latLng(position.Lat, position.Lng), false, position, asset, 255, null, null, null, tracking.mapModes.LIVE);
                isMarkerAdded = true;
            }
            positionMarker.data.hide = true;
            positionMarker.data.message = message;
            if (!positionMarker.data.location.IsHidden) {
                addItemToMap(positionMarker, null, tracking.viewModes.NORMAL);
            }
            positionMarker.fire('click');
            if (isMarkerAdded) {
                tracking.data.live.markersByAssetId = _.groupBy(tracking.data.live.markers, function (marker) { return marker.data.assetId; });
                tracking.data.live.markersByPositionId = _.keyBy(tracking.data.live.markers, function (marker) { return marker.data.location.Id; });
            }
        }

        function showOrLoadAssetPosition(positionId, assetId, message) {
            if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                // check if the position is within our stored history, or is the current live position
                if (highlightPosition(positionId, message)) {
                    return;
                }
                
                var asset = findAssetById(assetId);                

                // position not in live markers, check asset live history TODO replace asset.Positions with tracking.data.live.positionsByAssetId[asset.Id]
                var livePosition = _.find(asset.Positions, function(item) { return item.Id === positionId; });
                if (livePosition !== undefined) {
                    addTemporaryPositionMarker(position, asset, message);
                    return;
                }

                // position still not found, query it
                console.log('querying position');
                toggleLoadingMessage(true, 'event-position');
                var data = { assetId: assetId, positionId: positionId };
                return $.ajax({
                    type: 'POST',
                    url: wrapUrl('/services/GPSService.asmx/GetPositionById'),
                    data: JSON.stringify(data),
                    contentType: 'application/json; charset=utf-8',
                    dataType: 'json',
                    success: function (msg) {
                        if (msg.d) {
                            var position = msg.d;
                            if (position == null) {
                                return;
                            }
                            // TODO is asset.Positions a duplicate of tracking.data.live.positionsByAssetId[asset.Id]?
                            if (asset.Positions != null) {
                                asset.Positions.push(position);
                            } else {
                                asset.Positions = [position];
                            }

                            addTemporaryPositionMarker(position, asset, message);
                        }
                        toggleLoadingMessage(false, 'event-position');
                    },
                    error: function (xhr, status, error) {
                        utility.handleWebServiceError(tracking.strings.MSG_LOAD_POSITION_ERROR);
                        toggleLoadingMessage(false, 'event-position');
                    }
                });
            } else {
                // should currently be on the map
                highlightPosition(positionId, message);
            }
        }

        $j('#map-item-information-dialog,#event-panel-events').on('click', 'a.beacon-place', function (e) {
            e.preventDefault();
            var placeId = $j(this).data('placeId');
            var place = findPlaceById(placeId);
            if (place != null) {
                // take to place
                for (var i = 0; i < tracking.data.placeMarkers.length; i++) {
                   // var markerPlaceId = $j.data(tracking.data.placeMarkers[i], 'placeId');
                    var markerPlaceId = tracking.data.placeMarkers[i].data.placeId;
                    if (markerPlaceId == placeId) {
						tracking.data.placeMarkers[i].fire('click');
                        break;
                    }
                }
            }
        });
        $(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', 'a.location', addOrUpdatePlaceClick);

        $j('#event-panel-container,#acknowledge-alert-dialog,#message-alerts').on('click', 'a.location', function (e) {
            e.preventDefault();

            var positionId = $j(this).attr('data-marker');
            var assetId = parseInt($j(this).attr('data-asset'));
            showOrLoadAssetPosition(positionId, assetId);
        });

        $j('#acknowledge-alert-dialog').on('click', '.alert-ack-type', function (e) { //#AcknowledgeDetails
            e.preventDefault();
            $j(this).parent().parent().find('a.location').trigger('click');
        });

        $j('#garmin-forms-dialog,#drivers-dialog,#position-history-dialog').on('click', 'a.location', function (e) {
            e.preventDefault();

            var positionId = $j(this).attr('data-marker');
            var assetId = $j(this).parents('div.dialog').data('assetId');
            showOrLoadAssetPosition(positionId, assetId);
        });

        $(tracking.data.domNodes.infoDialogs.positionHistory).on('change', 'select.time-picker', function (e) {
            var dialog = $(tracking.data.domNodes.infoDialogs.positionHistory);
            var asset = findAssetById(dialog.data('assetId'));
            var trip = findTripById(dialog.data('tripId'));
            var fromId = $('#positions-begin-header .time-picker').val();
            var toId = $('#positions-end-header .time-picker').val();
            createPositionHistorySummary(asset, trip, fromId, toId);
        });
        $j('#event-data').on('click', 'button.ViewPhoto', function (e) {
        	e.preventDefault();

        	var photoData = $j(this).data('photo');
        	var assetId = $j(this).data('assetId');
        	var positionId = $j(this).parent().parent().find('a.location').attr('data-marker');
        	if (positionId != null) {
        		$j.when(showOrLoadAssetPosition(positionId, assetId))
                    .done(function () {
                	    // photo should be displayed in event detail
                    });
        	} else {
        		// tooltip with photo above the button?
                $j(this).tooltip().tooltip('close').tooltip('option', 'content', el('img.fatigue-image', { src: 'data:image/jpeg;base64, ' + photoData })).tooltip('open');
        	}
        });
        $('#asset-events-dialog').on('click', 'button.ViewGarminSubmission', function (e) {
            e.preventDefault();
            var submissionId = $j(this).data('id');
            var assetId = $j(this).data('assetId');
            loadGarminFormSubmission(assetId, submissionId, null);
        });
        $j('#event-data').on('click', 'button.ViewGarminSubmission', function (e) {
            e.preventDefault();

            var submissionId = $j(this).data('id');
            var assetId = $j(this).data('assetId');
            var positionId = $j(this).parent().parent().find('a.location').attr('data-marker');
            if (positionId != null) {
                $j.when(showOrLoadAssetPosition(positionId, assetId))
                .done(function () {
                    // activate garmin form panel
                    $('#accordion-garmin-content').collapse('show');
                });
            } else {
                loadGarminFormSubmission(assetId, submissionId, null);
            }
        });

        $('#form-add-geofence').on('click', 'button.add-alert', function (e) {
            e.preventDefault();
            var link = this.getAttribute('data-action');
            window.location = link;
        });

        $j('#form-edit-asset').on('click', 'a.portal-registration-help', function(e) {
            e.preventDefault();
            $('#iridium-verification-modal').modal('show');
        });

        $j('body').on('click', '#jGrowl button.acknowledge', function (e) {
            e.preventDefault();
            var event = null;
            var eventId = $j(this).data('eventId');
            for (var i = 0; i < tracking.data.alerts.length; i++) {
                if (tracking.data.alerts[i].Id == eventId) {
                    event = tracking.data.alerts[i];
                    break;
                }
            }
            if (event != null) {
                var asset = findAssetById(event.AssetId);
                openAcknowledgeAlertDialog(asset, event);
                //openAlertsRequiringAcknowledgementDialog(event);
            }
        });

        $('body').on('click', '#jGrowl button.notification-position', function(e) {
            e.preventDefault();
            
            var assetId = this.getAttribute('data-asset-id');
            var positionId = this.getAttribute('data-position-id');
            var text = $(this).parent().prev().find('div.message').text();

            showOrLoadAssetPosition(positionId, assetId, text);
        });

        // message notification acknowledgements
        $('body').on('click', '#jGrowl .reply-to-text', function(e) {
            e.preventDefault();
            if (tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging) {
                return;
            }
            var id = this.getAttribute('data-asset-id');
            var asset = findAssetById(id);
            openSendMessageToAssetDialog(asset);
        });

        $('body').on('click', '#jGrowl button.confirm', function(e) {
            e.preventDefault();
            if (tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging) {
                return;
            }
            var btn = this;
            var id = btn.getAttribute('data-notification-id');
            if (id !== undefined) {
                id = id.toString();
                if (id.indexOf('notify') !== -1) {
                    var notify = Cookies.get('notif');
                    var confirmedNotifications = [];
                    if (notify !== undefined) {
                        confirmedNotifications = notify.split(',');
                    }
                    confirmedNotifications.push(id.substring(7));
                    Cookies.set('notif', confirmedNotifications.join(','), { expires: 365, path: '/', secure: true });
                    $('#jGrowl div[data-notification-id=' + id + ']')
                        .trigger('jGrowl.close')
                        .find('button').prop('disabled', true);
                    return;
                }
            }
            var assetId = this.getAttribute('data-asset-id');
            var asset = findAssetById(assetId);
            btn.disabled = true;
            var dataPost = { id: id };
            $.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/ConfirmNotification'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    if (msg.d && asset != null && asset.UnconfirmedMessages != null) {
                        var index = _.indexOf(asset.UnconfirmedMessages, id.toString());
                        if (index !== -1) {
                            asset.UnconfirmedMessages.splice(index, 1);
                            tracking.throttles.updatePositionStatus();

                            // remove confirmed message from unread indexes
                            if (tracking.data.live.messageCountsByAssetId[asset.Id] !== undefined) {
                                var unreadIndex = _.indexOf(tracking.data.live.messageCountsByAssetId[asset.Id].FromMobileUnread, id);
                                if (unreadIndex !== -1) {
                                    tracking.data.live.messageCountsByAssetId[asset.Id].FromMobileUnread.splice(unreadIndex, 1);
                                }
                            }
                        }
                    }
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_CONFIRM_NOTIFICATION_ERROR);
                    btn.disabled = false;
                }
            });
        });

        $j('#edit-asset-inmarsatc').on('click', 'input[name=rbInmarsatCActivate]', function (e) {
            if ($j(this).val() == 'false') {
                $j('#inmarsatc-gateway-details').show();
                $j('#txtEditAssetInmarsatCDNID').rules('add', { required: true });
                $j('#txtEditAssetInmarsatCMemberNumber').rules('add', { required: true });
            } else {
                $j('#inmarsatc-gateway-details').hide();
                $j('#txtEditAssetInmarsatCDNID').rules('remove', 'required');
                $j('#txtEditAssetInmarsatCMemberNumber').rules('remove', 'required');
            }
        });

        //$j('#edit-asset-inmarsatc').on('click', '#InmarsatCDNIDDownload', function (e) {
        //    e.preventDefault();

        //    // ajax portal registration
        //    var id = $j('#hfEditAssetId').val();
        //    var status = $j('#edit-asset-status');
        //    status.text('').hide(); // clear previous status
        //    toggleLoadingMessage(true, 'dnid-download');
        //    var button = $j(this).prop('disabled', true);
        //    var dataPost = { assetId: id };
        //    $j.ajax({
        //        type: 'POST',
        //        url: wrapUrl('/Services/GPSService.asmx/InmarsatCDNIDDownload'),
        //        data: JSON.stringify(dataPost),
        //        contentType: 'application/json; charset=utf-8',
        //        dataType: 'json',
        //        success: function (msg) {
        //            button.prop('disabled', false);
        //            toggleLoadingMessage(false, 'dnid-download');
        //            var result = msg.d;
        //            if (result) {
        //                if (result.Success == true) {
        //                    status.show().text(tracking.strings.DNID_DOWNLOAD_SUCCESS).addClass('success');
        //                }
        //            }
        //        },
        //        error: function (xhr, status, error) {
        //            button.prop('disabled', false);
        //            utility.handleWebServiceError(tracking.strings.DNID_DOWNLOAD_ERROR);
        //            toggleLoadingMessage(false, 'dnid-download');
        //        }
        //    });
        //});

        $j('#PortalRegistrationCode').on('click', '#SendPortalRegistration', function(e) {
            e.preventDefault();

            // ajax portal registration
            var id = $j('#hfEditAssetId').val();
            var status = $j('#edit-asset-status');
            status.text('').hide(); // clear previous status
            toggleLoadingMessage(true, 'portal-registration');
            $j(this).prop('disabled', true);
            var dataPost = { assetId: id };
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/Services/GPSService.asmx/SendPortalRegistration'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    toggleLoadingMessage(false, 'portal-registration');
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            status.show().text('Portal registration successfully sent.').addClass('success');
                        }
                    }
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError('An error occurred while sending the portal registration.');
                    toggleLoadingMessage(false, 'portal-registration');
                }
            });
        });

        $j('#form-edit-asset').on('keyup', '#txtEditAssetUniqueId', function(e) {
            validateIMEI(this);
        });

        $j('#form-edit-asset').on('keyup', '#txtEditAssetIMEIVerificationCode', function(e) {
            validateIMEIVerificationCode(this);
        });

        $j('#form-edit-asset').on('change', '#ddlEditAssetAddressBookGroup,#ddlEditAssetCannedMessageGroup', function (e) {
            if (tracking.state.isAddingAsset)
                return;
            if (!tracking.options.enableShoutConfigOta)
                return;

            var asset = findAssetById($j('#hfEditAssetId').val());
            if (asset != null) {
                if (_.indexOf(tracking.devices.NAL, asset.DeviceId) !== -1) {
                    $j('#edit-asset-sync-ota').show();
                }
            }
        });

        $j('#form-edit-asset').on('change', '#ddlEditAssetDevice', function(e) {
            deviceDialogChange(this, null);
        });

        $('#form-edit-asset').on('change', '#ddlEditAssetDeviceManufacturer', function (e) {
            var mfr = $(this).val();
            // filter device list by manufacturer
            var isFiltered = (mfr == '');
            var devices = isFiltered ? _.filter(tracking.data.devices, function (item) { return item.IsActive === true }) : _.filter(tracking.data.devices, function (item) { return item.Manufacturer === mfr && item.IsActive === true });
            devices = devices.sort(function (a, b) { return a.Name.localeCompare(b.Name); }).sort(function(a,b) { return a.Manufacturer.localeCompare(b.Manufacturer); });
            //devices = _.chain(devices).sortBy('Name').sortBy('Manufacturer').value();
            var deviceList = document.getElementById('ddlEditAssetDevice');
            var priorValue = deviceList.value;
            $(deviceList).find('option').not('[value=\'\']').remove();

            _.each(devices, function (device) {
                var option = document.createElement('option');
                option.value = device.Id;
                option.textContent = isFiltered ? device.Manufacturer + ': ' + device.Name : device.Name;
                option.selected = devices.length === 1 || option.value === priorValue; // auto-select option if it is the only one
                deviceList.appendChild(option);
            });
            deviceDialogChange(deviceList, null);
        });

        $j('#edit-asset-dialog').on('click', '#RefreshAssetAlerts', function (e) {
            e.preventDefault();
            var assetId = $j('#hfEditAssetId').val();
            var asset = findAssetById(assetId);
            if (asset != null)
                loadAssetAlerts(asset);
        });

        $(document).on('click', '.dialog-titlebar .item-close', function (e) {
            var dialog = document.getElementById(this.getAttribute('data-for-dialog'));
            if (dialog.getAttribute('data-manual-close') !== 'false') {
                $(dialog).dialog('close');
            } else {
                if (dialog.manualCloseCallback !== undefined && dialog.manualCloseCallback !== null) {
                    dialog.manualCloseCallback();
                }
            }
        });

        $(document).on('click', '.dialog-titlebar .item-collapse', function (e) {
            var dialog = document.getElementById(this.getAttribute('data-for-dialog'));
            if (dialog.getAttribute('data-collapsed') === 'true') {
                // expand
                //dialog.style.display = 'block';
                dialog.setAttribute('data-collapsed', 'false');                
                this.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgPath('angle-up'));
            } else {
                //dialog.style.display = 'none';
                dialog.setAttribute('data-collapsed', 'true');
                this.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgPath('angle-down'));
            }
        });

        $j('body').on('click', 'button.delete-refuel', function(e) {
            e.preventDefault();

            // open confirm dialog
            $j('#hfDeleteFillupId').val($(this).data('refuelId'));
            $j('#hfDeleteFillupAssetId').val($('#hfRefuelAssetId').val());
            var deleteFillupModal = $('#delete-fillup-modal');
            deleteFillupModal.modal('show');
        });

        $j('#AssetAlerts').on('click', 'button.alert-remove', function (e) {
            e.preventDefault();

            // open confirm dialog
            var asset = findAssetById($j('#hfEditAssetId').val());
            $j('#hfDeleteAlertId').val($j(this).data('alertId'));
            $j('#hfDeleteAlertAssetId').val($j('#hfEditAssetId').val());
            var removeAlertModal = $('#delete-alert-modal');
            $('.modal-body', removeAlertModal).text(tracking.strings.REMOVE_FROM_ALERT_CONFIRM.replace('{0}', asset.Name).replace('{1}', $j(this).data('alertType')));
            removeAlertModal.modal('show');
        });

        var assetContainer = $j('#assets-all');

        $j('#geofence-dialog').on('click', 'span.highlight-segment', function (e) {
            e.preventDefault();
            // highlight the selected segment
            var seg = $j(this).data('segment');
            MapToolbar.setMapCenter(seg);
        });

        $('#panel-primary').on('click', '.leftnav-add-group', function (e) {
            e.preventDefault();
            if (this.classList.contains('disabled-feature')) {
                return;
            }
            openAssetGroupDialog(null);
            // by default, add currently selected assets to group if not all assets are selected
            _.each(tracking.data.assets, function (asset) {
                var isChecked = false;
                if (tracking.data.assets.length !== tracking.data.visible.assets.length) {
                    isChecked = tracking.data.visible.assets.indexOf(asset.Id) !== -1;
                }
                $('#edit-asset-group-assets-list input[value=' + asset.Id + ']').prop('checked', isChecked);
            });
        });

        $('#panel-primary').on('click', '.leftnav-add-place', function (e) {
            e.preventDefault();
            openPlaceDialog(null);
        });

        $('#panel-primary').on('click', '.leftnav-add-fence', function (e) {
            e.preventDefault();
            openGeofenceDialog(null);
        });

        $('#panel-primary').on('click', '.leftnav-add-asset', function (e) {
            e.preventDefault();
            openEditAssetDialog(null);
        });

        $('#panel-primary').on('click', '.leftnav-add-shared-view', function (e) {
            e.preventDefault();
            openSharedViewDialog(null);
        });

        $('#panel-primary').on('click', '.item-settings', itemSettingsClick);
        $('#panel-secondary').on('click', '.breadcrumb-item a', function (e) {
            // navigate back up the breadcrumb tree
            e.preventDefault();
            if (this.callback !== undefined) {
                this.callback();
            }
        });

        $(document).on('click', '#acknowledge-alert-dialog .item-settings,.infoheader .item-settings', itemSettingsClick);

        $('#panel-secondary-content-wrapper').on('hidden.bs.collapse', function () {
            // handle accordion resizes that affect the scrollbar calculations for some reason
            tracking.data.domNodes.simpleBars.secondary.recalculate();
        });
        $('#panel-secondary-content-wrapper').on('shown.bs.collapse', function () {
            // handle accordion resizes that affect the scrollbar calculations for some reason
            tracking.data.domNodes.simpleBars.secondary.recalculate();
        });
        $('#panel-secondary-content-wrapper').on('shown.bs.tab', function () {
            // handle accordion resizes that affect the scrollbar calculations for some reason
            tracking.data.domNodes.simpleBars.secondary.recalculate();
        });
        $('#panel-secondary-content-wrapper').on('hidden.bs.tab', function () {
            // handle accordion resizes that affect the scrollbar calculations for some reason
            tracking.data.domNodes.simpleBars.secondary.recalculate();
        });

        $('#panel-secondary-nav-tabs').on('click', '.nav-item', function (e) {
            e.preventDefault();

            var action = this.getAttribute('data-action');
            var type = tracking.data.domNodes.panels.secondary.getAttribute('data-item-type');
            var id = tracking.data.domNodes.panels.secondary.getAttribute('data-item-id');
            handleNotificationItemAction(action, type, id);
        });

        $('#panel-content-assets,#panel-content-journeys,#panel-content-shared-views').on('click', '.item-action', function (e) {
            e.preventDefault();
            if (this.classList.contains('disabled')) {
                return;
            }
            var rootNode = this.parentNode.parentNode.parentNode;
            var assetId = rootNode.getAttribute('data-asset-id');
            var tripId = rootNode.getAttribute('data-trip-id');
            var sharedViewId = rootNode.getAttribute('data-shared-view-id');
            var action = this.getAttribute('data-action');
            if (assetId !== null) {
                handleNotificationItemAction(action, 'assets', assetId);
            } else  if (tripId !== null) {
                handleNotificationItemAction(action, 'trips', tripId);
            } else if (sharedViewId !== null) {
                handleNotificationItemAction(action, 'shared-views', sharedViewId);
            }
        });
        $('#panel-secondary').on('click', '.item-action', function (e) {
            if (this.classList.contains('disabled')) {
                e.preventDefault();
                return;
            }
            var panel = tracking.data.domNodes.panels.secondary;
            var actionGroup = panel.getAttribute('data-group-for');
            var action = this.getAttribute('data-action');
            var id = panel.getAttribute('data-item-id')
            handleSecondaryPanelItemAction(action, actionGroup, id, e);
        });

        assetContainer.on('click', 'svg', function (e) {
            e.preventDefault();
            //console.log('svg click');
        });

        $('#panel-primary').on('click', '.item-toggle', function (e) {
            e.preventDefault();
            // determine if shown/hidden
            // attach/detach based on view mode and
            var itemNode = this.parentNode.parentNode;
            //toggleItemNotificationList(itemNode);
        });

        $j('#topbar-divider').on('click', '#panel-contract', function (e) { // todo: remove anon function
            e.preventDefault();
            hideSidePanel();
        });

        $j('#topbar-divider').on('click', '#panel-expand', function (e) {// todo: remove anon function
            e.preventDefault();
            showSidePanel();
        });

        $j('#controlarea').on('click', '#controls-close a', function(e) {
            $j('#controlarea').slideUp();
            e.preventDefault();
        });

        $j('#live-follow-status').on('click', '#live-follow-close', function(e) {
        	e.preventDefault();
			stopLiveFollowing();
        });

        $j('#live-follow-status').on('click', '#live-follow-assets', function(e) {
            e.preventDefault();
            if (tracking.state.liveFollow.asset == null) {
                return;
            }

            if (tracking.state.liveFollow.assets.length == 1) {
                openAssetLatestPosition(tracking.state.liveFollow.assets[0]);
            } else {
            	// pan/zoom to include all assets latest positions
            	var bounds = L.latLngBounds();
                for (var i = 0; i < tracking.state.liveFollow.assets.length; i++) {
                    var assetId = tracking.state.liveFollow.assets[i].Id;
                    if (tracking.data.live.latestPositionsByAssetId[assetId] !== undefined) {
                        bounds.extend(L.latLng(tracking.data.live.latestPositionsByAssetId[assetId].Position.Lat, tracking.data.live.latestPositionsByAssetId[assetId].Position.Lng));
                    }
                }
            	if (bounds.isValid()) {
                    tracking.map.fitBounds(bounds, { padding: [10, 10] });
            	}
            }
        });

        $j('#map-functions').on('click', '#map-reveal-all', function(e) {
            e.preventDefault();
            resizeApp(true);
            setMapBounds();
        });

        $('#map-functions').on('click', '#map-current-location-hide', function (e) {
            e.preventDefault();
            if (tracking.state.userLocation.marker === null) {
                return;
            }

            removeItemFromMap(tracking.state.userLocation.marker, tracking.data.mapLayers.other);
            if (tracking.state.userLocation.circle !== null) {
                removeItemFromMap(tracking.state.userLocation.circle, tracking.data.mapLayers.other);
            }
            if (tracking.state.userLocation.watch !== null) {
                navigator.geolocation.clearWatch(tracking.state.userLocation.watch);
            }
            tracking.state.userLocation.position = null;
            tracking.state.userLocation.marker = null;
            tracking.state.userLocation.circle = null;
            tracking.state.userLocation.watch = null;
            tracking.state.userLocation.zoomToPosition = false;
        });

        $('#map-functions').on('click', '#map-current-location-zoom', function (e) {
            e.preventDefault();
            zoomToUserLocation();
        });

        $('#map-functions').on('click', '#map-current-location', function (e) {
            e.preventDefault();
            e.stopPropagation();
            if (navigator.geolocation) {
                if (tracking.state.userLocation.marker != null) {
                    $('#map-current-location-toggle').dropdown('toggle');
                } else {
                    // request current location
                    tracking.state.userLocation.zoomToPosition = true;
                    if (tracking.state.userLocation.watch === null) {
                        tracking.state.userLocation.watch = navigator.geolocation.watchPosition(userLocationUpdated,
                            userLocationErrorManual,
                            tracking.state.userLocation.options);
                    } else {
                        navigator.geolocation.getCurrentPosition(userLocationUpdated,
                            userLocationErrorManual,
                            tracking.state.userLocation.options);
                    }
                }
            } else {
                $('#map-current-location').addClass('disabled');
                utility.handleWebServiceError(tracking.strings.CURRENT_LOCATION_UNSUPPORTED);
            }
        })

        $j('#map-functions').on('click', '#map-ruler', function(e) {
            e.preventDefault();
            if (tracking.data.ruler.isOpen) {
                closeRuler();
            } else {
                openRuler();
            }
        });

        $j('#map-functions').on('click', '#routing-start', function(e) {
            e.preventDefault();
            openRouting();
        });

        $j(tracking.data.domNodes.infoDialogs.mapItemInformation).on('click', 'button.event-information', function (e) {
            // handles view details button for an event
            e.preventDefault();

            var eventId = parseInt(this.getAttribute('data-event'));
            var location = $(tracking.data.domNodes.infoDialogs.mapItemInformation).data('location');

            for (var i = 0; i < location.Events.length; i++) {
                var evt = location.Events[i];
                if (evt.Id === eventId) {
                    setChildren(tracking.data.domNodes.infoDialogs.eventDetails, formattedTextToDiv(evt.Details));
                    $(tracking.data.domNodes.infoDialogs.eventDetails).dialog('open');
                    break;
                }
            }
        });

        $j('#map-functions').on('click', '#map-zoom-region', function(e) {
            e.preventDefault();
            if (!tracking.data.zoom.isActive) {
                enableDragZoom();
            } else {
                disableDragZoom();
            }
        });

        assetContainer.on('click', 'a.edit,a.edit-group,a.edit-fence,a.edit-place', function(e) {
            e.preventDefault();
        });

        // duplicate of callbacks for fence context menu -- todo: should merge/refactor
        $j(document).on('click', '#FenceReplay', function (e) {
            var id = $j(this).parent().parent().find('.item-settings').data('fenceId');
        	var fence = findFenceById(id);
        	var assetIds = findAssetIdsInGeofence(fence);
        	loadHistory(assetIds);
        });
        $j(document).on('click', '#FenceSendMessage', function (e) {
			console.log('fence send click');
            var id = $j(this).parent().parent().find('.item-settings').data('fenceId');
            var fence = findFenceById(id);
            openSendMessageDialog(null, null, fence);
        });
        $j(document).on('click', '#FenceGroup', function (e) {
            var id = $j(this).parent().parent().find('.item-settings').data('fenceId');
            var fence = findFenceById(id);
            openAssetGroupDialog(null);
            var assetIds = findAssetIdsInGeofence(fence);
            var assets = new Array();
            for (var i = 0; i < assetIds.length; i++) {
                var asset = findAssetById(assetIds[i]);
                if (asset == null) {
                    continue;
                }
                assets.push(asset);
                $('#edit-asset-group-assets-list input[value=' + asset.Id + ']').prop('checked', true);
            }
        });
        $j(document).on('click', '#FenceEdit', function (e) {
            var id = $j(this).parent().parent().find('.item-settings').data('fenceId');
            var fence = findFenceById(id);
            openGeofenceDialog(fence);
        });
        $j(document).on('click', '#FenceAlert', function (e) {
            var id = $j(this).parent().parent().find('.item-settings').data('fenceId');
            var fence = findFenceById(id);
            var assetIds = findAssetIdsInGeofence(fence);
            window.location = '/Alerts/CreateAlert?assetIds=' + assetIds.join() + '&fenceId=' + fence.Id;
        });
        $j(document).on('click', '#FenceReport', function (e) {
            var id = $j(this).parent().parent().find('.item-settings').data('fenceId');
            var fence = findFenceById(id);
            window.location = '/Reports/Location?fenceId=' + fence.Id;
        });
        $j(document).on('click', '#FenceDelete', function (e) {
            var id = $j(this).parent().parent().find('.item-settings').data('fenceId');
            var fence = findFenceById(id);
            //var dialog = $j('#delete-geofence-dialog');
            //$j('div', dialog[0]).text(tracking.strings.MSG_DELETE_FENCE_CONFIRM.replace('{0}', fence.Name));
            //$j('#hfDeleteFenceId').val(fence.Id);
            //$j(dialog).dialog('open');

            var deleteFenceModal = $('#delete-geofence-modal');
            $('.modal-body', deleteFenceModal).text(tracking.strings.MSG_DELETE_FENCE_CONFIRM.replace('{0}', fence.Name));
            $j('#hfDeleteFenceId').val(fence.Id);
            deleteFenceModal.modal('show');
        });

        // select this search result on the map via the info window to send a position
        $j('#search-position-results').on('click', 'a.highlight-position', function(e) {
            e.preventDefault();

            if (tracking.data.searchPositionResults == null) {
                return;
            }

            var numRes = $j(this).data('resultNumber');
            if (tracking.data.searchPositionResults[numRes] == null) {
                return;
            }

            var loc = tracking.data.searchPositionResults[numRes];
            var latLng = L.latLng(loc.address.lat, loc.address.lon);
            updateChosenLocation(latLng, tracking.state.mapClickHandlers.POSITION);
            tracking.map.panTo(latLng);
            $('#SendPositionName').val(loc.formatted_address);
        });

        $('#send-position-dialog').on('click', '#SendPositionCancel', function (e) {
            e.preventDefault();
            tracking.data.validation.sendPositionSend.resetForm();
            tracking.data.validation.sendPositionSend.currentForm.reset();
            removeItemFromMap(tracking.state.chosenLocations[tracking.state.mapClickHandlers.POSITION]);
            $('#send-position-search').addClass('is-visible');
            $('#form-send-position-send').removeClass('is-visible');
        });

        $('#send-position-dialog').on('click', '#SendPositionSend', function (e) {
            e.preventDefault();
            var isFormValid = $(tracking.data.validation.sendPositionSend.currentForm).valid();
            if (!isFormValid) {
                tracking.data.validation.sendPositionSend.focusInvalid();
                return;
            }

            var id = $('#hfPositionAssetId').val();
            var asset = findAssetById(id);
            if (asset === null) {
                return;
            }

            // submit position to pending
            var text = $('#SendPositionMessage');
            var name = $('#SendPositionName');
            var id = $('#hfPositionAssetId').val();
            var lat = $('#hfPositionLat').val();
            var lng = $('#hfPositionLng').val();
            var isServerSide = ($('#send-position-dialog').data('isServerSide') === true);

            var data = {
                assetId: id,
                name: name.val(),
                message: text.val(),
                lat: lat,
                lng: lng,
                notifyStatus: true,
                notifyETA: true,
                isServerSide: isServerSide
            };

            var status = document.getElementById('send-position-status');
            $.when(handleAjaxFormSubmission('SendWaypointToAsset', data, this, status, tracking.strings.SEND_WAYPOINT_SUCCESS, tracking.strings.SEND_WAYPOINT_ERROR))
                .done(function () {
                    tracking.data.validation.sendPositionSend.resetForm();
                    tracking.data.validation.sendPositionSend.currentForm.reset();
                    removeItemFromMap(tracking.state.chosenLocations[tracking.state.mapClickHandlers.POSITION]);
                    $('#send-position-search').addClass('is-visible');
                    $('#form-send-position-send').removeClass('is-visible');
                });
            //toggleLoadingMessage(true, 'send-position');

            //$j.ajax({
            //    type: 'POST',
            //    url: wrapUrl('/services/GPSService.asmx/SendWaypointToAsset'),
            //    data: JSON.stringify(dataPost),
            //    contentType: 'application/json; charset=utf-8',
            //    dataType: 'json',
            //    success: function (msg) {
            //        if (msg.d) {
            //            toggleLoadingMessage(false, 'send-position');
            //            if (tracking.state.openWindow != null) {
            //                var status = $j('#send-position-status');
            //                status.text(tracking.strings.SEND_WAYPOINT_SUCCESS).addClass('alert-success').show();
            //            }
            //        }
            //    },
            //    error: function (xhr, status, error) {
            //        utility.handleWebServiceError(tracking.strings.SEND_WAYPOINT_ERROR);
            //        toggleLoadingMessage(false, 'send-position');
            //    }
            //});
        });

        $j('#send-position-dialog').on('click', '#btnSendPositionPlace', function (e) {
            e.preventDefault();

            if (!tracking.state.isChoosingPosition) {
                return;
            }

            var isFormValid = $(tracking.data.validation.sendPositionShow.currentForm).valid();
            if (!isFormValid) {
                tracking.data.validation.sendPositionShow.focusInvalid();
                return;
            }

            var placeId = $j('#ddlSendPositionPlace').val();
            if (placeId == '') {
                return;
            }
            var place = findPlaceById(placeId);
            if (place == null) {
                return;
            }

            var latLng = L.latLng(place.Location.Lat, place.Location.Lng);
            updateChosenLocation(latLng, tracking.state.mapClickHandlers.POSITION);
            tracking.map.panTo(latLng);
            $('#SendPositionName').val(place.Name);
            //$('#SendPositionLatLng').text(latLng.lat.toFixed(6) + ', ' + latLng.lng.toFixed(6));
            //$('#form-send-position-search').removeClass('is-visible');
            //$('#form-send-position-send').addClass('is-visible');
        });

        $j('#send-position-dialog').on('click', '#btnSendPositionLatLng', function(e) {
            e.preventDefault();

            if (!tracking.state.isChoosingPosition) {
                return;
            }

            var isFormValid = $(tracking.data.validation.sendPositionFind.currentForm).valid();
            if (!isFormValid) {
                tracking.data.validation.sendPositionFind.focusInvalid();
                return;
            }

            var lat = $('#txtSendPositionLat').val();
            var lng = $('#txtSendPositionLng').val();

            var latLng = L.latLng(lat, lng);
            if (isNaN(latLng.lat) || isNaN(latLng.lng)) {
                return;
            }

            updateChosenLocation(latLng, tracking.state.mapClickHandlers.POSITION);
            tracking.map.panTo(latLng);
        });

        $j('#send-position-dialog').on('click', '#btnPositionSearch', function(e) {
            e.preventDefault();

            var btn = this;
            if (!tracking.options.enableGeocoding) {
                return;
            }

            var isFormValid = $(tracking.data.validation.sendPositionSearch.currentForm).valid();
            if (!isFormValid) {
                tracking.data.validation.sendPositionSearch.focusInvalid();
                return;
            }

            var search = document.getElementById('txtPositionSearch').value;
            var loading = document.getElementById('search-position-loading');
            loading.classList.add('is-visible');

            btn.disabled = true;

            var status = $('#search-position-status').hide();

            var searchResults = document.getElementById('search-position-results');
            searchResults.classList.remove('is-visible');
            var noResults = document.getElementById('search-position-no-results');
            noResults.classList.remove('is-visible');

            var resultList = document.getElementById('search-position-results-list');
            setChildren(resultList, []);

            tracking.data.searchPositionResults = null;

            // search for address via external service
            tracking.addressSearch(search, function (success, resultData) {
                btn.disabled = false;
                loading.classList.remove('is-visible');
                searchResults.classList.add('is-visible');
                if (!success) {
                    status.text(tracking.strings.MSG_SEARCH_POSITION_ERROR).show();
                } else {
                    if (resultData.length == 0) {
                        noResults.classList.add('is-visible');
                    } else {
                        tracking.data.searchPositionResults = resultData;
                        var resultItems = [];
                        for (var i = 0; i < resultData.length; i++) {
                            resultItems.push(el('li', el('a#HighlightPosition' + i + '.highlight-position', { href: '#', dataset: { resultNumber: i } }, resultData[i].formatted_address)));
                        }
                        setChildren(resultList, resultItems);
                    }

                }
            });
        });

        // from/to filters
        $j('#txtDateFrom,#RunReportTo').datetimepicker({
            dateFormat: tracking.user.shortDateFormat,
            minDate: tracking.options.dateRangeMin === null ? null : new Date(tracking.options.dateRangeMin),
            maxDate: tracking.options.dateRangeMax === null ? null : new Date(tracking.options.dateRangeMax)
        });
        $j('#txtDateTo,#RunReportFrom').datetimepicker({
            dateFormat: tracking.user.shortDateFormat,
            hour: 23,
            minute: 59,
            minDate: tracking.options.dateRangeMin === null ? null : new Date(tracking.options.dateRangeMin),
            maxDate: tracking.options.dateRangeMax === null ? null : new Date(tracking.options.dateRangeMax)
        });

        $('#delete-waypoint-modal').on('click', '#delete-waypoint-button', function (e) {
            var modal = $('#delete-waypoint-modal');

            var btn = this;
            btn.disabled = true;
            var id = $j('#hfDeleteWaypointId').val();
            var assetId = $j('#hfDeleteWaypointAssetId').val();
            toggleLoadingMessage(true, 'delete-waypoint');
            var dataPost = {
                id: id,
                assetId: assetId
            };
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/WaypointDelete'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        modal.modal('hide');
                        if (result.Success == true) {
                            var data = $j('#WaypointHistory').DataTable();
                            if (data != null) {
                                var rows = data.data();
                                for (var i = 0; i < rows.length; i++) {
                                    if (rows[i][9] == id) {
                                        data.row(i).remove();
                                    }
                                }
                                data.draw();
                            }
                        }
                    }
                    btn.disabled = false;
                    toggleLoadingMessage(false, 'delete-waypoint');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_DELETE_WAYPOINT_ERROR);
                    toggleLoadingMessage(false, 'delete-waypoint');
                    btn.disabled = false;
                }
            });
        });

        $('#delete-fillup-modal').on('click', '#delete-fillup-button', function (e) {
            // delete the fillup and reload the history
            var id = $j('#hfDeleteFillupId').val();
            var assetId = $j('#hfDeleteFillupAssetId').val();
            var data = {
                id: id,
                assetId: assetId
            };
            var status = document.getElementById('add-refuel-status');
            $.when(handleAjaxFormSubmission('DeleteFillup', data, this, status, tracking.strings.MSG_DELETE_FILLUP_SUCCESS, tracking.strings.MSG_DELETE_FILLUP_ERROR))
                .done(function () {
                    $('#delete-fillup-modal').modal('hide');
                    var asset = findAssetById(assetId);
                    loadFillupHistory(asset);
                });
        });

        $('#delete-message-modal').on('click', '#delete-message-button', function (e) {
            var modal = $('#delete-message-modal');
            var btn = this;
            btn.disabled = true;

            var id = modal.data('messageId');
            var assetId = modal.data('assetId');

            toggleLoadingMessage(true, 'delete-message');
            var dataPost = {
                id: id,
                assetId: assetId
            };
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/DeleteMessage'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        modal.modal('hide');
                        btn.disabled = false;
                        if (result.Success == true) {
                            var asset = findAssetById(assetId);
                            loadMessageHistory(asset);
                        }
                    }
                    toggleLoadingMessage(false, 'delete-message');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_DELETE_MESSAGE_ERROR);
                    toggleLoadingMessage(false, 'delete-message');
                    btn.disabled = false;
                }
            });
        });

        $('#delete-group-modal').on('click', '#delete-group-button', function (e) {
            var modal = $('#delete-group-modal');
            var btn = this;
            btn.disabled = true;
            var id = $j('#hfDeleteGroupId').val();
            toggleLoadingMessage(true, 'delete-group');
            var dataPost = {
                id: id
            };
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/DeleteAssetGroup'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            var group = findGroupById(id);
                            deleteAssetGroup(group);
                            closeSecondaryPanel();
                        }
                    }
                    modal.modal('hide');
                    btn.disabled = false;
                    toggleLoadingMessage(false, 'delete-group');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_DELETE_GROUP_ERROR);
                    toggleLoadingMessage(false, 'delete-group');
                    btn.disabled = false;
                }
            });

        });

        $('#delete-shared-view-modal').on('click', '#delete-shared-view-button', function (e) {
            var modal = $('#delete-shared-view-modal');
            var btn = this;
            btn.disabled = true;
            var id = $('#hfDeleteSharedViewId').val();
            toggleLoadingMessage(true, 'delete-shared-view');
            var dataPost = {
                id: id
            };
            $.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/DeleteSharedView'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            var sharedView = findSharedViewById(id);
                            deleteSharedView(sharedView);
                        }
                    }
                    modal.modal('hide');
                    btn.disabled = false;
                    toggleLoadingMessage(false, 'delete-shared-view');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_DELETE_SHARED_VIEW_ERROR);
                    toggleLoadingMessage(false, 'delete-shared-view');
                    btn.disabled = false;
                }
            });
        });

        $('#delete-place-modal').on('click', '#delete-place-button', function (e) {
            var modal = $('#delete-place-modal');
            var btn = this;
            btn.disabled = true;
            var id = $j('#hfDeletePlaceId').val();
            toggleLoadingMessage(true, 'delete-place');
            var dataPost = {
                id: id
            };
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/DeletePlace'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            var place = findPlaceById(id);
                            deletePlace(place);
                        }
                    }
                    modal.modal('hide');
                    btn.disabled = false;
                    toggleLoadingMessage(false, 'delete-place');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_DELETE_PLACE_ERROR);
                    toggleLoadingMessage(false, 'delete-place');
                    btn.disabled = false;
                }
            });
        });

        $('#delete-trip-modal').on('click', '#delete-trip-button', function (e) {
            var modal = $('#delete-trip-modal');
            var btn = this;
            btn.disabled = true;
            var id = $j('#hfDeleteTripId').val();
            toggleLoadingMessage(true, 'delete-trip');
            var dataPost = {
                id: id
            };
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/DeleteTrip'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            var trip = findTripById(id);
                            deleteTrip(trip);
                        }
                    }
                    modal.modal('hide');
                    btn.disabled = false;
                    toggleLoadingMessage(false, 'delete-trip');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_DELETE_TRIP_ERROR);
                    toggleLoadingMessage(false, 'delete-trip');
                    btn.disabled = false;
                }
            });
        });

        $('#delete-journey-modal').on('click', '#delete-journey-button', function (e) {
            var modal = $('#delete-journey-modal');
            var btn = this;
            btn.disabled = true;
            var id = $j('#hfDeleteJourneyId').val();
            toggleLoadingMessage(true, 'delete-journey');
            var dataPost = {
                id: id
            };
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/DeleteJourney'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            var journey = findJourneyById(id);
                            deleteJourney(journey);
                        }
                    }
                    modal.modal('hide');
                    btn.disabled = false;
                    toggleLoadingMessage(false, 'delete-journey');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_DELETE_JOURNEY_ERROR);
                    toggleLoadingMessage(false, 'delete-journey');
                    btn.disabled = false;
                }
            });
        });

        $(document).on('click', '#accordion-query-idp-diagnostics-head', function () {
            var table = $('#IDPSleepSchedules').dataTable();
            table.fnAdjustColumnSizing();
        });

        $('#delete-geofence-document-modal').on('click', '#delete-geofence-button', function (e) {
            var modal = $('#delete-geofence-document-modal');
            var btn = this;
            btn.disabled = true;
            var id = $j('#hfDeleteDocumentId').val();
            var fenceId = $j('#hfDeleteFenceId').val();
            toggleLoadingMessage(true, 'delete-document');
            var dataPost = {
                id: id,
                fenceId: fenceId
            };
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/DeleteGeofenceDocument'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            updateFence(result.Fence);
                            populateGeofenceDocuments(result.Fence);
                        }
                    }
                    modal.modal('hide');
                    btn.disabled = false;
                    toggleLoadingMessage(false, 'delete-document');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_DELETE_DOCUMENT_ERROR);
                    toggleLoadingMessage(false, 'delete-document');
                    btn.disabled = false;
                }
            });
        });

        $('#delete-geofence-modal').on('click', '#delete-geofence-button', function (e) {
            e.preventDefault();
            var modal = $('#delete-geofence-modal');
            var btn = this;
            btn.disabled = true;
            // ajax post for delete
            var id = $j('#hfDeleteFenceId').val();
            toggleLoadingMessage(true, 'delete-fence');
            var dataPost = {
                id: id
            };
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/DeleteGeofence'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            var fence = findFenceById(id);
                            deleteFence(fence);
                        }
                    }
                    modal.modal('hide');
                    btn.disabled = false;
                    toggleLoadingMessage(false, 'delete-fence');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_DELETE_GEOFENCE_ERROR);
                    toggleLoadingMessage(false, 'delete-fence');
                    btn.disabled = false;
                }
            });
        });

        $('#delete-asset-modal').on('click', '#delete-asset-button', function (e) {
            e.preventDefault();
            var modal = $('#delete-asset-modal');
            var btn = this;
            btn.disabled = true;
            var id = $j('#hfDeleteAssetId').val();
            toggleLoadingMessage(true, 'delete-asset');
            var dataPost = {
                id: id
            };
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/DeleteAsset'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            var asset = findAssetById(id);
                            deleteAsset(asset);
                        }
                    }
                    modal.modal('hide');
                    btn.disabled = false;
                    toggleLoadingMessage(false, 'delete-asset');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_DELETE_ASSET_ERROR);
                    toggleLoadingMessage(false, 'delete-asset');
                    btn.disabled = false;
                }
            });
        });

        $j('input.datepick').datepicker();

        $('#delete-alert-modal').on('click', '#delete-alert-button', function (e) {
            var id = $j('#hfDeleteAlertId').val();
            var btn = this;
            btn.disabled = true;
            var assetId = $j('#hfDeleteAlertAssetId').val();
            var dialog = $j(this);
            toggleLoadingMessage(true, 'delete-alert');
            var dataPost = {
                alertId: id,
                assetId: assetId
            };
            var status = document.getElementById('edit-asset-status');
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/RemoveAssetFromAlert'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            formShowSuccessMessage(status, tracking.strings.MSG_REMOVE_FROM_ALERT_SUCCESS);
                            var asset = findAssetById(assetId);
                            loadAssetAlerts(asset);
                        } else {
                            formShowErrorMessage(status, tracking.strings.MSG_REMOVE_FROM_ALERT_ERROR);
                            if (result.ErrorMessage != null && result.ErrorMessage != '') {
                                formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                            }
                        }
                        $('#delete-alert-modal').modal('hide');
                    }
                    btn.disabled = false;
                    toggleLoadingMessage(false, 'delete-alert');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_REMOVE_FROM_ALERT_ERROR);
                    toggleLoadingMessage(false, 'delete-alert');
                    btn.disabled = false;
                }
            });
        });

        // add geofence
        var geofenceButtons = [{
            buttonType: 'primary',
            text: tracking.strings.CREATE_GEOFENCE,
            id: 'EditGeofenceButton',
            click: function () {
                var isFormValid = $(tracking.data.validation.geofence.currentForm).valid();
                if (!isFormValid) {
                    tracking.data.validation.geofence.focusInvalid();
                    return;
                }

                // must have at least one segment/shape
                // todo: return custom validation message if not

                var dialog = tracking.data.domNodes.dialogs.geofence;
                var name = $j('#txtGeofenceName', dialog).val();
                var color = $j('#txtGeofenceColor', dialog).val();
                var id = $j('#hfEditFenceId', dialog).val();
                var description = $j('#txtGeofenceDescription').val();
                var userIds = new Array();
                $j('input[name=EditFenceUserIds]:checked', dialog).each(function (index, elem) {
                    userIds.push($j(this).val());
                });
                var removePhoto = $('#edit-geofence-remove-photo').val() === 'true';

                var segs = $j('#add-geofence-sections').find('span.highlight-segment');
                var status = document.getElementById('add-geofence-status');
                var segments = [];
                var circles = [], shapes = [], lines = [];
                segs.each(function (index, elem) {
                    var segment = MapToolbar.getSegmentInfo($j(elem).data('segment'));
                    var segmentId = $j(elem).data('segmentid');
                    if (segmentId != 'undefined') {
                        segment.Id = segmentId;
                    }

                    if (segment.Shape == 'circle') {
                        circles.push(segment);
                    } else if (segment.Shape == 'shape') {
                        shapes.push(segment);
                    } else if (segment.Shape == 'line') {
                        lines.push(segment);
                    }
                    segments.push(segment);
                });

                if (color == '') {
                    color = '#fc6355';
                }

                if (segments.length == 0) {
                    formShowErrorMessage(status, tracking.strings.MSG_ADD_GEOFENCE_NO_SEGMENTS);
                    return;
                }

                var attributes = getAttributesForType(1, 'EditGeofenceAttribute');
                var contactId = $j('#ddlFenceContactId').val();
                var isServerSideOnly = $j('#chkGeofenceServerSide').prop('checked');
                var data = {
                    Id: id,
                    Name: name,
                    Description: description,
                    Color: color,
                    Circles: circles,
                    Shapes: shapes,
                    Lines: lines,
                    UserIds: userIds,
                    Attributes: attributes,
                    ContactId: contactId,
                    IsServerSideOnly: isServerSideOnly,
                    RemovePhoto: removePhoto
                };
                var url = '/services/GPSService.asmx/AddGeofence';
                if (tracking.state.isEditingGeofence) {
                    url = '/services/GPSService.asmx/UpdateGeofence';
                } else {
                    delete data.Id;
                    delete data.RemovePhoto;
                }
                var btn = this;
                btn.disabled = true;
                toggleLoadingMessage(true, 'add-geofence');
                $j.ajax({
                    type: 'POST',
                    url: wrapUrl(url),
                    data: '{ geofence: ' + JSON.stringify(data) + '}',
                    contentType: 'application/json; charset=utf-8',
                    dataType: 'json',
                    success: function (msg) {
                        btn.disabled = false;
                        var result = msg.d;
                        if (result) {
                            if (result.Success == true) {
                                if (tracking.state.isEditingGeofence) {
                                    formShowSuccessMessage(status, tracking.strings.MSG_EDIT_GEOFENCE_SUCCESS);

                                    updateFence(result.Fence);
                                    var previousUserIds = findFenceUsersByFenceId(id);
                                    if (previousUserIds != null) {
                                        if (previousUserIds.toString() != userIds.toString()) {
                                            for (var i = 0; i < previousUserIds.length; i++) {
                                                value = previousUserIds[i];
                                                if (_.indexOf(userIds, value) == -1) {
                                                    removeUserFromFence(value, id);
                                                    i--;
                                                }
                                            }

                                            $j.each(userIds, function (index, value) {
                                                if (_.indexOf(previousUserIds, value) == -1) {
                                                    addUserToFence(value, id);
                                                }
                                            });
                                        }
                                    }

                                    if (removePhoto) {
                                        var fence = findFenceById(result.Fence.Id);
                                        if (fence !== null) {
                                            fence.PhotoType = null;
                                        }
                                    }
                                } else {
                                    processNewFence(result.Fence);
                                    if (tracking.data.fenceUsers !== null) {
                                        tracking.data.fenceUsers.push({ FenceId: result.Fence.Id, UserIds: [] });
                                    }
                                    $j.each(userIds, function (index, value) {
                                        addUserToFence(value, result.Fence.Id);
                                    });

                                    // quick link to add alert
                                    var assetIds = findAssetIdsInGeofence(findFenceById(result.Fence.Id));
                                    var addAlertLink = '/Alerts/CreateAlert?assetIds=' + assetIds.join() + '&fenceId=' + result.Fence.Id;
                                    setChildren(status, [
                                        text(status.textContent + ' '),
                                        el('button.add-alert.btn.btn-primary.btn-small.ml-auto', { type: 'button', dataset: { action: addAlertLink } }, [
                                            svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#exclamation-triangle' } })),
                                            text(' '),
                                            el('span', tracking.strings.ADD_ALERT)
                                        ])
                                    ]);

                                    cleanupFenceEditing();
                                }

                                var photo = new FormData();

                                uploadFile('fileEditGeofencePhoto',
	                                '/Home/UploadGeofencePhoto',
	                                { fenceId: result.Fence.Id },
	                                function (res) {
	                                    if ((res == '') || (res == 'empty')) { // plugin does not handle empty result properly
	                                        return;
                                        }
	                                    res = JSON.parse(res);

	                                    if (res.success == true) {
	                                        if ((res.type != null) && (res.type != '')) {
	                                            var fence = findFenceById(result.Fence.Id);
	                                            fence.PhotoType = res.type;
                                                if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
                                                    && tracking.data.domNodes.panels.secondary.getAttribute('data-item-id') === fence.Id) {
                                                    $j('#edit-geofence-photo').find('img').attr('src', '/uploads/images/fences/' + fence.Id + '_thumb.' + res.type + '?rnd=' + new Date().getTime());
                                                    $j('#edit-geofence-photo a').attr('href', '/uploads/images/fences/' + fence.Id + '.' + res.type);
                                                    $j('#edit-geofence-photo').show();
                                                }
                                                $('#fileEditGeofencePhoto').val('').next('.custom-file-label').text(tracking.strings.NO_FILE_SELECTED);
	                                        }
	                                    }
	                                }
                                );

                                if (!tracking.state.isEditingGeofence) {
                                    openGeofenceDialog(null);
                                    formShowSuccessMessage(status, tracking.strings.MSG_ADD_GEOFENCE_SUCCESS);
                                }
                                //dialog.dialog('close');
                            } else {
                                // show error
                                var stat = tracking.strings.MSG_ADD_GEOFENCE_ERROR;
                                if (tracking.state.isEditingGeofence) {
                                    stat = tracking.strings.MSG_EDIT_GEOFENCE_ERROR;
                                }
                                formShowErrorMessage(status, stat);
                                if ((result.ErrorMessage != null) && (result.ErrorMessage != '')) {
                                    formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                                }
                            }
                        }

                        toggleLoadingMessage(false, 'add-geofence');
                    },
                    error: function (xhr, status, error) {
                        btn.disabled = false;
                        if (tracking.state.isEditingGeofence) {
                            utility.handleWebServiceError(tracking.strings.MSG_EDIT_GEOFENCE_ERROR);
                        } else {
                            utility.handleWebServiceError(tracking.strings.MSG_ADD_GEOFENCE_ERROR);
                        }
                        toggleLoadingMessage(false, 'add-geofence');
                    }
                });
            }
        }, {
                buttonType: 'secondary',
                icons: { primary: 'ui-icon-close' },
                text: tracking.strings.CANCEL,
                click: function () {
                    if (tracking.state.isEditingGeofence) {
                        document.getElementById('panel-dialog-back').click();
                    } else {
                        closeSecondaryPanel();
                    }
                }
            }];

        tracking.data.domNodes.dialogs.geofence = document.getElementById('geofence-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.geofence, geofenceButtons);
        $(tracking.data.domNodes.dialogs.geofence).on('click', 'a.remove-feature', function (e) {
            e.preventDefault();
            MapToolbar.removeFeature(this.dataset.segment);
        });

        $(tracking.data.domNodes.dialogs.geofence).on('click', 'div.tool', function (e) {
            e.preventDefault();
            MapToolbar.initFeature(this.dataset.tool);
        });

        $j('#geofence-documents').on('click', 'button.delete', function (e) {
            e.preventDefault();
            var button = $j(this);
            var fence = findFenceById(button.data('fenceId'));
            var deleteFenceDocumentModal = $('#delete-geofence-document-modal');
            var docId = button.data('docId');
            if (fence.Documents != null) {
                for (var i = 0; i < fence.Documents.length; i++) {
                    var doc = fence.Documents[i];
                    if (doc.Id === docId) {
                        $('.modal-body', deleteFenceDocumentModal).text(tracking.strings.MSG_DELETE_DOCUMENT_CONFIRM.replace('{0}', doc.Name));
                        break;
                    }
                }
            }

            $j('#hfDeleteFenceId').val(fence.Id);
            $j('#hfDeleteDocumentId').val(docId);
            deleteFenceModal.modal('show');
        });
        $(tracking.data.domNodes.dialogs.geofence).on('click', '#RemoveGeofencePhoto', function (e) {
            e.preventDefault();
            $(this).next().val('true');
            $(this).parent().parent().hide();
        });
        $j(tracking.data.domNodes.dialogs.geofence).on('click', '#AddFenceDocument', function (e) {
            e.preventDefault();
            var button = $j(this)[0];
            var dialog = $j(tracking.data.domNodes.dialogs.geofence);
            var status = $j('#add-document-status').text('').hide();
            var startDate = $j('#txtFenceDocumentStartDate').val();
            var endDate = $j('#txtFenceDocumentExpiryDate').val();
            var name = $j('#txtFenceDocumentName').val();
            var fence = findFenceById(dialog.data('fenceId'));
            var data = {
                FenceId: fence.Id,
                StartDate: startDate,
                EndDate: endDate,
                Name: name
            };
            button.disabled = true;
            toggleLoadingMessage(true, 'add-geofence-document');
            uploadFile('FileFenceDocument',
                '/Home/AddDocumentToFence',
                    data,
                    function (res) {
                        console.log(res);
                        button.disabled = false;
                        toggleLoadingMessage(false, 'add-geofence-document');
                        if ((res == '') || (res == 'empty')) // plugin does not handle empty result properly (html)
                            return;
                        res = JSON.parse(res);

                        if (res.Success == true) {
                            updateFence(res.Fence);
                            populateGeofenceDocuments(res.Fence);
                            $j('#edit-geofence-documents input').val('');
                        } else {
                            status.text(res.ErrorMessage).addClass('error').show();
                        }
                    }
            );
        });

        var routeAsGeofenceButton = {
        	id: 'RouteGeofenceCreate',
        	text: tracking.strings.ADD_GEOFENCE,
        	disabled: true,
        	click: function () {
				// original dialog
        		var dialog = $j(this);
                if (tracking.options.enabledFeatures.indexOf('UI_GEOFENCE_FROM_ROUTE') === -1) {
                    return;
                }
        		var btn = $j('#RouteGeofenceCreate').prop('disabled', true);
        		var latlngs = [];
        		if (tracking.data.routeLine != null) {
        			tracking.data.routeLine.getLatLngs().forEach(function (elem, index) {
        				latlngs.push({ Lat: elem.lat, Lng: elem.lng });
        			});
        		}
        		var data = {
        			encodedPolyline: tracking.data.routeLineEncoded,
					encodedPolylines: tracking.data.routeLinesEncoded,
        			latlngs: latlngs,
					buffer: $j('#RouteGeofenceBuffer').slider('value')
        		};
        		toggleLoadingMessage(false, 'polyline-geofence');
        		// submit encoded polyline to server, have it create and return a buffered polygon to populate add geofence
        		$j.ajax({
        			type: 'POST',
                    url: wrapUrl('/services/GPSService.asmx/BufferPolyline'),
        			data: JSON.stringify(data),
        			contentType: 'application/json; charset=utf-8',
        			dataType: 'json',
        			success: function (msg) {
        				var result = msg.d;
        				if (result) {
        					if (result.Success != true) {
        						utility.handleWebServiceError(tracking.strings.MSG_ADD_GEOFENCE_ERROR);
        					} else {
        						dialog.dialog('close');
        						dialog.data('dialog').dialog('close'); // close original dialog
        						var points = result.Points;

        						var fence = {
        							Id: 1,
        							Segments: [{ Points: points, Type: 1 }]
        						};

        						openGeofenceDialog(null);
        						editFenceSegments(fence);
        						btn.prop('disabled', true);
        					}
        				}
        				toggleLoadingMessage(false, 'polyline-geofence');
        			},
        			error: function (xhr, status, error) {
        				utility.handleWebServiceError(tracking.strings.MSG_ADD_GEOFENCE_ERROR);
        				toggleLoadingMessage(false, 'polyline-geofence');
        				btn.prop('disabled', false);
        			}
        		});
        	}
        };

        tracking.data.domNodes.dialogs.routing = document.getElementById('routing-dialog');
        $(tracking.data.domNodes.dialogs.routing).on('click', 'button.routing-map', function (e) {
            e.preventDefault();

            // enable clicking the map to add a location
            if (tracking.data.routing.isUsingMap) {
                this.classList.remove('active');
                tracking.data.routing.isUsingMap = false;
                stopChoosingMapLocation(tracking.state.mapClickHandlers.ROUTING);
                $j('button.routing-map').prop('disabled', false);
                tracking.data.routing.mapClickDestination = 'waypoint';
            } else {
                tracking.data.routing.isUsingMap = true;
                startChoosingMapLocation(tracking.state.mapClickHandlers.ROUTING);
                tracking.data.routing.mapClickDestination = $(this.getAttribute('data-target'));
                $('button.routing-map').not(this).prop('disabled', true).removeClass('active');
                this.classList.add('active');
            }
        });
        $(tracking.data.domNodes.dialogs.routing).on('click', 'svg.remove', function (e) {
            e.preventDefault();
            var destinationList = document.getElementById('routing-destinations');
            destinationList.removeChild(this.parentNode);
            routingCheckForPendingDestination();
        });
        $(tracking.data.domNodes.dialogs.routing).on('click', '#RoutingCancel', function (e) {
            e.preventDefault();
            closeSecondaryPanel();
        });
        //$(tracking.data.domNodes.dialogs.routing).

        function routingAddressEntry(e) {
            console.log('keyup!');
            // cancel the current route (if any)

            // cancel previous geocoding request (if any)

            // geocode the current text and populate the address results

            if (!tracking.options.enableGeocoding) {
                return;
            }
            var destination = this;
            var search = destination.value;
            $(this).removeData('location');

            if (tracking.state.routingSearch !== undefined && tracking.state.routingSearch !== null) {
                tracking.state.routingSearch.abort();
            }

            if (search === '') {
                return;
            }

            var results = document.getElementById('routing-destination-results');
            results.classList.add('is-visible');

            var loading = document.getElementById('routing-address-loading');
            loading.classList.add('is-visible');

            var status = $('#routing-address-status').hide();

            var searchResults = document.getElementById('routing-address-results');
            searchResults.classList.remove('is-visible');
            var noResults = document.getElementById('routing-address-no-results');
            noResults.classList.remove('is-visible');

            var resultList = document.getElementById('routing-address-results-list');
            setChildren(resultList, []);

            tracking.data.routingAddressResults = null;

            tracking.data.routing.searchDestination = $(destination);

            // search for address via external service
            tracking.state.routingSearch = tracking.addressSearch(search, function (success, resultData) {
                loading.classList.remove('is-visible');
                searchResults.classList.add('is-visible');
                if (!success) {
                    status.text(tracking.strings.MSG_SEARCH_POSITION_ERROR).show();
                } else {
                    if (resultData.length == 0) {
                        noResults.classList.add('is-visible');
                    } else {
                        tracking.data.routing.searchResults = resultData;
                        var RESULT_LIMIT = 5;
                        var numResults = resultData.length > RESULT_LIMIT ? RESULT_LIMIT : resultData.length;
                        var resultItems = [];
                        for (var i = 0; i < numResults; i++) {
                            resultItems.push(el('li', el('a#HighlightRouteLocation' + i + '.highlight-route-location', { dataset: { resultNumber: i } }, [
                                svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#map-marker-alt' } })),
                                text(' ' + resultData[i].formatted_address)
                            ])));
                        }
                        setChildren(resultList, resultItems);
                    }
                }
            });
        }


        $('#routing-address-results-list').on('click', 'a.highlight-route-location', function (e) {
            e.preventDefault;

            if (tracking.data.routing.searchResults == null) {
                return;
            }

            var numRes = $j(this).data('resultNumber');
            if (tracking.data.routing.searchResults[numRes] === undefined || tracking.data.routing.searchResults[numRes] === null) {
                return;
            }

            var loc = tracking.data.routing.searchResults[numRes];
            var latLng = L.latLng(loc.address.lat, loc.address.lon);

            addPointToRoutingDestination(tracking.data.routing.searchDestination, latLng);
            tracking.data.routing.searchDestination.val(loc.formatted_address);
            routingCheckForPendingDestination();

            // hide search results
            var results = document.getElementById('routing-destination-results');
            results.classList.remove('is-visible');

            // calculate route (if valid)
        });

        function routingCalculateRoute() {
            if (tracking.options.enabledFeatures.indexOf('GET_ROUTE') === -1) {
                return;
            }
            var isFormValid = $(tracking.data.validation.routing.currentForm).valid();
            if (!isFormValid) {
                tracking.data.validation.routing.focusInvalid();
                return;
            }

            var dialog = tracking.data.domNodes.dialogs.routing;
            var destinations = $('.routing-destination input', dialog);
            var points = [];

            for (var i = 0; i < destinations.length; i++) {
                var $item = $(destinations[i]);
                var location = $item.data('location');
                if (location !== undefined && location !== null) {
                    var waypoint = new L.Routing.Waypoint();
                    waypoint.latLng = location.getLatLng();
                    waypoint.options = { allowUTurn: false };
                    points.push(waypoint);
                } else if ($item.val() !== '') {
                    // address search may not have completed
                    // how to determine if a user entered a lat/lng instead here?
                    // this doesn't work, latLng is required
                    //var waypoint = new L.Routing.Waypoint();
                    //waypoint.name = $item.val();
                    //points.push(waypoint);
                }
            }

            if (points.length < 2) {
                return;
            }

            //tracking.directionsService.getPlan().setWaypoints([]);
            //tracking.directionsService.remove();

            //tracking.directionsService.remove();
            //tracking.directionsService.control = tracking.directionsService.onAdd(tracking.map);
            tracking.directionsService
                //.on('routingstart', routesLoading)
                .on('routesfound', routesFound)
                .on('routingerror', routesError)
                .on('waypointschanged', routesWaypointsChanged);
            tracking.data.routing.calculatingRoute = true;
            routesLoading();
            tracking.directionsService.setWaypoints(points);

            // hide our duplicate icons
            var $destinations = $('.routing-destination input', tracking.data.domNodes.dialogs.routing);
            _.each($destinations, function (destination) {
                var location = $(destination).data('location');
                if (location !== undefined) {
                    location.removeFrom(tracking.map);
                }
            });
            $j('#routing-directions').append(tracking.directionsService.control);
        }


        $('#routing-destinations').on('keyup', '.routing-destination input', _.debounce(routingAddressEntry, 500));
        $(tracking.data.domNodes.dialogs.routing).on('click', '#RoutingAddDestination', function (e) {
            e.preventDefault();

            routingAddDestinationOption()
            routingCheckForPendingDestination();
        });
        $(tracking.data.domNodes.dialogs.routing).on('click', '#RoutingCalculateRoute', function (e) {
            e.preventDefault();

            routingCalculateRoute();
        });
        $(tracking.data.domNodes.dialogs.routing).on('click', '#RoutingNewRoute', function (e) {
            e.preventDefault();
            closeSecondaryPanel();
            openRouting();
        });

        $(tracking.data.domNodes.dialogs.routing).on('click', '#RoutingAddGeofence', function (e) {
            if (!tracking.user.canEditGeofences || tracking.options.enabledFeatures.indexOf('UI_GEOFENCE_FROM_ROUTE') === -1) {
                return;
            }
            $(tracking.data.domNodes.modals.geofenceBuffer).modal('show');
            tracking.state.isPreviewingBuffer = true;
            var buffer = $j('#RouteGeofenceBuffer').slider('value');
            $('#RouteGeofenceBufferValue').text(buffer);
            tracking.throttles.bufferGeofence(buffer);
        });
        $('#routing-destinations').sortable({
            axis: 'y',
            items: '.routing-destination',
            handle: '.drag',
            update: routingCheckForPendingDestination
        });

        function secondsToHms(d) {
            d = Number(d);
            var h = Math.floor(d / 3600);
            var m = Math.floor(d % 3600 / 60);
            var s = Math.floor(d % 3600 % 60);
            return ((h > 0 ? h + " hrs " : "") + (m > 0 ? (h > 0 && m < 10 ? "0" : "") + m + " mins " : "") + (s < 10 ? "0" : "") + s + " secs");
        }

        tracking.data.domNodes.dialogs.placeRouting = $('#place-route-dialog')[0];
        $(tracking.data.domNodes.dialogs.placeRouting).on('click', '#PlaceRouteCalculate', function (e) {
            closePlaceRouting();
            var dialog = $j('#place-route-dialog');
            var placeId = $j('#PlaceRoutePlace').val();
            var assetId = $j('#PlaceRouteAsset').val();
            var place = findPlaceById(placeId);
            var via = $j('input[name=PlaceRouteVia]:checked', dialog).val();

            // take to latest position
            var latestPositionMarker = undefined;
            if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                var latest = tracking.data.live.latestPositionsByAssetId[assetId];
                if (latest !== undefined && latest.Position !== undefined) {
                    latestPositionMarker = tracking.data.live.markersByPositionId[latest.Position.Id];
                }
            } else {
                if (tracking.data.history.positionsByAssetId[assetId] !== undefined) {
                    var firstPosition = _.first(tracking.data.history.positionsByAssetId[assetId].Positions);
                    if (firstPosition !== undefined) {
                        latestPositionMarker = tracking.data.history.markersByPositionId[firstPosition.Id];
                    }
                }
            }

            if (latestPositionMarker === undefined || latestPositionMarker === null) {
                return;
            }

            var startingLocation = latestPositionMarker.getLatLng();

            var placePosition = null;
            for (var i = 0; i < tracking.data.placeMarkers.length; i++) {
                var placeId = tracking.data.placeMarkers[i].data.placeId;
                if (placeId == place.Id) {
                    placePosition = tracking.data.placeMarkers[i];
                    break;
                }
            }
            var endingLocation = placePosition.getLatLng();
            var bounds = L.latLngBounds(startingLocation, endingLocation);
            tracking.map.fitBounds(bounds, { padding: [10, 10] });
            switch (via) {
                case 'Road':
                    tracking.directionsService
                        .on('routesfound', placeRoutesFound)
                        .on('routingerror', placeRoutesError);
                    placeRoutesLoading();
                    tracking.directionsService.setWaypoints([startingLocation, endingLocation]);
                    $j('#place-route-directions').append(tracking.directionsService.control);
                    // todo: loading screen here, it can take a while!
                    break;
                default:
                    // direct line of sight
                    tracking.data.routeLine = L.geodesic([[startingLocation, endingLocation]], {
                        weight: 4,
                        color: '#cea843',
                        wrap: false,
                        steps: 50,
                        opacity: 0.7
                    });
                    addItemToMap(tracking.data.routeLine);
                    if (tracking.options.enabledFeatures.indexOf('UI_GEOFENCE_FROM_ROUTE') !== -1) {
                        $('#PlaceRouteGeofence,#RouteGeofence,#RouteGeofenceCreate').removeClass('disabled');
                    }

                    var distanceMeters = startingLocation.distanceTo(endingLocation);
                    var distance = convertFromMetresToUserDistancePreference(distanceMeters);

                    var speed = null;

                    //var loc = $j(latestPosition).data('location');
                    var loc = latestPositionMarker.data.location;
                    if (loc != null) {
                        speed = loc.Speed;
                    }
                    var time = null;
                    if ((speed != null) && (speed > 0)) {
                        time = distanceMeters / loc.Speed;
                        time = secondsToHms(time);
                    }
                    placeRoutesFound();

                    $j('#place-route-directions')
                        .append(el('span', distance));
                    if (speed != null) {
                        $j('#place-route-directions')
                            .append(el('span', ' @ ' + convertSpeedToPreference(speed)));
                    }
                    if (time != null) {
                        $j('#place-route-directions')
                            .append(el('span', ' - about ' + time));
                    }
                    break;
            }

        });
        $(tracking.data.domNodes.dialogs.placeRouting).on('click', '#PlaceRouteCancel', function (e) {
            document.getElementById('panel-dialog-back').click();
        });
        tracking.data.domNodes.modals.geofenceBuffer = document.getElementById('geofence-buffer-modal');
        $(tracking.data.domNodes.modals.geofenceBuffer).on('hide.bs.modal', function (e) {
            tracking.state.isPreviewingBuffer = false;
            if (tracking.data.routePolygon !== null) {
                removeItemFromMap(tracking.data.routePolygon);
            }
        });
        $(tracking.data.domNodes.dialogs.placeRouting).on('click', '#PlaceRouteGeofence', function (e) {
            if (!tracking.user.canEditGeofences || tracking.options.enabledFeatures.indexOf('UI_GEOFENCE_FROM_ROUTE') === -1) {
                return;
            }
            var modal = $(tracking.data.domNodes.modals.geofenceBuffer).modal('show');
            tracking.state.isPreviewingBuffer = true;
            var buffer = $j('#RouteGeofenceBuffer').slider('value');
            $j('#RouteGeofenceBufferValue').text(buffer);
            tracking.throttles.bufferGeofence(buffer);
        });
        $(tracking.data.domNodes.modals.geofenceBuffer).on('click', '#RouteGeofenceCreate', function (e) {
            var btn = this;
            btn.disabled = true;
            if (tracking.options.enabledFeatures.indexOf('UI_GEOFENCE_FROM_ROUTE') === -1) {
                return;
            }
            
            var modal = $j(tracking.data.domNodes.modals.geofenceBuffer);
            var btn = $j('#RouteGeofenceCreate').prop('disabled', true);
            var latlngs = [];
            if (tracking.data.routeLine != null) {
                tracking.data.routeLine.getLatLngs().forEach(function (elem, index) {
                    latlngs.push({ Lat: elem.lat, Lng: elem.lng });
                });
            }
            var data = {
                encodedPolyline: tracking.data.routeLineEncoded,
                encodedPolylines: tracking.data.routeLinesEncoded,
                latlngs: latlngs,
                buffer: $j('#RouteGeofenceBuffer').slider('value')
            };
            toggleLoadingMessage(false, 'polyline-geofence');
            // submit encoded polyline to server, have it create and return a buffered polygon to populate add geofence
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/BufferPolyline'),
                data: JSON.stringify(data),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    btn.disabled = false;
                    var result = msg.d;
                    if (result) {
                        if (result.Success != true) {
                            utility.handleWebServiceError(tracking.strings.MSG_ADD_GEOFENCE_ERROR);
                        } else {
                            modal.modal('hide');
                            var points = result.Points;

                            var fence = {
                                Id: 1,
                                Segments: [{ Points: points, Type: 1 }]
                            };

                            openGeofenceDialog(null);
                            editFenceSegments(fence);
                        }
                    }
                    toggleLoadingMessage(false, 'polyline-geofence');
                },
                error: function (xhr, status, error) {
                    btn.disabled = false;
                    utility.handleWebServiceError(tracking.strings.MSG_ADD_GEOFENCE_ERROR);
                    toggleLoadingMessage(false, 'polyline-geofence');
                }
            });
        });
        //loadDialogButtons(tracking.data.domNodes.dialogs.placeRouting, placeRouteButtons);

        function loadDialogButtons(dialog, buttons) {
            if (buttons === null || buttons.length === 0) {
                return;
            }
            if (dialog === null) {
                return;
            }
            var buttonPane = dialog.querySelector('.dialog-buttons');
            if (buttonPane === null) {
                return;
            }
            _.each(buttons, function (button, index, list) {
                var btn = document.createElement('button');
                btn.className = 'btn';
                if (button.class !== undefined) {
                    btn.className += ' ' + button.class;
                }
                btn.type = 'button';
                if (button.id !== undefined) {
                    btn.id = button.id;
                }
                if (button.svgIcon !== undefined) {
                    var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
                    var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
                    svg.appendChild(use);
                    use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#' + button.svgIcon);
                    btn.appendChild(svg);
                    btn.appendChild(document.createTextNode('\u00A0'));
                }
                if (button.buttonType !== undefined) {
                    btn.classList.add('btn-' + button.buttonType);
                } else {
                    // default to primary if left unspecified
                    btn.classList.add('btn-primary');
                }
                if (button.disabled !== undefined && button.disabled === true) {
                    btn.disabled = true;
                }
                var label = document.createElement('span');
                label.textContent = button.text;
                btn.appendChild(label);

                if (button.click !== undefined) {
                    $(btn).on('click', button.click);
                }
                if (buttonPane.hasChildNodes()) {
                    buttonPane.appendChild(document.createTextNode('\u00A0'));
                }
                buttonPane.appendChild(btn);
            });
        }

        tracking.data.domNodes.infoDialogs.ruler = document.getElementById('ruler-dialog');
        $j(tracking.data.domNodes.infoDialogs.ruler).on('click', 'a.delete', function (e) {
            e.preventDefault();
            var index = $j('a.delete', tracking.data.domNodes.infoDialogs.ruler).index($j(this));
            removeRulerPoint(index);
        });

        $j(tracking.data.domNodes.infoDialogs.ruler).dialog({
            autoOpen: false,
            modal: false,
            maxWidth: 350,
            resizable: false,
            close: function () {
                closeRuler();
            },
            create: function () {
                createDialogTitlebar(this);
                replaceDialogButtons(this, $(this).dialog('option', 'buttons'));
            }
        });

        var sharedViewShareButtons = [{
            buttonType: 'primary',
            svgIcon: 'envelope-solid',
            text: tracking.strings.SEND_INVITE,
            id: 'SharedViewShareButton',
            click: function () {
                var isValid = $(tracking.data.validation.sharedViewShare.currentForm).valid();
                if (!isValid) {
                    tracking.data.validation.sharedViewShare.focusInvalid();
                    return;
                }

                var dialog = tracking.data.domNodes.dialogs.sharedViewShare;
                var status = dialog.querySelector('.dialog-status');
                var btn = this;

                var id = parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id'));
                var email = document.getElementById('SharedViewShareEmail').value;
                var subject = document.getElementById('SharedViewShareSubject').value;
                var body = document.getElementById('SharedViewShareBody').value;
                var includePassword = document.getElementById('SharedViewShareIncludePasswordYes').checked;

                var data = {
                    request: {
                        id: id,
                        email: email,
                        subject: subject,
                        body: body,
                        includePassword: includePassword
                    }
                };

                handleAjaxFormSubmission('SendSharedViewInvites', data, btn, status, tracking.strings.MSG_SHARE_SHARED_VIEW_SUCCESS, tracking.strings.MSG_SHARE_SHARED_VIEW_ERROR, function (result) {
                    // reload form defaults
                    tracking.data.validation.sharedViewShare.resetForm();
                    tracking.data.validation.sharedViewShare.currentForm.reset();

                    var sharedView = findSharedViewById(id);
                    if (sharedView !== null) {
                        querySharedViewInvites(sharedView);
                    }
                });
            }
        }, closeButton];

        tracking.data.domNodes.dialogs.sharedViewShare = document.getElementById('shared-view-share-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.sharedViewShare, sharedViewShareButtons);

        tracking.data.domNodes.dialogs.sharedViewStatistics = document.getElementById('shared-view-statistics-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.sharedViewStatistics, [closeButton]);
        $('#shared-view-visitor-activity').dataTable({
            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': false, 'autoWidth': false,
            'lengthChange': false, 'paging': false, 'pageLength': 3, 'deferRender': true,
            'order': [[2, 'desc']],
            'columnDefs': [{
                'targets': '_all',
                'render': $.fn.dataTable.render.text()
            }],
            'columns': [
                {}, // name
                {}, // visits
                {} // last visit
            ],
            'language': tracking.strings.DATATABLE,
            'drawCallback': function (oSettings) {
            },
            'initComplete': function (oSettings, json) {
                $('#shared-view-visitor-activity').DataTable().clear();
            }
        });

        var sharedViewButtons = [{
            buttonType: 'primary',
            text: tracking.strings.CREATE_SHARED_VIEW,
            id: 'UpdateSharedViewButton',
            click: function () {

            }
        }, closeButton];
        tracking.data.domNodes.dialogs.sharedView = document.getElementById('shared-view-dialog');

        tracking.data.wizards.sharedView = tracking.data.domNodes.dialogs.sharedView.querySelector('.step-wizard');
        tracking.data.wizards.sharedView.steps = [];
        _.each(tracking.data.wizards.sharedView.querySelectorAll('.wizard-step'), function(item) {
            tracking.data.wizards.sharedView.steps.push({
                id: item.getAttribute('data-step-id'),
                step: parseInt(item.getAttribute('data-step-step')),
                title: item.getAttribute('data-step-title'),
                icon: item.getAttribute('data-step-icon'),
                form: $(item.querySelector('form')).validate({
                    ignore: ":hidden:not(#SharedViewShareOne)",
                    showErrors: function (errorMap, errorList) {
                        if (errorMap.SharedViewShareOne !== undefined) {
                            // turn it red
                            document.getElementById('SharedViewShareOneHint').classList.add('invalid-feedback');
                        } else {
                            this.defaultShowErrors();
                        }
                    },
                    errorPlacement: function (error, element) {
                        // error = error element
                        if (element.id !== 'SharedViewShareOne' && element.id !== 'SharedViewDataTimeframeRelativeNumber') {
                            error.addClass('invalid-feedback');
                            error.insertAfter(element);
                        }
                    },
                    unhighlight: function (element, errorClass, validClass) {
                        console.log('unhighlight', element, errorClass, validClass);
                        if (element.id === 'SharedViewShareOne') {
                            document.getElementById('SharedViewShareOneHint').classList.remove('invalid-feedback');
                        } else {
                            $(element).removeClass(errorClass).addClass(validClass);
                        }
                    }
                }) // this should happen after jquery validation initialization
            });
        });
        tracking.data.wizards.sharedView.steps = _.sortBy(tracking.data.wizards.sharedView.steps, 'step');

        loadDialogButtons(tracking.data.domNodes.dialogs.sharedView, sharedViewButtons);
        $(tracking.data.domNodes.dialogs.sharedView).on('click', 'input[name=SharedViewIsPublic]', function(e) {
            var isPrivate = document.getElementById('SharedViewIsPublicNo').checked;
            if (isPrivate) {
                document.getElementById('SharedViewPrivatePassword').classList.add('is-visible');
            } else {
                document.getElementById('SharedViewPrivatePassword').classList.remove('is-visible');
            }
        });
        $(tracking.data.domNodes.dialogs.sharedView).on('click', '#ShareViewShowAll', function(e) {
            e.preventDefault();
            closeSecondaryPanel();
        });
        $(tracking.data.domNodes.dialogs.sharedView).on('click', '#ShareViewEdit', function (e) {
            e.preventDefault();
            if (tracking.data.sharedView.current !== null) {
                openSharedViewSettingsPanel(tracking.data.sharedView.current);
            }
        });
        $(tracking.data.domNodes.infoDialogs.sharedViewInformation).on('click', '#SharedViewMapInformationShare', function(e) {
            e.preventDefault();
            if (tracking.data.sharedView.current !== null) {
                openSharedViewSettingsPanel(findSharedViewById(tracking.data.sharedView.current.Id));
            }
        });
        $(tracking.data.domNodes.dialogs.sharedView).on('click', '#ShareViewCompleteEmail', function (e) {
            e.preventDefault();
            if (tracking.data.sharedView.current !== null) {
                openSharedViewShareDialog(findSharedViewById(tracking.data.sharedView.current.Id));
            }
        });
        $(tracking.data.domNodes.dialogs.sharedView).on('click', 'input[name=SharedViewDoesExpire]', function (e) {
            var doesExpire = document.getElementById('SharedViewDoesExpireYes').checked;
            if (doesExpire) {
                document.getElementById('SharedViewExpireDate').classList.add('is-visible');
                document.getElementById('SharedViewExpiresOn').disabled = false;
            } else {
                document.getElementById('SharedViewExpireDate').classList.remove('is-visible');
                document.getElementById('SharedViewExpiresOn').disabled = true;
            }
        });
        //$(tracking.data.domNodes.dialogs.sharedView).on('change', '#SharedViewDefaultMode', function(e) {
        //    if (parseInt(this.value) === tracking.mapModes.HISTORY) {
        //        document.getElementById('SharedViewModeHistory').classList.add('is-visible');
        //    } else {
        //        document.getElementById('SharedViewModeHistory').classList.remove('is-visible');
        //    }
        //});
        $(tracking.data.domNodes.dialogs.sharedView).on('change', '#SharedViewMapType', function (e) {
            var editing = tracking.data.sharedView.temp;
            if (editing !== null) {
                var prior = cloneSharedView(editing);
                editing.Preferences.MapType = parseInt(this.value);
                compareSharedViews(editing, prior);
            }
        });
        $(tracking.data.domNodes.dialogs.sharedView).on('click', 'input[name=SharedViewPositionConsolidation]', function (e) {
            var isPositionsConsolidated = document.getElementById('SharedViewPositionConsolidationYes').checked;
            var editing = tracking.data.sharedView.temp;
            if (editing !== null) {
                var prior = cloneSharedView(editing);
                editing.Preferences.PositionConsolidation = isPositionsConsolidated;
                compareSharedViews(editing, prior);
            }
        });
        $(tracking.data.domNodes.dialogs.sharedView).on('click', 'input[name=SharedViewRemoveRoads]', function (e) {
            var isRemoveRoads = document.getElementById('SharedViewRemoveRoadsYes').checked;
            var editing = tracking.data.sharedView.temp;
            if (editing !== null) {
                var prior = cloneSharedView(editing);
                editing.Preferences.RemoveRoads = isRemoveRoads;
                compareSharedViews(editing, prior);
            }
        });
        $(tracking.data.domNodes.dialogs.sharedView).on('click', 'input[name=SharedViewIsMessagingEnabled]', function (e) {
            var isMessagingEnabled = document.getElementById('SharedViewIsMessagingEnabledYes').checked;
            var editing = tracking.data.sharedView.temp;
            if (editing !== null) {
                var prior = cloneSharedView(editing);
                editing.IsMessagingEnabled = isMessagingEnabled;
                compareSharedViews(editing, prior);
            }
        });

        function sharedViewDateChange() {
            console.log('sharedViewDateChange');
            var editing = tracking.data.sharedView.temp;
            if (editing === null) {
                return;
            }

            var prior = cloneSharedView(editing);
            var isRelative = document.getElementById('SharedViewDataTimeframeRelative').checked;
            var isDataLimited = document.getElementById('SharedViewDataTimeframeCustom').checked;            

            if (isRelative) {
                editing.IsTimeframeRelative = true;
                editing.RelativeTimeframeNumber = document.getElementById('SharedViewDataTimeframeRelativeNumber').value;
                editing.RelativeTimeframeType = document.getElementById('SharedViewDataTimeframeRelativeType').value;
                editing.FromDateEpoch = null;
                editing.ToDateEpoch = null;
            } else {
                editing.IsTimeframeRelative = false;
                editing.RelativeTimeframeNumber = null;
                editing.RelativeTimeframeType = null;
                if (isDataLimited) {                
                    var from = $(document.getElementById('SharedViewFrom')).datetimepicker('getDate');
                    if (from !== null) {
                        from = from.getTime();
                    }
                    var to = $(document.getElementById('SharedViewTo')).datetimepicker('getDate');
                    if (to !== null) {
                        to = to.getTime();
                    }
                    editing.FromDateEpoch = from;
                    editing.ToDateEpoch = to;
                } else {
                    editing.FromDateEpoch = null;
                    editing.ToDateEpoch = null;
                }
            }
            compareSharedViews(editing, prior);
        }
        $(tracking.data.domNodes.dialogs.sharedView).on('click', 'input[name=SharedViewDataTimeframe]', function (e) {
            // enable/disable inputs based on which radio is selected
            var isRelative = document.getElementById('SharedViewDataTimeframeRelative').checked;
            if (isRelative) {                
                document.getElementById('SharedViewRelativeDateRange').classList.add('is-visible');
                document.getElementById('SharedViewDataTimeframeRelativeNumber').disabled = false;
                document.getElementById('SharedViewDataTimeframeRelativeType').disabled = false;
                document.getElementById('SharedViewDateRange').classList.remove('is-visible');
                document.getElementById('SharedViewFrom').disabled = true;
                document.getElementById('SharedViewTo').disabled = true;
            } else {
                document.getElementById('SharedViewRelativeDateRange').classList.remove('is-visible');
                document.getElementById('SharedViewDataTimeframeRelativeNumber').disabled = true;
                document.getElementById('SharedViewDataTimeframeRelativeType').disabled = true;
                var isLimited = document.getElementById('SharedViewDataTimeframeCustom').checked;
                if (isLimited) {
                    document.getElementById('SharedViewDateRange').classList.add('is-visible');
                    document.getElementById('SharedViewFrom').disabled = false;
                    document.getElementById('SharedViewTo').disabled = false;
                } else {
                    document.getElementById('SharedViewDateRange').classList.remove('is-visible');
                    document.getElementById('SharedViewFrom').disabled = true;
                    document.getElementById('SharedViewTo').disabled = true;
                }                
            }
            sharedViewDateChange();
        });
        $(tracking.data.domNodes.dialogs.sharedView).on('change', '#SharedViewFrom,#SharedViewTo,#SharedViewDataTimeframeRelativeNumber,#SharedViewDataTimeframeRelativeType', sharedViewDateChange);
        $(tracking.data.domNodes.dialogs.sharedView).on('change', '#SharedViewPermissionsGroups,#SharedViewPermissionsAssets,#SharedViewPermissionsGeofences,#SharedViewPermissionsPlaces,#SharedViewPermissionsDrivers', function (e) {
            var collapse = document.getElementById(this.getAttribute('aria-controls'));
            if (this.checked) {
                $(collapse).collapse('show');
            } else {
                $(collapse).collapse('hide');
            }
            var editing = tracking.data.sharedView.temp;
            if (editing !== null) {
                var permissions = getSharedViewSelectionOptions();
                var prior = cloneSharedView(editing);
                editing.AssetIds = permissions.assetIds;
                editing.FenceIds = permissions.fenceIds;
                editing.AssetGroupIds = permissions.assetGroupIds;
                editing.PlaceIds = permissions.placeIds;
                editing.DriverIds = permissions.driverIds;
                compareSharedViews(editing, prior);
            }  
        });
        $(tracking.data.domNodes.dialogs.sharedView).on('click', 'input[name=SharedViewAssetGroupIds]', function (e) {
            var cont = document.getElementById('shared-view-groups-list');
            if (this.checked) {
                restrictAllChildren(this, cont);
            } else {
                unrestrictDirectChildren(this, cont);
            }
        });
        $(tracking.data.domNodes.dialogs.sharedView).on('change', 'input[name=SharedViewAssetGroupIds],input[name=SharedViewAssetIds],input[name=SharedViewPlaceIds],input[name=SharedViewFenceIds],input[name=SharedViewDriverIds]', function (e) {
            // this is happening before the input checkbox is checked
            // 
            console.log('permissions item click');
            var editing = tracking.data.sharedView.temp;
            if (editing !== null) {
                var permissions = getSharedViewSelectionOptions();
                var prior = cloneSharedView(tracking.data.sharedView.temp);
                editing.AssetIds = permissions.assetIds;
                editing.FenceIds = permissions.fenceIds;
                editing.AssetGroupIds = permissions.assetGroupIds;
                editing.PlaceIds = permissions.placeIds;
                editing.DriverIds = permissions.driverIds;
                compareSharedViews(editing, prior);
            }            
        });

        $(tracking.data.domNodes.dialogs.sharedView).on('click', '.next-step,.prev-step', function (e) {
            console.log('next/prev step');
            var isNext = this.classList.contains('next-step');
            var currentStep = tracking.data.domNodes.dialogs.sharedView.querySelector('.wizard-step.is-active');
            var currentStepId = currentStep.getAttribute('data-step-id');
            var currentStepStep = parseInt(currentStep.getAttribute('data-step-step'));
            console.log(currentStepId, isNext);
            if (isNext) {
                goToWizardStep(tracking.data.wizards.sharedView, null, ++currentStepStep);
            } else {
                goToWizardStep(tracking.data.wizards.sharedView, null, --currentStepStep);
            }
        });

        function getSharedViewSelectionOptions() {
            var dialog = tracking.data.domNodes.dialogs.sharedView;
            var assetGroupIds = [];
            var assetIds = [];
            var fenceIds = [];
            var placeIds = [];
            var driverIds = [];
            if (document.getElementById('SharedViewPermissionsGroups').checked) {
                assetGroupIds = _.map(dialog.querySelectorAll('input[name="SharedViewAssetGroupIds"]:checked'), 'value');
            }
            if (document.getElementById('SharedViewPermissionsAssets').checked) {
                assetIds = _.map(dialog.querySelectorAll('input[name="SharedViewAssetIds"]:checked'), function (item) { return parseInt(item.value); });
            }
            if (document.getElementById('SharedViewPermissionsGeofences').checked) {
                fenceIds = _.map(dialog.querySelectorAll('input[name="SharedViewFenceIds"]:checked'), 'value');
            }
            if (document.getElementById('SharedViewPermissionsPlaces').checked) {
                placeIds = _.map(dialog.querySelectorAll('input[name="SharedViewPlaceIds"]:checked'), function (item) { return parseInt(item.value); });
            }
            if (document.getElementById('SharedViewPermissionsDrivers').checked) {
                driverIds = _.map(dialog.querySelectorAll('input[name="SharedViewDriverIds"]:checked'), function (item) { return parseInt(item.value); });
            }
            return {
                assetIds: assetIds,
                fenceIds: fenceIds,
                placeIds: placeIds,
                driverIds: driverIds,
                assetGroupIds: assetGroupIds
            };
        }

        function populateSharedViewSubmissionFromForm() {            
            var id = parseInt(document.getElementById('SharedViewId').value);
            var name = document.getElementById('SharedViewName').value;
            var description = document.getElementById('SharedViewDescription').value;
            var color = document.getElementById('SharedViewColor').value;

            var permissions = getSharedViewSelectionOptions();
            var assetGroupIds = permissions.assetGroupIds;
            var assetIds = permissions.assetIds;
            var fenceIds = permissions.fenceIds;
            var placeIds = permissions.placeIds;
            var driverIds = permissions.driverIds;
            
            var isEnabled = document.getElementById('SharedViewIsEnabledYes').checked;
            var isPublic = document.getElementById('SharedViewIsPublicYes').checked;
            var password = document.getElementById('SharedViewPassword').value;
            var doesExpire = document.getElementById('SharedViewDoesExpireYes').checked;
            var expiresOn = $(document.getElementById('SharedViewExpiresOn')).datetimepicker('getDate'); // user's local date
            if (expiresOn !== null) {
                expiresOn = moment(expiresOn).format(tracking.user.dateFormat);
            }
            var isMessagingEnabled = document.getElementById('SharedViewIsMessagingEnabledYes').checked;
            var isTimeframeRelative = document.getElementById('SharedViewDataTimeframeRelative').checked;
            var relativeTimeframeNumber = null;
            var relativeTimeframeType = null;
            var isDataLimited = false;
            var from = null;
            var to = null;
            if (isTimeframeRelative) {
                relativeTimeframeNumber = document.getElementById('SharedViewDataTimeframeRelativeNumber').value;
                relativeTimeframeType = document.getElementById('SharedViewDataTimeframeRelativeType').value;
            } else {
                isDataLimited = document.getElementById('SharedViewDataTimeframeCustom').checked;            
                from = $(document.getElementById('SharedViewFrom')).datetimepicker('getDate');
                if (from !== null) {
                    from = moment(from).format(tracking.user.dateFormat);
                }
                to = $(document.getElementById('SharedViewTo')).datetimepicker('getDate');
                if (to !== null) {
                    to = moment(to).format(tracking.user.dateFormat);
                }
            }
            
            var preferenceLanguage = document.getElementById('SharedViewLanguage').value;
            var preferenceTimezone = document.getElementById('SharedViewTimezone').value;
            var preferenceLatLng = parseInt(document.getElementById('SharedViewLatLngFormat').value);
            var preferenceSpeed = parseInt(document.getElementById('SharedViewSpeedFormat').value);
            var preferenceFuel = parseInt(document.getElementById('SharedViewFuelFormat').value);

            var preferenceRemoveRoads = document.getElementById('SharedViewRemoveRoadsYes').checked;
            var preferencePositionConsolidation = document.getElementById('SharedViewPositionConsolidationYes').checked;
            var preferencePositionAlpha = document.getElementById('SharedViewPositionAlphaYes').checked;
            var preferenceHistoryViewNumber = 7;//parseInt(document.getElementById('SharedViewHistoryViewNumber').value);
            var preferenceHistoryViewType = 'd';//document.getElementById('SharedViewHistoryViewType').value;
            var preferenceMapType = parseInt(document.getElementById('SharedViewMapType').value);
            var preferenceDefaultMode = parseInt(document.getElementById('SharedViewDefaultMode').value);

            var preferences = {
                preferenceSpeed: preferenceSpeed,
                preferenceFuel: preferenceFuel,
                preferenceTimezone: preferenceTimezone,
                preferenceLanguage: preferenceLanguage,
                preferenceRemoveRoads: preferenceRemoveRoads,
                preferencePositionConsolidation: preferencePositionConsolidation,
                preferencePositionAlpha: preferencePositionAlpha,
                preferenceHistoryViewNumber: preferenceHistoryViewNumber,
                preferenceHistoryViewType: preferenceHistoryViewType,
                preferenceLatLng: preferenceLatLng,
                preferenceMapType: preferenceMapType,
                preferenceDefaultMode: preferenceDefaultMode
            };

            var data = {
                request: {
                    id: id,
                    name: name,
                    description: description,
                    color: color,
                    assetGroupIds: assetGroupIds,
                    assetIds: assetIds,
                    fenceIds: fenceIds,
                    placeIds: placeIds,
                    driverIds: driverIds,
                    isEnabled: isEnabled,
                    isPublic: isPublic,
                    password: password,
                    doesExpire: doesExpire,
                    expiresOn: expiresOn,
                    //expiresOnUtc: expiresOn,
                    isTimeframeRelative: isTimeframeRelative,
                    relativeTimeframeNumber: relativeTimeframeNumber,
                    relativeTimeframeType: relativeTimeframeType,
                    isDataLimited: isDataLimited,
                    isMessagingEnabled: isMessagingEnabled,
                    //fromUtc: from,
                    from: from,
                    to: to,
                    //toUtc: to,
                    preferences: preferences
                }
            };
            return data;
        }
        $(tracking.data.domNodes.dialogs.sharedView).on('click', '#SharedViewWizardSave', function (e) {
            e.preventDefault();

            var isFormValid = true;
            var invalidStep = null;
            _.each(tracking.data.wizards.sharedView.steps, function (step) {
                if (step.form !== undefined) {
                    var isValid = $(step.form.currentForm).valid();
                    if (!isValid) {
                        isFormValid = false;
                        invalidStep = step;
                    }
                }
                return;
            });

            if (!isFormValid) {
                invalidStep.form.focusInvalid();
                return;
            }

            var dialog = tracking.data.domNodes.dialogs.sharedView;
            var status = dialog.querySelector('.dialog-status');
            var btn = this;
            var data = populateSharedViewSubmissionFromForm();
            delete data.request.id;
            handleAjaxFormSubmission('AddSharedView', data, btn, status, null, tracking.strings.MSG_CREATE_SHARED_VIEW_ERROR, function (result) {
                var sharedView = result.SharedView;
                addSharedView(sharedView);

                tracking.data.sharedView.current = sharedView;
                highlightActiveItemInPrimaryPanel('shared-views', sharedView.Id);

                // show success step
                goToWizardStep(tracking.data.wizards.sharedView, 'success');

                // populate share links
                dialog.querySelector('.share-view-share.email').setAttribute('data-share-view-id', sharedView.Id);
                dialog.querySelector('.share-view-share.twitter').setAttribute('href', 'https://twitter.com/intent/tweet?url=' + encodeURIComponent(sharedView.Link) + '&text=' + encodeURIComponent(sharedView.Name) + '. ' + encodeURIComponent(sharedView.Description));
                dialog.querySelector('.share-view-share.linkedin').setAttribute('href', 'https://www.linkedin.com/shareArticle?mini=true&url=' + encodeURIComponent(sharedView.Link) + '&title=' + encodeURIComponent(sharedView.Name) + '&summary=' + encodeURIComponent(sharedView.Description) + '&source=' + encodeURIComponent(tracking.productTitle));
                dialog.querySelector('.share-view-share.facebook').setAttribute('href', 'https://www.facebook.com/sharer.php?u=' + encodeURIComponent(sharedView.Link));
                dialog.querySelector('#ShareViewCopyLink').setAttribute('data-share-view-id', sharedView.Id);
                dialog.querySelector('#ShareViewCopyLink').setAttribute('data-link', sharedView.Link);
                document.getElementById('ShareViewCopyLinkLink').textContent = sharedView.Link;
                document.getElementById('ShareViewCopyLinkLink').href = sharedView.Link;
            });
        });

        $(document).on('click', '.copy-link', function (e) {
            e.preventDefault();
            var btn = this;
            var existingText = btn.querySelector('span').textContent;
            $.when(copyTextToClipboard(btn.getAttribute('data-link'))).done(function() {
                btn.querySelector('span').textContent = tracking.strings.LINK_COPIED;
                setTimeout(function () {
                    btn.querySelector('span').textContent = existingText;
                }, 3000);
                console.log('copied successfully.');
            }).fail(function() {
                console.log('failed to copy');
            });
        });

        $(tracking.data.domNodes.dialogs.sharedView).on('click', '#UpdateSharedViewButton', function (e) {
            var step = tracking.data.wizards.sharedView.currentStep;
            var isValid = $(step.form.currentForm).valid();
            if (!isValid) {
                step.form.focusInvalid();
                return;
            }

            var dialog = tracking.data.domNodes.dialogs.sharedView;
            var status = dialog.querySelector('.dialog-status');
            var btn = this;

            // really should just be updating the current step, but probably ok to do it all
            var data = populateSharedViewSubmissionFromForm();

            handleAjaxFormSubmission('UpdateSharedView', data, btn, status, tracking.strings.MSG_EDIT_SHARED_VIEW_SUCCESS, tracking.strings.MSG_EDIT_SHARED_VIEW_ERROR, function (result) {
                updateSharedView(result.SharedView);

                tracking.data.sharedView.temp = result.SharedView;
                tracking.data.sharedView.current = result.SharedView;
                showSharedViewInformationOnMap(result.SharedView, false);
            });
        });
        $(tracking.data.domNodes.dialogs.sharedView).on('click', '.wizard-buttons .cancel', function (e) {
            e.preventDefault();
            _.each(tracking.data.wizards.sharedView.steps, function (step) {
                if (step.form !== undefined) {
                    step.form.resetForm();
                    step.form.currentForm.reset();
                }
            });
            closeSecondaryPanel();
        });
        $(tracking.data.domNodes.dialogs.sharedView).on('click', '.cancel', function (e) {
            e.preventDefault();

            _.each(tracking.data.wizards.sharedView.steps, function (step) {
                if (step.form !== undefined) {
                    step.form.resetForm();
                    step.form.currentForm.reset();
                }
            });
            document.getElementById('panel-dialog-back').click();
        });
        $('#SharedViewExpiresOn').datetimepicker({
            dateFormat: tracking.user.shortDateFormat,
            hour: 0,
            minute: 0,
            minDate: tracking.options.dateRangeMin === null ? null : new Date(tracking.options.dateRangeMin),
            maxDate: tracking.options.dateRangeMax === null ? null : new Date(tracking.options.dateRangeMax)
        });
        $('#SharedViewFrom').datetimepicker({
            dateFormat: tracking.user.shortDateFormat,
            hour: 0,
            minute: 0,
            minDate: tracking.options.dateRangeMin === null ? null : new Date(tracking.options.dateRangeMin),
            maxDate: tracking.options.dateRangeMax === null ? null : new Date(tracking.options.dateRangeMax),
            onSelect: function (selectedDate) {
                // when a date is selected here, set the min date for the to picker
                var current = $('#SharedViewTo').val();
                $('#SharedViewTo').datetimepicker('option', 'minDate', $(this).datetimepicker('getDate'));
                $('#SharedViewTo').val(current);
            }
        });
        $('#SharedViewTo').datetimepicker({
            dateFormat: tracking.user.shortDateFormat,
            hour: 23,
            minute: 59,
            minDate: tracking.options.dateRangeMin === null ? null : new Date(tracking.options.dateRangeMin),
            maxDate: tracking.options.dateRangeMax === null ? null : new Date(tracking.options.dateRangeMax),
            onSelect: function (selectedDate) {
                // when a date is selected here, set the max date for the from picker
                var current = $('#SharedViewFrom').val();
                $('#SharedViewFrom').datetimepicker('option', 'maxDate', $(this).datetimepicker('getDate'));
                $('#SharedViewFrom').val(current);
            }
        });

        tracking.data.domNodes.dialogs.addPlace = document.getElementById('add-place-dialog');
        $(tracking.data.domNodes.dialogs.addPlace).on('click', '#RemovePlacePhoto', function (e) {
            e.preventDefault();
            $(this).next().val('true');
            $(this).parent().parent().hide();
        });
        $(tracking.data.domNodes.dialogs.addPlace).on('click', '#AddPlace', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.placeAdd.currentForm).valid();
            if (!isFormValid) {
                tracking.data.validation.placeAdd.focusInvalid();
                return;
            }

            // submit position to pending
            var btn = this;
            var status = document.getElementById('add-place-status');

            var id = $('#hfPlaceId').val();
            var name = $('#txtPlaceName').val();
            var description = $('#txtPlaceDescription').val();
            var contact = $('#txtPlaceContact').val();
            var uniqueKey = $('#txtPlaceUniqueKey').val();
            var color = $('#form-add-place').find('input[name=rbEditPlaceColor]:checked').val();
            var lat = $('#hfPlaceLat').val();
            var lng = $('#hfPlaceLng').val();
            var userIds = new Array();
            $('#place-users input[name=EditPlaceUserIds]:checked').each(function (index, elem) {
                userIds.push($(this).val());
            });
            var attributes = getAttributesForType(2, 'EditPlaceAttribute');
            var removePhoto = $('#edit-place-remove-photo').val() === 'true';

            if (tracking.state.isChoosingPlace) {
                var dataPost = {
                    place: {
                        Name: name,
                        Description: description,
                        Contact: contact,
                        Color: color,
                        UniqueKey: uniqueKey,
                        Location: { Lat: lat, Lng: lng },
                        UserIds: userIds,
                        Attributes: attributes
                    }
                };

                handleAjaxFormSubmission('AddPlace', dataPost, btn, status, tracking.strings.MSG_ADD_PLACE_SUCCESS, tracking.strings.MSG_ADD_PLACE_ERROR, function (result) {
                    // add place to map
                    var place = result.Place;
                    processNewPlace(place);

                    if (tracking.data.placeUsers !== null) {
                        tracking.data.placeUsers.push({ PlaceId: place.Id, UserIds: [] });
                    }
                    _.each(userIds, function (userId) {
                        addUserToPlace(userId, place.Id);
                    });

                    uploadFile('fileEditPlacePhoto',
                        '/Home/UploadPlacePhoto',
                        { placeId: place.Id },
                        function (res) {
                            if (res == '' || res == 'empty') { // plugin does not handle empty result properly
                                return;
                            }
                            res = JSON.parse(res);

                            if (res.success == true) {
                                if (res.type != null && res.type != '') {
                                    var item = findPlaceById(place.Id);
                                    item.PhotoType = res.type;
                                    if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
                                        && parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id')) === place.Id) {
                                        $j('#edit-place-photo').find('img').attr('src', '/uploads/images/places/' + place.Id + '_thumb.' + res.type + '?rnd=' + new Date().getTime());
                                        $j('#edit-place-photo a').attr('href', '/uploads/images/places/' + place.Id + '.' + res.type);
                                        $j('#edit-place-photo').show();
                                    }
                                    $('#fileEditPlacePhoto').val('').next('.custom-file-label').text(tracking.strings.NO_FILE_SELECTED);
                                }
                            }
                        }
                    );

                    tracking.data.validation.placeAdd.resetForm();
                    tracking.data.validation.placeAdd.currentForm.reset();
                });
            } else {
                var dataPost = {
                    place: {
                        Id: id,
                        Name: name,
                        Description: description,
                        Contact: contact,
                        Color: color,
                        UniqueKey: uniqueKey,
                        Location: { Lat: lat, Lng: lng },
                        UserIds: userIds,
                        Attributes: attributes,
                        RemovePhoto: removePhoto
                    }
                };

                handleAjaxFormSubmission('UpdatePlace', dataPost, btn, status, tracking.strings.MSG_EDIT_PLACE_SUCCESS, tracking.strings.MSG_EDIT_PLACE_ERROR, function (result) {
                    var place = result.Place;
                    updatePlace(place);

                    var previousUserIds = findPlaceUsersByPlaceId(id);
                    if (previousUserIds != null) {
                        if (previousUserIds.toString() != userIds.toString()) {
                            for (var i = 0; i < previousUserIds.length; i++) {
                                value = previousUserIds[i];
                                if ($j.inArray(value, userIds) == -1) {
                                    removeUserFromPlace(value, id);
                                    i--;
                                }
                            }

                            $j.each(userIds, function (index, value) {
                                if ($j.inArray(value, previousUserIds) == -1) {
                                    addUserToPlace(value, id);
                                }
                            });
                        }
                    }

                    if (removePhoto) {
                        var oldPlace = findPlaceById(place.Id);
                        oldPlace.PhotoType = null;
                    }

                    uploadFile('fileEditPlacePhoto',
                        '/Home/UploadPlacePhoto',
                        { placeId: place.Id },
                        function (res) {
                            if (res == '' || res == 'empty') { // plugin does not handle empty result properly
                                return;
                            }
                            res = JSON.parse(res);

                            if (res.success == true) {
                                if (res.type != null && res.type != '') {
                                    var item = findPlaceById(place.Id);
                                    item.PhotoType = res.type;
                                    if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
                                        && parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id')) === place.Id) {
                                        $j('#edit-place-photo').find('img').attr('src', '/uploads/images/places/' + place.Id + '_thumb.' + res.type + '?rnd=' + new Date().getTime());
                                        $j('#edit-place-photo a').attr('href', '/uploads/images/places/' + place.Id + '.' + res.type);
                                        $j('#edit-place-photo').show();
                                    }
                                    $('#fileEditPlacePhoto').val('').next('.custom-file-label').text(tracking.strings.NO_FILE_SELECTED);
                                }
                            }
                        }
                    );
                });
            }
        });
        $(tracking.data.domNodes.dialogs.addPlace).on('click', '#AddPlaceCancel', function (e) {
            e.preventDefault();
            tracking.data.validation.placeAdd.resetForm();
            tracking.data.validation.placeAdd.currentForm.reset();

            // if we're editing the place, cancel = dialog close
            if (tracking.state.isChoosingPlace) {
                $('#add-place-search').addClass('is-visible');
                $('#form-add-place').removeClass('is-visible');
            } else {
                document.getElementById('panel-dialog-back').click();
            }
            // remove the marker from the map?
        });

        $(tracking.data.domNodes.dialogs.addPlace).on('click', '#btnAddPlaceLatLng', function (e) {
            e.preventDefault();

            if (!tracking.state.isChoosingPlace) {
                return;
            }

            var isFormValid = $(tracking.data.validation.placeFind.currentForm).valid();
            if (!isFormValid) {
                tracking.data.validation.placeFind.focusInvalid();
                return;
            }

            var lat = $('#txtAddPlaceLat').val();
            var lng = $('#txtAddPlaceLng').val();

            var latLng = L.latLng(lat, lng);
            if (isNaN(latLng.lat) || isNaN(latLng.lng)) {
                return;
            }

            updateChosenLocation(latLng, tracking.state.mapClickHandlers.PLACE);
            tracking.map.panTo(latLng);
        });
        $j('#search-place-results').on('click', 'a.highlight-place', function (e) {
            e.preventDefault();

            if (tracking.data.searchPlaceResults == null) {
                return;
            }

            var numRes = $j(this).data('resultNumber');
            if (tracking.data.searchPlaceResults[numRes] == null) {
                return;
            }

            var loc = tracking.data.searchPlaceResults[numRes];
            var latLng = L.latLng(loc.address.lat, loc.address.lon);
            updateChosenLocation(latLng, tracking.state.mapClickHandlers.PLACE);
            tracking.map.panTo(latLng);
            $('#txtPlaceName').val(loc.formatted_address);
        });


        $(tracking.data.domNodes.dialogs.addPlace).on('click', '#btnPlaceSearch', function (e) {
            e.preventDefault();

            var btn = this;
            if (!tracking.options.enableGeocoding) {
                return;
            }

            var isFormValid = $(tracking.data.validation.placeSearch.currentForm).valid();
            if (!isFormValid) {
                tracking.data.validation.placeSearch.focusInvalid();
                return;
            }

            var search = document.getElementById('txtPlaceSearch').value;
            var loading = document.getElementById('search-place-loading');
            loading.classList.add('is-visible');

            btn.disabled = true;

            var status = $('#search-place-status').hide();

            var searchResults = document.getElementById('search-place-results');
            searchResults.classList.remove('is-visible');
            var noResults = document.getElementById('search-place-no-results');
            noResults.classList.remove('is-visible');

            var resultList = document.getElementById('search-place-results-list');
            setChildren(resultList, []);

            tracking.data.searchPlaceResults = null;

            // search for address via external service
            tracking.addressSearch(search, function (success, resultData) {
                btn.disabled = false;
                loading.classList.remove('is-visible');
                searchResults.classList.add('is-visible');
                if (!success) {
                    status.text(tracking.strings.MSG_SEARCH_POSITION_ERROR).show();
                } else {
                    if (resultData.length == 0) {
                        noResults.classList.add('is-visible');
                    } else {
                        tracking.data.searchPlaceResults = resultData;
                        var resultItems = [];
                        for (var i = 0; i < resultData.length; i++) {
                            var result = resultData[i];
                            resultItems.push(el('li', el('a#HighlightPlace' + i + '.highlight-place', { dataset: { resultNumber: i } }, resultData[i].formatted_address)));
                        }
                        setChildren(resultList, resultItems);
                    }
                }
            });
        });

        if (!tracking.options.enableGeocoding) {
            $j('#search-place-address,#searchPositionAddress').hide();
        }

        tracking.data.domNodes.dialogs.sendPosition = document.getElementById('send-position-dialog');

        tracking.data.domNodes.dialogs.garminFormsHistory = document.getElementById('garmin-forms-dialog');
        $(tracking.data.domNodes.dialogs.garminFormsHistory).on('click', '#RefreshGarminFormLogs', function (e) {
            e.preventDefault();
            var assetId = $(tracking.data.domNodes.dialogs.garminFormsHistory).data('assetId');
            var asset = findAssetById(assetId);
            if (asset != null) {
                loadGarminFormsHistory(asset);
            }
        });
        $(tracking.data.domNodes.dialogs.garminFormsHistory).on('click', 'button.ViewGarminSubmission', function (e) {
            e.preventDefault();

            var submissionId = $j(this).data('id');
            var assetId = $(tracking.data.domNodes.dialogs.garminFormsHistory).data('assetId');

            var positionId = $j(this).parent().parent().find('a.location').attr('data-marker');
            if (positionId != null) {
                $j.when(showOrLoadAssetPosition(positionId, assetId))
                    .done(function () {
                        // activate garmin form panel for position
                        $('#accordion-garmin-content').collapse('show');
                    });
            } else {
                loadGarminFormSubmission(assetId, submissionId, null);
            }
        });

        tracking.data.domNodes.infoDialogs.garminSubmission = document.getElementById('garmin-submission-dialog');
        $j(tracking.data.domNodes.infoDialogs.garminSubmission).dialog({
            autoOpen: false,
            modal: false,
            width: 400,
            close: function () {
            },
            create: function () {
                createDialogTitlebar(this);
                replaceDialogButtons(this, $(this).dialog('option', 'buttons'));
            }
        });

        tracking.data.domNodes.dialogs.driverHistory = document.getElementById('drivers-dialog');
        $(tracking.data.domNodes.dialogs.driverHistory).on('click', 'a.history', function (e) {
            e.preventDefault();

            // this loads the asset's history and selects the first position of the trip the driver took
            var assetId = $(tracking.data.domNodes.dialogs.driverHistory).data('assetId');
            var from = moment(this.getAttribute('data-from'), tracking.user.dateFormat);
            var to = moment(this.getAttribute('data-to'), tracking.user.dateFormat);
            $('#txtDateFrom').datetimepicker('setDate', from.toDate());
            if (to == null) {
                $('#txtDateTo').val('');
            } else {
                $('#txtDateTo').datetimepicker('setDate', to.toDate());
            }

            // make all other assets inactive
            // todo: this should be a helper function
            _.each(tracking.data.assets, function (asset) {
                var makeActive = asset.Id === assetId;
                toggleAssetActive(asset.Id, makeActive, false);
            });
            var callback = function () {
                // highlight asset's first position
                var lastPosition = _.last(tracking.data.history.normalizedPositionsByAssetId[assetId]);
                if (lastPosition !== undefined) {
                    highlightPosition(lastPosition.Position.Id, null);
                }
            };
            // todo: this should be a helper function
            // turn to history mode for the trip's date range
            if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                $.when(switchMapMode(false, null, true)).done(callback);
            } else {
                $.when(queryActiveAssets(null)).then(callback);
            }
        });
        $j(tracking.data.domNodes.dialogs.driverHistory).on('click', '#RefreshDrivers', function (e) {
            e.preventDefault();
            var assetId = $j(tracking.data.domNodes.dialogs.driverHistory).data('assetId');
            var asset = findAssetById(assetId);
            if (asset !== null) {
                loadDriverHistory(asset);
            }
        });

        tracking.data.domNodes.dialogs.currentDrivers = document.getElementById('current-drivers-dialog');
        $('#AssetDrivers').on('click', 'button.update-driver', function (e) {
            e.preventDefault();
            var assetId = $(tracking.data.domNodes.dialogs.currentDrivers).data('assetId');
            var asset = findAssetById(assetId);
            var sel = $(this).parent().prev().find('select');
            var driverId = sel.data('driverId');
            var statusId = sel.val();
            updateAssetDriverStatus(asset, driverId, statusId, this, null);
        });
        $(tracking.data.domNodes.dialogs.currentDrivers).on('click', '#LoginDriver', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.currentDriver.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var assetId = $(tracking.data.domNodes.dialogs.currentDrivers).data('assetId');
            var asset = findAssetById(assetId);
            var driverId = $('#LoginDrivers').val();
            if (driverId == null)
                return;
            var statusId = $('#LoginDriversStatus').val();
            var notes = $('#LoginDriversNotes').val();

            if (statusId == 0) {
                loginAssetDriver(asset, driverId, notes, this);
            } else {
                updateAssetDriverStatus(asset, driverId, statusId, this, notes);
            }
        });
        $(tracking.data.domNodes.dialogs.currentDrivers).on('click', '#RefreshAssetDrivers', function (e) {
            e.preventDefault();
            var assetId = $(tracking.data.domNodes.dialogs.currentDrivers).data('assetId');
            var asset = findAssetById(assetId);
            loadAssetDrivers(asset);
        });

        tracking.data.domNodes.dialogs.waypointHistory = document.getElementById('waypoint-history-dialog');

        $(tracking.data.domNodes.dialogs.waypointHistory).on('click', '#RefreshWaypoints', function (e) {
            e.preventDefault();
            var assetId = $(tracking.data.domNodes.dialogs.waypointHistory).data('assetId');
            var asset = findAssetById(assetId);
            if (asset != null) {
                loadWaypointHistory(asset);
            }
        });
        $j('#WaypointHistory').on('click', 'button.delete-waypoint', function (e) {
            e.preventDefault();

            $('#hfDeleteWaypointId').val($j(this).data('waypointId'));
            $('#hfDeleteWaypointAssetId').val($(tracking.data.domNodes.dialogs.waypointHistory).data('assetId'));
            $('#delete-waypoint-modal').modal('show');
        });

        tracking.data.domNodes.dialogs.serviceMeterHistory = document.getElementById('service-meter-dialog');

        tracking.data.domNodes.dialogs.refuelHistory = document.getElementById('refuel-dialog');
        $(tracking.data.domNodes.dialogs.refuelHistory).on('click', 'a.location', function (e) {
            e.preventDefault();

            var positionId = this.getAttribute('data-marker');
            var assetId = parseInt(document.getElementById('hfRefuelAssetId').value);
            showOrLoadAssetPosition(positionId, assetId);
        });
        $('#form-add-refuel').on('click', '#btnAddRefuel', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.refuelAdd.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var fuel = $j('#txtRefuelAmount').val();
            var time = $j('#txtRefuelDate').val();
            var odometer = $j('#txtRefuelOdometer').val();
            var assetId = $j('#hfRefuelAssetId').val();
            var data = {
                assetId: assetId,
                fuel: fuel,
                time: time,
                odometer: odometer
            }
            var status = document.getElementById('add-refuel-status');

            $.when(handleAjaxFormSubmission('AddFillupToAsset', data, this, status, tracking.strings.MSG_ADD_FILLUP_SUCCESS, tracking.strings.MSG_ADD_FILLUP_ERROR))
                .done(function () {
                    $j('#txtRefuelAmount').val('');
                    var asset = findAssetById(assetId);
                    loadFillupHistory(asset);
                });
        });

        tracking.data.domNodes.dialogs.ioHistory = document.getElementById('io-dialog');

        tracking.data.domNodes.dialogs.messageHistory = document.getElementById('message-dialog');
        $(tracking.data.domNodes.dialogs.messageHistory).on('click', '#RefreshMessages', function (e) {
            e.preventDefault();

            var assetId = $(tracking.data.domNodes.dialogs.messageHistory).data('assetId');
            var asset = findAssetById(assetId);
            if (asset == null) {
                return;
            }

            loadMessageHistory(asset);
        });
        $(tracking.data.domNodes.dialogs.messageHistory).on('click', '#MessagesSendCommand', function (e) {
            e.preventDefault();

            var assetId = $(tracking.data.domNodes.dialogs.messageHistory).data('assetId');
            var asset = findAssetById(assetId);
            if (asset == null) {
                return;
            }

            openSendCommandDialog(asset);
        });
        $(tracking.data.domNodes.dialogs.messageHistory).on('click', 'button.delete-message', function (e) {
            e.preventDefault();

            var modal = $('#delete-message-modal').modal('show');
            modal.data('messageId', $(this).data('messageId'));
            modal.data('assetId', $(tracking.data.domNodes.dialogs.messageHistory).data('assetId'));
        });
        $('#MessageHistoryOutbox').on('click', 'a.error', function (e) {
            e.preventDefault();
            var messageId = $j(this).data('messageId');
            var assetId = $(tracking.data.domNodes.dialogs.messageHistory).data('assetId');
            var asset = findAssetById(assetId);
            if (asset === null)
                return;
            if ((_.indexOf(tracking.devices.SKYWAVE_IDP_DUAL_MODE, asset.DeviceId) === -1)
                && (_.indexOf(tracking.devices.SKYWAVE_IDP, asset.DeviceId) === -1))
                return;

            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                // ajax post
                var data = {
                    assetId: id,
                    messageId: messageId,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries
                };
                toggleLoadingMessage(true, 'idp-resend-message');
                $j.ajax({
                    type: 'POST',
                    url: wrapUrl('/services/GPSService.asmx/IDPResendOutgoingMessage'),
                    data: JSON.stringify(data),
                    contentType: 'application/json; charset=utf-8',
                    dataType: 'json',
                    success: function (msg) {
                        var result = msg.d;
                        if (result) {
                            if (result.Success === true) {
                                // refresh message log
                                loadMessageHistory(asset);
                            } else {
                                utility.handleWebServiceError('');
                            }
                        }
                        toggleLoadingMessage(false, 'idp-resend-message');
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    },
                    error: function (xhr, status, error) {
                        utility.handleWebServiceError('');
                        toggleLoadingMessage(false, 'idp-resend-message');
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    }
                });
            };

            openActionDialog(tracking.strings.RESEND_MESSAGE, tracking.strings.RESEND_MESSAGE, callback, asset.Id, false);
        });
        $('#MessageHistoryInbox').on('click', 'a.location', function (e) {
            e.preventDefault();

            for (var i = 0; i < tracking.data.messaging.inbox.length; i++) {
                var message = tracking.data.messaging.inbox[i];
                if (message.Position == null)
                    continue;

                if (message.Position.Id == $(this).attr('data-marker')) {
                    var positionDate = moment(message.Position.Time, tracking.user.dateFormat);
                    positionDate.add({ hours: -12 });
                    // set from date
                    $('#txtDateFrom').val(positionDate.format(tracking.user.dateFormat));
                    positionDate.add({ hours: 24 });
                    $('#txtDateTo').val(positionDate.format(tracking.user.dateFormat));

                    // make sure asset is highlighted
                    toggleAssetActive(message.AssetId, true, true);

                    // if not in history, switch to it
                    var self = this;
                    if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                        // always show the control area if it is hidden
                        if ($('#controlarea').is(':hidden')) {
                            $('#controlarea').slideDown();
                        }
                        // switch view to history with zero sensitivity so the position is not consolidated
                        $.when(switchMapMode(false, 0, true)).done(function () {
                            highlightPosition($(self).attr('data-marker'), null);
                        });
                    } else {
                        // query history with zero sensitivity so the position is not consolidated
                        $.when(queryActiveAssets(0)).then(function () {
                            highlightPosition($j(self).attr('data-marker'), null);
                        });
                    }
                    return;
                }
            }
        });

        $j('#edit-asset-dialog').on('click', '#ResetAssetStatus', function (e) {
            e.preventDefault();
            var assetId = $j('#hfEditAssetId').val();
            var asset = findAssetById(assetId);
            if (asset == null)
                return;

            var data = {
                assetId: asset.Id
            };

            var btn = $j(this);
            btn.addClass('disabled').prop('disabled', true);

            toggleLoadingMessage(true, 'reset-status');
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/ResetAssetStatus'),
                data: JSON.stringify(data),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success != true) {
                            utility.handleWebServiceError(tracking.strings.MSG_UPDATE_STATUS_ERROR);
                        } else {
                            loadAssetStatus(asset);
                        }
                    }
                    toggleLoadingMessage(false, 'reset-status');
                    btn.removeClass('disabled').prop('disabled', false);
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_UPDATE_STATUS_ERROR);
                    toggleLoadingMessage(false, 'reset-status');
                    btn.removeClass('disabled').prop('disabled', false);
                }
            });
        });

        $j('#edit-asset-dialog').on('click', '#UpdateAssetStatus', function (e) {
            e.preventDefault();
            var assetId = $j('#hfEditAssetId').val();
            var asset = findAssetById(assetId);
            if (asset == null)
                return;

            // not currently used... if added back must be updated with new states
            var isIdling = $j('input[name=rbEditAssetIdling]:checked').val();
            var isDwelling = $j('input[name=rbEditAssetDwelling]:checked').val();
            var isIgnitionOn = $j('input[name=rbEditAssetIgnitionOn]:checked').val();
            var isMoving = $j('input[name=rbEditAssetMoving]:checked').val();
            var isSpeeding = $j('input[name=rbEditAssetSpeeding]:checked').val();
            //var isTowing = $j('input[name=rbEditAssetTowing]:checked').val();
            var state = {
                IsIdling: (isIdling == '' ? null : (isIdling === 'true')),
                IsDwelling: (isDwelling == '' ? null : (isDwelling === 'true')),
                IsIgnitionOn: (isIgnitionOn == '' ? null : (isIgnitionOn === 'true')),
                IsMoving: (isMoving == '' ? null : (isMoving === 'true')),
                IsSpeeding: (isSpeeding == '' ? null : (isSpeeding === 'true')),
            };

            var data = {
                assetId: asset.Id,
                state: state
            };

            var btn = $j(this);
            btn.prop('disabled', true);

            toggleLoadingMessage(true, 'update-status');
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/UpdateAssetStatus'),
                data: JSON.stringify(data),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success != true) {
                            utility.handleWebServiceError(tracking.strings.MSG_UPDATE_STATUS_ERROR);
                        } else {
                            updateAssetState(asset, state);
                        }
                    }
                    toggleLoadingMessage(false, 'update-status');
                    btn.prop('disabled', false);
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_UPDATE_STATUS_ERROR);
                    toggleLoadingMessage(false, 'update-status');
                    btn.prop('disabled', false);
                }
            });
        });

        $j('#edit-asset-dialog').on('click', '#RefreshAssetStatus', function (e) {
            e.preventDefault();
            var assetId = $j('#hfEditAssetId').val();
            var asset = findAssetById(assetId);
            loadAssetStatus(asset);
        });

        // add position dialog
        var addPositionButtons = [{
            svgIcon: 'plus',
            buttonType: 'primary',
            icons: { primary: 'ui-icon-plus' },
            text: tracking.strings.BUTTON_ADD_POSITION,
            click: function () {
                var isFormValid = $j('#form-add-position').valid();
                if (!isFormValid) {
                    return;
                }
                var dialog = $j(this);
                var lat = $j('#txtAddPositionLat');
                var lng = $j('#txtAddPositionLng');
                var time = $j('#txtAddPositionDate');
                var speed = $j('#txtAddPositionSpeed');
                var assetId = $j('#hfAddPositionAssetId').val();
                var status = document.getElementById('add-position-status');
                toggleLoadingMessage(true, 'add-position');
                var dataPost = {
                    assetId: assetId,
                    lat: lat.val(),
                    lng: lng.val(),
                    time: time.val(),
                    speed: speed.val()
                };

                $j.ajax({
                    type: 'POST',
                    url: wrapUrl('/services/GPSService.asmx/AddPositionToAsset'),
                    data: JSON.stringify(dataPost),
                    contentType: 'application/json; charset=utf-8',
                    dataType: 'json',
                    success: function (msg) {
                        var result = msg.d;
                        if (result) {
                            if (result.Success == true) {
                                //status.text(tracking.strings.MSG_ADD_POSITION_SUCCESS).addClass('success').removeClass('error').show();
                                //lat.val(''); // clear text field
                                //lng.val('');
                                //speed.val('');
                                //updateLiveAssets();
                                tracking.throttles.updateLiveAssets();
                                formShowSuccessMessage(status, tracking.strings.MSG_ADD_POSITION_SUCCESS);
                                //closeSecondaryPanel();
                            } else {
                                formShowErrorMessage(status, tracking.strings.MSG_ADD_POSITION_ERROR);
                                if (result.ErrorMessage != null && result.ErrorMessage !== '') {
                                    formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                                }
                            }
                        }
                        toggleLoadingMessage(false, 'add-position');
                    },
                    error: function (xhr, status, error) {
                        utility.handleWebServiceError(tracking.strings.MSG_ADD_POSITION_ERROR);
                        toggleLoadingMessage(false, 'add-position');
                    }
                });
            }
        }, closeButton];

        tracking.data.domNodes.dialogs.addPosition = document.getElementById('add-position-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.addPosition, addPositionButtons);

        tracking.data.domNodes.dialogs.assetPositions = document.getElementById('asset-positions-dialog');
        tracking.data.domNodes.dialogs.assetEvents = document.getElementById('asset-events-dialog');
        tracking.data.domNodes.dialogs.assetStatus = document.getElementById('asset-status-dialog');
        tracking.data.domNodes.dialogs.assetAlerts = document.getElementById('asset-alerts-dialog');
        tracking.data.domNodes.dialogs.assetMessages = document.getElementById('asset-messages-dialog');
        tracking.data.domNodes.dialogs.assetChat = document.getElementById('asset-chat-dialog');
        tracking.data.domNodes.dialogs.assetActivity = document.getElementById('asset-activity-dialog');

        // send message dialog
        var sendMessageButtons = [{
            id: 'SendMessageToAsset',
            svgIcon: 'envelope',
            icons: {primary: 'ui-icon-mail-closed'},
            text: tracking.strings.BUTTON_SEND_MESSAGE,
            click: function () {
                var dialog = tracking.data.domNodes.dialogs.sendMessage;
                var bValid = true;
                var text = $j('#txtMessageText');
                var assetId = $j(dialog).data('assetId');
                var groupId = $j(dialog).data('groupId');
                var fenceId = $j(dialog).data('fenceId');
                var isFormValid = $(tracking.data.validation.sendMessage.currentForm).valid();
                if (!isFormValid) {
                    return;
                }
                var notifyOnRead = $j('#chkMessageNotifyOnRead').prop('checked');
                var quickMessageId = $j('#quick-messages').val();

                var dataPost = {};
                var url = 'SendTextMessageToAsset';

                if (assetId !== undefined) {
                    dataPost = {
                        assetId: assetId,
                        text: text.val(),
                        notifyOnRead: notifyOnRead,
                        quickMessageId: quickMessageId
                    };
                }
                if (groupId !== undefined) {
                    url = 'SendTextMessageToGroup';
                    dataPost = {
                        id: groupId === 'all-assets' ? null : groupId,
                        text: text.val(),
                        isForAllAssets: groupId === 'all-assets'
                    };
                }
                if (fenceId !== undefined) {
                    url = 'SendTextMessageToAssets';
                    var assetIds = new Array();
                    $j('input[name=SendMessageAssetIds]:checked', dialog[0]).each(function (index, elem) {
                        assetIds.push($j(this).val());
                    });
                    dataPost = {
                        assetIds: assetIds,
                        text: text.val()
                    };
                }

                var status = document.getElementById('send-message-status');
                var btn = this;
                handleAjaxFormSubmission(url, dataPost, btn, status, tracking.strings.MSG_MESSAGE_SENT_SUCCESS, tracking.strings.MSG_MESSAGE_SENT_ERROR, function () {
                    tracking.data.validation.sendMessage.resetForm();
                    tracking.data.validation.sendMessage.currentForm.reset();
                });
            }
        }, closeButton];

        tracking.data.domNodes.dialogs.sendMessage = document.getElementById('send-message-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.sendMessage, sendMessageButtons);

        $(tracking.data.domNodes.dialogs.sendMessage).on('change', '#quick-messages', function (e) {
            e.preventDefault();
            if ($j(this).val() == '') {
                $j('#message-text-label').text(tracking.strings.TEXT + ':');
                $j('#txtMessageText').addClass('required');
            } else {
                $j('#message-text-label').text(tracking.strings.ADDITIONAL_TEXT + ':');
                $j('#txtMessageText').removeClass('required');
            }
        });

        $j(tracking.data.domNodes.dialogs.sendMessage).on('click', '#beam-set-message', function (e) {
            e.preventDefault();

            $j('#txtMessageText').val($j('#beam-message').val());
        });

        $('#edit-asset-dialog').on('change', '#EditAssetIconSet', function(e) {
            var iconSet = this.value;
            $('.icon-set').removeClass('is-visible');
            var chosenSet = $('#icon-set-' + iconSet).addClass('is-visible');
            //chosenSet.find('.custom-control-input')[0].checked = true;
        });

        // color change for asset icon in edit asset dialog
        $j('#edit-asset-dialog').on('click', 'input[name=rbEditAssetColor]', function () {
            // change the color to the selected value
            var val = $j('#edit-asset-dialog').find('input[name=rbEditAssetColor]:checked').val();
            var icons = $('#edit-asset-dialog input[name=rbEditAssetIcon]');
            _.each(icons, function(icon) {
                var iconImage = $(icon).next().children('img').attr('src', '/markers/' + icon.value + '?color=' + val);
            });
        });

        $j('#edit-asset-address-book-group').on('click', '#SendAddressBookGroup', function (e) {
            e.preventDefault();
            // todo
        });

        $('#edit-asset-drivers-list').on('click', 'label', function (e) {
            var val = $('#' + this.getAttribute('for')).val();
            var driver = findDriverById(parseInt(val));
            if (driver == null) {
                return;
            }
            var info = _.compact([
                includeRowIfNotNull(tracking.strings.DRIVER_ID, driver.DriverId),
                includeRowIfNotNull(tracking.strings.IBUTTON_ID, driver.IButtonId),
                includeRowIfNotNull(tracking.strings.GARMIN_ID, driver.GarminId),
                includeRowIfNotNull(tracking.strings.REGION, driver.Region),
                includeRowIfNotNull(tracking.strings.PHONE_NUMBER, driver.Phone),
                includeRowIfNotNull(tracking.strings.BLOOD_TYPE, driver.BloodType),
                includeRowIfNotNull(tracking.strings.LICENSE_NUMBER, driver.LicenseNumber),
                includeRowIfNotNull(tracking.strings.LICENSE_EXPIRATION, driver.LicenseExpiration),
                includeRowIfNotNull(tracking.strings.LICENSE_RESTRICTION, driver.LicenseRestriction),
                includeRowIfNotNull(tracking.strings.MANAGER, driver.Manager),
                includeRowIfNotNull(tracking.strings.EMERGENCY_CONTACT, driver.EmergencyContact),
                includeRowIfNotNull(tracking.strings.EMERGENCY_CONTACT_NUMBER, driver.EmergencyContactNumber)
            ]);
            setChildren(document.getElementById('driver-information-current').querySelector('tbody'), info);
            if (driver.PhotoType != null && driver.PhotoType != '') {
                $('#driver-information-photo')
                    .attr('src', '/uploads/images/drivers/' + driver.Id + '_thumb.' + driver.PhotoType)
                    .attr('alt', driver.DriverId)
                    .show();
            } else {
                $('#driver-information-photo').hide();
            }
        });

        $j('#edit-asset-dialog').on('change', '#EditAssetIOTemplate', function (e) {
            var templateId = $j(this).val();
            var device = findDeviceById($j('#ddlEditAssetDevice').val());
            var template = null;
            var asset = findAssetById($j('#hfEditAssetId').val());
            if (templateId == '') {
                // no template, use the current selected asset
                $j('#labels-input-analog input, #labels-input-analog select, #labels-input-digital input, #labels-output input').prop('disabled', false); // , #labels-input select
                setLabelsByTemplate(device, asset, template);
            } else {
                for (var i = 0; i < device.IOTemplates.length; i++) {
                    if (device.IOTemplates[i].Id == templateId) {
                        template = device.IOTemplates[i];
                        break;
                    }
                }
                setLabelsByTemplate(device, asset, template);
                // disable all inputs
                $j('#labels-input-digital input, #labels-input-analog input, #labels-input-analog select, #labels-output input').prop('disabled', true); // , #labels-input select
            }
            // enable/disable overrides
            $j('#labels-input-analog select.behavior').each(function (index, elem) {
                toggleAnalogIOBehavior($j(this));
            });
        });

        $j('#edit-asset-dialog').on('click', '#EditAssetCurrentDrivers', function (e) {
            e.preventDefault();
            var asset = findAssetById($j('#hfEditAssetId').val());
            openCurrentDriversDialog(asset);
        });

        // this default button is necessary because we also have buttons within the form itself that come before submit
        // todo: ensure there is only one button with role=submit for the form
        // the rest should be role=button
        $j('#edit-asset-dialog').on('click', '#EditAssetDefaultButton', function (e) {
            e.preventDefault();
            $j('#EditAssetSave').trigger('click');
        });

        $j('#edit-asset-canned-message-group').on('click', '#SendCannedMessageGroup', function (e) {
            e.preventDefault();

            var id = $j('#hfEditAssetId').val();
            var dialog = $j('#edit-asset-dialog');
            var status = $j('#edit-asset-status');
            status.text('').hide(); // clear previous status
            var cannedMessageGroupId = $j('#ddlEditAssetCannedMessageGroup').val();
            var dataPost = {
                assetId: id,
                cannedMessageGroupId: cannedMessageGroupId
            }
            $j(this).prop('disabled', true);
            toggleLoadingMessage(true, 'send-canned-message');
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/SendCannedMessageGroupToAsset'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    $j('#SendCannedMessageGroup').prop('disabled', false);
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            status.text('Canned message group sent to asset.').addClass('success').removeClass('error').show();
                        }
                    }
                    toggleLoadingMessage(false, 'send-canned-message');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError('An error occurred while sending the canned message group.');
                    toggleLoadingMessage(false, 'send-canned-message');
                    $j('#SendCannedMessageGroup').prop('disabled', false);
                }
            });
        });

        $j('#edit-asset-address-book-group').on('click', '#SendAddressBookGroup', function (e) {
            e.preventDefault();

            var id = $j('#hfEditAssetId').val();
            var dialog = $j('#edit-asset-dialog');
            var status = $j('#edit-asset-status');
            status.text('').hide(); // clear previous status
            var addressBookGroupId = $j('#ddlEditAssetAddressBookGroup').val();
            var dataPost = {
                assetId: id,
                addressBookGroupId: addressBookGroupId
            }
            $j(this).prop('disabled', true);
            toggleLoadingMessage(true, 'send-address-book');
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/SendAddressBookGroupToAsset'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    $j('#SendAddressBookGroup').prop('disabled', false);
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            status.text('Address book group sent to asset.').addClass('success').removeClass('error').show();
                        }
                    }
                    toggleLoadingMessage(false, 'send-address-book');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError('An error occurred while sending the address book group.');
                    toggleLoadingMessage(false, 'send-address-book');
                    $j('#SendAddressBookGroup').prop('disabled', false);
                }
            });
        });

        // edit asset dialog
        var editAssetButtons = [{
            id: 'EditAssetSave',
            icons: { primary: 'ui-icon-disk' },
            text: tracking.strings.SAVE_CHANGES,
            buttonType: 'primary',
            click: function () {
                var isFormValid = $(tracking.data.validation.editAsset.currentForm).valid();
                if (!isFormValid) {
                    tracking.data.validation.editAsset.focusInvalid();
                    return;
                }
                var btn = this;
                btn.disabled = true;
                var dialog = $j('#edit-asset-dialog');
                var id = $j('#hfEditAssetId').val();
                var status = document.getElementById('edit-asset-status');
                toggleLoadingMessage(true, 'edit-asset');
                var name = $j('#txtEditAssetName').val();
                var removePhoto = false;
                if (!tracking.user.limitAssetEditing) {
                    var uniqueId = $j('#txtEditAssetUniqueId').val();
                    var secondaryUniqueId = $j('#txtEditAssetSecondaryUniqueId').val();
                    var deviceId;
                    if (tracking.state.isAddingAsset) {
                        deviceId = $j('#ddlEditAssetDevice').val();
                    } else {
                        var asset = findAssetById(id);
                        if (asset != null) {
                            deviceId = asset.DeviceId;
                        }
                    }
                    var color = $j('input[name=rbEditAssetColor]:checked', dialog[0]).val();
                    var sensitivity = $j('#txtEditAssetSensitivity').val();
                    var type = $j('input[name=rbEditAssetIcon]:checked', dialog[0]).val();
                    if (type == null) {
                        type = 'Upload';
                    }

                    var inmarsatc = {
                        DNID: $j('#txtEditAssetInmarsatCDNID').val(),
                        MemberNumber: $j('#txtEditAssetInmarsatCMemberNumber').val()
                    };
                    if ($j('#rbInmarsatCActivateTrue').prop('checked')) {
                        inmarsatc = null;
                    }

                    // extra fields
                    var driver = $j('#txtEditAssetDriver').val();
                    var mission = $j('#txtEditAssetMission').val();
                    if (mission === undefined) {
                        mission = null;
                    }
                    var phoneNumber = $j('#txtEditAssetPhoneNumber').val();
                    if (phoneNumber === undefined) {
                        phoneNumber = null;
                    }
                    var plateNumber = $j('#txtEditAssetPlateNumber').val();
                    if (plateNumber === undefined) {
                        plateNumber = null;
                    }
                    var licenseNumber = $j('#txtEditAssetLicenseNumber').val();
                    if (licenseNumber === undefined) {
                        licenseNumber = null;
                    }
                    var nationalIdentityCardNumber = $j('#txtEditAssetNationalIdentityCardNumber').val();
                    if (nationalIdentityCardNumber === undefined) {
                        nationalIdentityCardNumber = null;
                    }
                    var softwareVersion = $j('#txtEditAssetSoftwareVersion').val();
                    var notes = $j('#txtEditAssetNotes').val();
                    var vehicleMakeAndModel = $j('#txtEditAssetVehicleMakeAndModel').val();
                    var vehiclePurchaseDate = $j('#txtEditAssetVehiclePurchaseDate').val();
                    var vehicleVIN = $j('#txtEditAssetVehicleVIN').val();
                    var fuelEfficiency = $j('#txtEditAssetFuelEfficiency').val();
                    var fuelEfficiencyType = $j('input[name=rbEditAssetFuelEfficiency]:checked', dialog[0]).val();
                    var drawLines = $j('input[name=rbDrawLinesBetweenPositions]:checked', dialog[0]).val();
                    drawLines = (drawLines === 'true'); // string to bool
                    var snapLinesToRoads = $j('input[name=rbSnapLinesToRoads]:checked', dialog[0]).val();
                    snapLinesToRoads = (snapLinesToRoads === 'true'); // string to bool
                    if (!drawLines) {
                        snapLinesToRoads = false;
                    }
                    var microUseCorrectedCourse = $j('input[name=rbMicroUseCorrectedCourse]:checked', dialog[0]).val();
                    microUseCorrectedCourse = (microUseCorrectedCourse === 'true'); // string to bool
                    var suppressEmergency = $j('input[name=rbSuppressEmergency]:checked', dialog[0]).val();
                    suppressEmergency = (suppressEmergency === 'true'); // string to bool
                    var prioritizeIncomingMessages = $j('input[name=rbPrioritizeIncomingMessages]:checked', dialog[0]).val();
                    prioritizeIncomingMessages = (prioritizeIncomingMessages === 'true');
                    var destinationId = $j('#txtEditAssetDestinationId').val();
                    var isGroupLocationSharingEnabled = $j('#EditAssetAllowGroupLocationSharing').prop('checked');
                    var receiveKey = $j('#txtEditAssetReceiveKey').val();
                    var transmitKey = $j('#txtEditAssetTransmitKey').val();
                    var analogTolerance = $j('#txtEditAssetAnalogTolerance').val();
                    var srid = $j('#ddlAssetSRID').val();
                    var cannedMessageGroupId = $j('#ddlEditAssetCannedMessageGroup').val();
                    var addressBookGroupId = $j('#ddlEditAssetAddressBookGroup').val();
                    var driverStatusTemplateId = $j('#ddlEditAssetDriverStatusTemplate').val();
                    var quickMessageFromMobileTemplateId = $j('#ddlEditAssetQuickMessageFromMobileTemplate').val();
                    var quickMessageToMobileTemplateId = $j('#ddlEditAssetQuickMessageToMobileTemplate').val();
                    var ioTemplateId = $j('#EditAssetIOTemplate').val();
                    var gatewayAccountId = $j('#ddlEditAssetGatewayAccount').val();
                    var secondaryGatewayAccountId = $('#ddlEditAssetSecondaryGatewayAccount').val();
                    var pollingInterval = $j('#txtEditAssetPollingInterval').val();
                    var pollingIntervalType = $j('#ddlEditAssetPollingInterval').val();
                    if (pollingInterval != '') {
                    	switch (pollingIntervalType) {
                    		case 'd':
                    			pollingInterval *= 86400;
								break;
                    		case 'h':
                    			pollingInterval *= 3600;
								break;
                    		case 'm':
                    			pollingInterval *= 60;
                    			break;
							default:
								break;
                    	}
                    }

                    var idpGatewayBehavior = $j('input[name=EditAssetIDPGatewayBehavior]:checked', dialog[0]).val();
                    var idpGatewayTimeout = $j('#EditAssetIDPGatewayBehaviorTimeout').val();
                    var idpGatewayRetries = $j('#EditAssetIDPGatewayBehaviorRetries').val();
                    if (idpGatewayBehavior == 3) {
                        idpGatewayRetries = $j('#EditAssetIDPGatewayBehaviorCellRetries').val();
                    }
                    var configuration = $j('#ddlEditAssetConfiguration').val();
                    var softwarePackageId = $j('#ddlEditAssetSoftwarePackage').val();
                    var imei = $j('#txtEditAssetIMEI').val();
                    var serialNumber = $j('#txtEditAssetSerialNumber').val();
                    var inmarsatSerialNumber = $j('#txtEditAssetInmarsatCSerialNumber').val();
                    var inmarsatCSubaddress = $j('#txtEditAssetInmarsatCSubaddress').val();
                    if ($j.inArray(deviceId, tracking.devices.INMARSAT_C) !== -1 && (inmarsatSerialNumber != '')) { // use for both
                        serialNumber = inmarsatSerialNumber;
                    }
                    var simPINUnlock = $j('#txtEditAssetSIMPINUnlock').val();
                    var simICCID = $j('#txtEditAssetSIMICCID').val();
                    var simPhoneNumber = $j('#txtEditAssetSIMPhoneNumber').val();
                    var simPIN = $j('#txtEditAssetSIMPIN').val();
                    var simProvider = $j('#txtEditAssetSIMProvider').val();
                    var vesselName = $j('#txtEditAssetVesselName').val();
                    var vesselCallSign = $j('#txtEditAssetVesselCallSign').val();
                    var vesselFlagRegistry = $j('#txtEditAssetVesselFlagRegistry').val();
                    var vesselIMONumber = $j('#txtEditAssetVesselIMONumber').val();
                    var vesselTonnage = $j('#txtEditAssetVesselTonnage').val();
                    var vesselClass = $j('#txtEditAssetVesselClass').val();
                    var vesselSkipper = $j('#txtEditAssetVesselSkipper').val();
                    var vesselMMSI = $j('#txtEditAssetVesselMMSI').val();
                    var customMessage = $j('#txtEditAssetCustomMessage').val();
                    var contactIds = new Array();
                    $j('input[name=EditAssetContactIds]:checked', dialog[0]).each(function (index, elem) {
                        contactIds.push($j(this).val());
                    });

                    var isOutOfService = $j('input[name=rbEditAssetIsOutOfService]:checked', dialog[0]).val();
                    isOutOfService = (isOutOfService === 'true'); // string to bool

                    var hideAltitude = $j('#chkHideInformationAltitude').prop('checked');
                    var hideAddress = $j('#chkHideInformationAddress').prop('checked');
                    var hideCourse = $j('#chkHideInformationCourse').prop('checked');
                    var hideSpeed = $j('#chkHideInformationSpeed').prop('checked');
                    var hideAccuracy = $j('#chkHideInformationAccuracy').prop('checked');
                    var hideFlags = $j('#chkHideInformationFlags').prop('checked');
                    var sendAddressBookAndCannedMessagesOta = $j('#EditAssetSyncOTA').prop('checked');

                    var password = $j('#txtEditAssetDevicePwrd').val();
                    var mobileUsername = $j('#txtEditAssetMobileUnme').val();
                    var mobilePassword = $j('#txtEditAssetMobilePwrd').val();

                    var groupIds = new Array();
                    $j('input[name=EditAssetGroupIds]:checked', dialog[0]).each(function (index, elem) {
                        groupIds.push($j(this).val());
                    });
                    var assetIds = new Array();
                    $j('input[name=EditAssetAssetIds]:checked', dialog[0]).each(function (index, elem) {
                    	assetIds.push(parseInt($j(this).val()));
                    });
                    var userIds = new Array();
                    $j('input[name=EditAssetUserIds]:checked', dialog[0]).each(function (index, elem) {
                        userIds.push($j(this).val());
                    });
                    var driverIds = new Array();
                    $j('input[name=EditAssetDriverIds]:checked').each(function (index, elem) {
                        driverIds.push(parseInt($j(this).val()));
                    });
                    var driverGroupIds = new Array();
                    $j('input[name=EditAssetDriverGroupIds]:checked').each(function (index, elem) {
                        driverGroupIds.push($j(this).val());
                    });

                    var driverDeviceType = null;
                    $j('input[name=EditAssetDriverDeviceType]:checked', dialog[0]).each(function (index, elem) {
                        if (driverDeviceType == null)
                            driverDeviceType = parseInt($j(this).val());
                        else
                            driverDeviceType += parseInt($j(this).val());
                    });

                    var garminFormIds = new Array();
                    var formIds = new Array();
                    $j('input[name=EditAssetGarminIds]:checked').each(function (index, elem) {
                        garminFormIds.push({ id: $j(this).val(), position: index+1 });
                        formIds.push(parseInt($j(this).val()));
                    });

                    if ($('#edit-asset-remove-photo').val() === 'true') {
                        removePhoto = true;
                    }

                    var deviceLabel = findDeviceById(deviceId);
                    var inputLabels = [];
                    // normal skywave 6xx device would have 4 inputs, but in ARC configuration we need to apply 8
                    //for (var i = 0; i < deviceLabel.InputPinCount; i++) {
                    var num = 0;
                    if (deviceLabel.InputConfigurationPinsDigital != null) {
                        for (var i = 0; i < deviceLabel.InputConfigurationPinsDigital.length; i++) {
                            num++;
                            var label = {
                                Number: num,
                                Label: $j('#EditAssetInputLabel' + num).val(),
                                InputPin: $j('#EditAssetInputLabelPin' + num).val(),
                                OnLabel: $j('#EditAssetInputOnLabel' + num).val(),
                                OnIsDisabled: !($j('#EditAssetInputOnEnabled' + num).prop('checked')),
                                OffLabel: $j('#EditAssetInputOffLabel' + num).val(),
                                OffIsDisabled: !($j('#EditAssetInputOffEnabled' + num).prop('checked')),
                                PulseLabel: '',
                                PulseIsDisabled: false,
                                IsNormallyOpen: $j('#EditAssetInputLabelNormallyOpen' + num).prop('checked'),
                                IsEmergency: $j('#EditAssetInputLabelEmergency' + num).prop('checked'),
                                IsFuel: $j('#EditAssetInputLabelFuel' + num).prop('checked'),
                                AnalogBehavior: $j('#EditAssetInputAnalogBehavior' + num).val(),
                                AnalogUnit: $j('#EditAssetInputAnalogUnit' + num).val(),
                                AnalogFactor: $j('#EditAssetInputAnalogFactor' + num).val(),
                                MaxVoltage: $j('#EditAssetInputMaxVoltage' + num).val(),
                                FuelCapacity: $j('#EditAssetInputFuelCapacity' + num).val(),
                                WeightMax: $j('#EditAssetInputWeightMax' + num).val()
                            };
                            inputLabels.push(label);
                        }
                    }
                    if (deviceLabel.InputConfigurationPinsAnalog != null) {
                        for (var i = 0; i < deviceLabel.InputConfigurationPinsAnalog.length; i++) {
                            num++;
                            var label = {
                                Number: num,
                                Label: $j('#EditAssetInputLabel' + num).val(),
                                InputPin: $j('#EditAssetInputLabelPin' + num).val(),
                                OnLabel: $j('#EditAssetInputOnLabel' + num).val(),
                                OnIsDisabled: !($j('#EditAssetInputOnEnabled' + num).prop('checked')),
                                OffLabel: $j('#EditAssetInputOffLabel' + num).val(),
                                OffIsDisabled: !($j('#EditAssetInputOffEnabled' + num).prop('checked')),
                                PulseLabel: '',
                                PulseIsDisabled: false,
                                IsNormallyOpen: $j('#EditAssetInputLabelNormallyOpen' + num).prop('checked'),
                                IsEmergency: $j('#EditAssetInputLabelEmergency' + num).prop('checked'),
                                IsFuel: $j('#EditAssetInputLabelFuel' + num).prop('checked'),
                                AnalogBehavior: $j('#EditAssetInputAnalogBehavior' + num).val(),
                                AnalogUnit: $j('#EditAssetInputAnalogUnit' + num).val(),
                                AnalogFactor: $j('#EditAssetInputAnalogFactor' + num).val(),
                                MaxVoltage: $j('#EditAssetInputMaxVoltage' + num).val(),
                                FuelCapacity: $j('#EditAssetInputFuelCapacity' + num).val(),
                                WeightMax: $j('#EditAssetInputWeightMax' + num).val()
                            };
                            inputLabels.push(label);
                        }
                    }

                    var outputLabels = [];
                    for (var i = 0; i < deviceLabel.OutputPinCount; i++) {
                        var num = i + 1;
                        var label = {
                            Number: num,
                            Label: $j('#EditAssetOutputLabel' + num).val(),
                            InputPin: '',
                            OnLabel: $j('#EditAssetOnLabel' + num).val(),
                            OnIsDisabled: !($j('#EditAssetOnEnabled' + num).prop('checked')),
                            OffLabel: $j('#EditAssetOffLabel' + num).val(),
                            OffIsDisabled: !($j('#EditAssetOffEnabled' + num).prop('checked')),
                            PulseLabel: $j('#EditAssetPulseLabel' + num).val(),
                            PulseIsDisabled: !($j('#EditAssetPulseEnabled' + num).prop('checked'))
                        };
                        outputLabels.push(label);
                    }

                    var attributes = getAttributesForType(0, 'EditAssetAttribute');
                }
                var dataPostName = {
                    assetId: id,
                    name: name
                }
                var dataPost = {
                    assetId: id,
                    name: name,
                    uniqueId: uniqueId,
                    secondaryUniqueId: secondaryUniqueId,
                    color: color,
                    sensitivity: sensitivity,
                    type: type,
                    deviceId: deviceId,
                    mission: mission === undefined ? null : mission,
                    driver: driver === undefined ? null : driver,
                    phoneNumber: phoneNumber === undefined ? null : phoneNumber,
                    plateNumber: plateNumber === undefined ? null : plateNumber,
                    groupIds: groupIds,
                    userIds: userIds,
                    driverIds: driverIds,
                    driverGroupIds: driverGroupIds,
                    fuelEfficiency: fuelEfficiency,
                    fuelEfficiencyType: fuelEfficiencyType,
                    drawLines: drawLines,
                    snapLinesToRoads: snapLinesToRoads,
                    microUseCorrectedCourse: microUseCorrectedCourse,
                    suppressEmergency: suppressEmergency,
                    prioritizeIncomingMessages: prioritizeIncomingMessages,
                    destinationId: destinationId,
                    receiveKey: receiveKey,
                    transmitKey: transmitKey,
                    srid: srid,
                    analogTolerance: analogTolerance,
                    cannedMessageGroupId: cannedMessageGroupId,
                    addressBookGroupId: addressBookGroupId,
                    driverStatusTemplateId: driverStatusTemplateId,
                    quickMessageFromMobileTemplateId: quickMessageFromMobileTemplateId,
                    quickMessageToMobileTemplateId: quickMessageToMobileTemplateId,
                    gatewayAccountId: gatewayAccountId,
                    secondaryGatewayAccountId: secondaryGatewayAccountId,
                    idpGatewayBehavior: idpGatewayBehavior,
                    idpGatewayTimeout: idpGatewayTimeout,
                    idpGatewayRetries: idpGatewayRetries,
                    ioTemplateId: ioTemplateId,
                    configuration: configuration,
                    softwarePackageId: softwarePackageId,
                    inputLabels: inputLabels,
                    outputLabels: outputLabels,
                    simICCID: simICCID === undefined ? null : simICCID,
                    simPhoneNumber: simPhoneNumber === undefined ? null : simPhoneNumber,
                    simPIN: simPIN === undefined ? null : simPIN,
                    simPINUnlock: simPINUnlock === undefined ? null : simPINUnlock,
                    simProvider: simProvider === undefined ? null : simProvider,
                    imei: imei === undefined ? null : imei,
                    serialNumber: serialNumber === undefined ? null : serialNumber,
                    licenseNumber: licenseNumber === undefined ? null : licenseNumber,
                    nationalIdentityCardNumber: nationalIdentityCardNumber === undefined ? null : nationalIdentityCardNumber,
                    softwareVersion: softwareVersion === undefined ? null : softwareVersion,
                    vehicleMakeAndModel: vehicleMakeAndModel === undefined ? null : vehicleMakeAndModel,
                    vehiclePurchaseDate: vehiclePurchaseDate === undefined ? null : vehiclePurchaseDate,
                    vehicleVIN: vehicleVIN === undefined ? null : vehicleVIN,
                    notes: notes === undefined ? null : notes,
                    vesselName: vesselName === undefined ? null : vesselName,
                    vesselFlagRegistry: vesselFlagRegistry === undefined ? null : vesselFlagRegistry,
                    vesselCallSign: vesselCallSign === undefined ? null : vesselCallSign,
                    vesselIMONumber: vesselIMONumber === undefined ? null : vesselIMONumber,
                    vesselClass: vesselClass === undefined ? null : vesselClass,
                    vesselTonnage: vesselTonnage === undefined ? null : vesselTonnage,
                    vesselSkipper: vesselSkipper === undefined ? null : vesselSkipper,
                    vesselMMSI: vesselMMSI === undefined ? null : vesselMMSI,
                    customMessage: customMessage,
                    contactIds: contactIds,
                    isOutOfService: isOutOfService,
                    hideAltitude: hideAltitude,
                    hideAddress: hideAddress,
                    hideSpeed: hideSpeed,
                    hideAccuracy: hideAccuracy,
                    hideCourse: hideCourse,
                    hideFlags: hideFlags,
                    garminFormIds: garminFormIds,
                    password: password,
                    driverDeviceType: driverDeviceType,
                    attributes: attributes,
                    inmarsatc: inmarsatc,
                    pollingInterval: pollingInterval,
                    inmarsatCSubaddress: inmarsatCSubaddress,
					mobileUsername: mobileUsername,
					mobilePassword: mobilePassword,
                    assetIds: assetIds,
                    sendAddressBookAndCannedMessagesOta: sendAddressBookAndCannedMessagesOta,
                    removePhoto: removePhoto,
                    isGroupLocationSharingEnabled: isGroupLocationSharingEnabled
                };

                var url = '/services/GPSService.asmx/UpdateAssetConfiguration';
                if (tracking.state.isAddingAsset) {
                    url = '/services/GPSService.asmx/AddAsset';
                    // exclude parameters not included in adding asset
                    delete dataPost.assetId;
                    delete dataPost.ioTemplateId; // wha...?
                    delete dataPost.removePhoto;
                }
                if (tracking.user.limitAssetEditing) {
                    url = '/services/GPSService.asmx/UpdateAssetName';
                    dataPost = dataPostName;
                }
                $j.ajax({
                    type: 'POST',
                    url: wrapUrl(url),
                    data: JSON.stringify(dataPost),
                    contentType: 'application/json; charset=utf-8',
                    dataType: 'json',
                    success: function (msg) {
                        btn.disabled = false;
                        var result = msg.d;
                        if (result) {
                            if (result.Success == true) {
                                var asset = null;
                                var isAssetAdded = false;
                                if (tracking.state.isAddingAsset) {
                                    // create asset and link to groups
                                    if (result.Asset != null) {
                                        asset = result.Asset;
                                        processNewAsset(asset);
                                        formShowSuccessMessage(status, tracking.strings.MSG_ADD_ASSET_SUCCESS);
                                        isAssetAdded = true;
                                    }
                                } else {
                                    formShowSuccessMessage(status, tracking.strings.MSG_EDIT_ASSET_SUCCESS);
                                    asset = findAssetById(id);
                                    var previousSensitivity = asset.Sensitivity;
                                    var previousGroupIds = asset.GroupIds;
                                    var previousAssetIds = asset.AssetIds;
                                    var previousUserIds = findAssetUsersByAssetId(id);
                                    var previousDriverIds = findAssetDriversByAssetId(id);
                                    var previousDriverGroupIds = findAssetDriverGroupIdsByAssetId(parseInt(id));
                                    var previousColor = asset.Color;
                                    var previousUniqueId = asset.UniqueId;
                                    var previousName = asset.Name;
                                    var previousClass = asset.Class;
                                    var previousDrawLines = asset.DrawLinesBetweenPositions;
                                    var previousSnapLinesToRoads = asset.SnapLinesToRoads;
                                    var previousSRID = asset.DisplaySRID;
                                    var previousService = asset.IsOutOfService;

                                    asset.Name = name;
                                    if (!tracking.user.limitAssetEditing) {
                                        asset.UniqueId = uniqueId;
                                        asset.SecondaryUniqueId = secondaryUniqueId;
                                        asset.Color = color;
                                        asset.ColorSorted = convertHexToSortable(convertNamedColorToHex(asset.Color));
                                        asset.Class = type;
                                        asset.Sensitivity = sensitivity;
                                        asset.Type = type;
                                        asset.DeviceId = parseInt(deviceId);
                                        asset.Configuration = configuration;

                                        asset.IMEI = imei;
                                        asset.SerialNumber = serialNumber;
                                        asset.SIMICCID = simICCID;
                                        asset.SIMPhoneNumber = simPhoneNumber;
                                        asset.SIMPIN = simPIN;
                                        asset.SIMPINUnlock = simPINUnlock;
                                        asset.SIMProvider = simProvider;
                                        asset.Driver = driver;
                                        asset.Mission = mission;
                                        asset.PhoneNumber = phoneNumber;
                                        asset.PlateNumber = plateNumber;
                                        asset.LicenseNumber = licenseNumber;
                                        asset.NationalIdentityCardNumber = nationalIdentityCardNumber;
                                        asset.Notes = notes;
                                        asset.CustomMessage = customMessage;
                                        asset.ContactIds = contactIds;
                                        asset.SoftwareVersion = softwareVersion;
                                        asset.VehicleMakeAndModel = vehicleMakeAndModel;
                                        asset.VehiclePurchaseDate = vehiclePurchaseDate;
                                        asset.VehicleVIN = vehicleVIN;
                                        asset.VesselName = vesselName;
                                        asset.VesselFlagRegistry = vesselFlagRegistry;
                                        asset.VesselCallSign = vesselCallSign;
                                        asset.VesselIMONumber = vesselIMONumber;
                                        asset.VesselTonnage = vesselTonnage;
                                        asset.VesselClass = vesselClass;
                                        asset.VesselSkipper = vesselSkipper;
                                        asset.VesselMMSI = vesselMMSI;
                                        asset.DriverDeviceType = driverDeviceType;
                                        asset.Attributes = attributes;

                                        asset.InmarsatC = inmarsatc;

                                        asset.GroupIds = groupIds;
										asset.AssetIds = assetIds;
                                        asset.FuelEfficiency = convertFuelEfficiencyToStandard(fuelEfficiency, fuelEfficiencyType);
                                        asset.DestinationId = destinationId;
                                        asset.DrawLinesBetweenPositions = drawLines;
                                        asset.SnapLinesToRoads = snapLinesToRoads;
                                        asset.MicroUseCorrectedCourse = microUseCorrectedCourse;
                                        asset.SuppressEmergency = suppressEmergency;
                                        asset.PrioritizeIncomingMessages = prioritizeIncomingMessages;
                                        asset.IsOutOfService = isOutOfService;
                                        if (asset.HideAddress !== hideAddress) {
                                            // expire any cached listings with the address shown
                                            // this may make more sense by setting a class on the listing to show/hide addresses
                                            // instead of re-creating dom list items
                                            console.log('remove listing cache');
                                            var assetPositions = _.union(tracking.data.live.normalizedPositionsByAssetId[asset.Id], tracking.data.history.normalizedPositionsByAssetId[asset.Id]);
                                            _.each(assetPositions, function (item) {
                                                if (tracking.data.domNodes.positionListingById[item.Position.Id] !== undefined) {
                                                    delete tracking.data.domNodes.positionListingById[item.Position.Id];
                                                }
                                                if (tracking.data.domNodes.sharedView.positionListingById[item.Position.Id] !== undefined) {
                                                    delete tracking.data.domNodes.sharedView.positionListingById[item.Position.Id];
                                                }
                                            });
                                            var assetEvents = _.union(tracking.data.live.normalizedEventsByAssetId[asset.Id], tracking.data.history.normalizedEventsByAssetId[asset.Id]);
                                            _.each(assetEvents, function (item) {
                                                if (tracking.data.domNodes.eventListingById[item.Event.Id] !== undefined) {
                                                    delete tracking.data.domNodes.eventListingById[item.Event.Id];
                                                }
                                                if (tracking.data.domNodes.sharedView.eventListingById[item.Event.Id] !== undefined) {
                                                    delete tracking.data.domNodes.sharedView.eventListingById[item.Event.Id];
                                                }
                                            });
                                            var assetMessages = _.union(tracking.data.live.normalizedMessagesByAssetId[asset.Id], tracking.data.history.normalizedMessagesByAssetId[asset.Id]);
                                            _.each(assetMessages, function (item) {
                                                if (item.Chat !== undefined) {
                                                    if (tracking.data.domNodes.messageListingById[item.Chat.Id] !== undefined) {
                                                        delete tracking.data.domNodes.messageListingById[item.Chat.Id];
                                                    }
                                                    if (tracking.data.domNodes.sharedView.messageListingById[item.Chat.Id] !== undefined) {
                                                        delete tracking.data.domNodes.sharedView.messageListingById[item.Chat.Id];
                                                    }
                                                } else if (item.Message !== undefined) {
                                                    if (tracking.data.domNodes.messageListingById[item.Message.Id] !== undefined) {
                                                        delete tracking.data.domNodes.messageListingById[item.Message.Id];
                                                    }
                                                    if (tracking.data.domNodes.sharedView.messageListingById[item.Message.Id] !== undefined) {
                                                        delete tracking.data.domNodes.sharedView.messageListingById[item.Message.Id];
                                                    }
                                                }
                                            });
                                        }
                                        asset.HideAddress = hideAddress;
                                        asset.HideAltitude = hideAltitude;
                                        asset.HideSpeed = hideSpeed;
                                        asset.HideAccuracy = hideAccuracy;
                                        asset.HideCourse = hideCourse;
                                        asset.HideFlags = hideFlags;
                                        asset.IsGroupLocationSharingEnabled = isGroupLocationSharingEnabled;
                                        asset.DisplaySRID = srid;
                                        asset.AnalogTolerance = analogTolerance;
                                        asset.CannedMessageGroupId = cannedMessageGroupId;
                                        asset.IOTemplateId = ioTemplateId;
                                        asset.AddressBookGroupId = addressBookGroupId;
                                        asset.DriverStatusTemplateId = driverStatusTemplateId;
                                        asset.QuickMessageFromMobileTemplateId = quickMessageFromMobileTemplateId;
                                        asset.QuickMessageToMobileTemplateId = quickMessageToMobileTemplateId;
                                        asset.GatewayAccountId = gatewayAccountId;
                                        asset.SecondaryGatewayAccountId = secondaryGatewayAccountId;
										asset.PollingInterval = pollingInterval;
                                        asset.DefaultIDPGateway = idpGatewayBehavior;
                                        asset.DefaultIDPGatewayTimeout = idpGatewayTimeout;
                                        asset.DefaultIDPGatewayRetries = idpGatewayRetries;
                                        asset.InputLabels = inputLabels;
                                        asset.OutputLabels = outputLabels;
										asset.MobileUsername = mobileUsername;
                                        asset.SoftwarePackageId = softwarePackageId;

                                        if (receiveKey != '') {
                                            asset.ReceiveEncryptionKey = 'placeholder';
                                        } else {
                                            asset.ReceiveEncryptionKey = '';
                                        }
                                        if (transmitKey != '') {
                                            asset.TransmitEncryptionKey = 'placeholder';
                                        } else {
                                            asset.TransmitEncryptionKey = '';
                                        }
                                        if (password != '') {
                                            asset.Password = 'placeholder';
                                        } else {
                                            asset.Password = '';
                                        }

                                        if (mobilePassword != '') {
                                        	asset.MobilePassword = 'placeholder';
                                        } else {
                                        	asset.MobilePassword = '';
                                        }

                                        if (removePhoto) {
                                            asset.PhotoType = null;
                                        }

                                        var isThisAssetActive = !isItemIncluded(tracking.user.displayPreferences.hiddenAssets, asset.Id);

                                        // check for changes
                                        if ((previousSensitivity != sensitivity) || (previousUniqueId != uniqueId) || (srid != previousSRID)) {
                                            requeryPositionsForAsset(asset);
                                        }
                                        if (previousGroupIds.toString() != groupIds.toString()) {
                                            // changes in group associations
                                            for (var i = 0; i < previousGroupIds.length; i++) {
                                                value = previousGroupIds[i];
                                                if ($j.inArray(value, groupIds) == -1) {
                                                    removeAssetFromGroup(asset, value);
                                                }
                                            }

                                            $j.each(groupIds, function (index, value) {
                                                if ($j.inArray(value, previousGroupIds) == -1) {
                                                    addAssetToGroup(asset, value);
                                                }
                                            });
                                            toggleItemSorting('assets', tracking.user.displayPreferences.sortMode.assets === tracking.sortModes.CUSTOM);
                                        }


                                        if (tracking.data.assetForms != null) {
                                        	for (var i = 0; i < tracking.data.assetForms.length; i++) {
                                        		if (tracking.data.assetForms[i].AssetId != id)
                                        			continue;
                                        		tracking.data.assetForms[i].FormIds = formIds;
                                        	}
                                        }

                                        if (previousUserIds != null) {
                                            if (previousUserIds.toString() != userIds.toString()) {
                                                // changes in asset user associations
                                                for (var i = 0; i < previousUserIds.length; i++) {
                                                    value = previousUserIds[i];
                                                    if ($j.inArray(value, userIds) == -1) {
                                                        // user no longer associated with asset
                                                        removeUserFromAsset(value, id);
                                                    }
                                                }

                                                $j.each(userIds, function (index, value) {
                                                    if ($j.inArray(value, previousUserIds) == -1) {
                                                        // new user association with asset
                                                        addUserToAsset(value, id);
                                                    }
                                                });
                                            }
                                        }

                                        if (previousDriverIds != null) {
                                            if (previousDriverIds.toString() != driverIds.toString()) {
                                                // changes in asset driver associations
                                                for (var i = 0; i < previousDriverIds.length; i++) {
                                                    value = previousDriverIds[i];
                                                    if ($j.inArray(value, driverIds) == -1) {
                                                        // driver no longer associated with asset
                                                        removeDriverFromAsset(value, id);
                                                    }
                                                }
                                                $j.each(driverIds, function (index, value) {
                                                    if ($j.inArray(value, previousDriverIds) == -1) {
                                                        // new driver association with asset
                                                        addDriverToAsset(value, id);
                                                    }
                                                });
                                            }
                                        } else {
                                            if (driverIds.length > 0) {
                                                var assetDrivers = {
                                                    AssetId: asset.Id,
                                                    DriverIds: []
                                                }
                                                if (tracking.data.assetDrivers == null) {
                                                    tracking.data.assetDrivers = [];
                                                }
                                                tracking.data.assetDrivers.push(assetDrivers);
                                                $j.each(driverIds, function (index, value) {
                                                    // new driver association with asset
                                                    addDriverToAsset(value, id);
                                                });
                                            }
                                        }

                                        if (previousDriverGroupIds != null) {
                                            if (previousDriverGroupIds.toString() != driverGroupIds.toString()) {
                                                // changes in asset driver group associations
                                                _.each(previousDriverGroupIds, function (driverGroupId) {
                                                    if (_.indexOf(driverGroupIds, driverGroupId) === -1) {
                                                        removeDriverGroupFromAsset(driverGroupId, parseInt(id));
                                                    }
                                                });
                                                _.each(driverGroupIds, function (driverGroupId) {
                                                    if (_.indexOf(previousDriverGroupIds, driverGroupId) === -1) {
                                                        addDriverGroupToAsset(driverGroupId, parseInt(id));
                                                    }
                                                });
                                            }
                                        } else {
                                            _.each(driverGroupIds, function (driverGroupId) {
                                                addDriverGroupToAsset(driverGroupId, parseInt(id));
                                            });
                                        }

                                        if ((previousDrawLines != drawLines) || (previousSnapLinesToRoads != snapLinesToRoads)) {
                                            updateAssetPositionLines(asset, false, tracking.viewModes.NORMAL);
                                        }

                                        if ((previousColor != color) || (previousClass != type)) {
                                            // update position colors
                                            updateMarkersForAsset(asset);

                                            // update marker colors in selection list, todo: fix this duplicate
                                            updateAssetListingIcon(asset.Id, asset.Color, isThisAssetActive, asset.Class);
                                        }

                                        if (previousService != isOutOfService) {
                                            _.each(tracking.data.domNodes.assets[asset.Id], function (assetNode) {
                                        	    if (isOutOfService) {
                                                    assetNode.classList.add('noservice');
                                        	    } else {
                                                    assetNode.classList.remove('noservice');
                                        	    }
                                             });
                                        }
                                    }
                                    if (previousName != name) {
                                        _.each(tracking.data.domNodes.assets[asset.Id], function (assetNode) {
                                            var nameNode = assetNode.querySelector('.asset-name');
                                            nameNode.textContent = name;
                                        });
                                        // todo: resort each list if alphabetical
                                    }
                                    indexAssetsForSearch();
                                }

                                toggleLoadingMessage(false, 'edit-asset'); // IE fix to place before upload plugin

                                // upload the photo if it exists
                                uploadFile('fileEditAssetPhoto',
                                    '/Home/UploadAssetPhoto',
                                    { assetId: asset.Id },
                                    function (res) {
                                        if ((res == '') || (res == 'empty')) { // plugin does not handle empty result properly
                                            return;
                                        }
                                        res = JSON.parse(res);

                                        if (res.success == true) {
                                            // show photo
                                            if ((res.type != null) && (res.type != '')) {
                                                asset.PhotoType = res.type;
                                                if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
                                                    && parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id')) === asset.Id) {
                                                    $('#edit-asset-photo').find('img').attr('src', '/uploads/images/assets/' + asset.Id + '_thumb.' + res.type + '?rnd=' + new Date().getTime());
                                                    $('#edit-asset-photo a').attr('href', '/uploads/images/assets/' + asset.Id + '.' + res.type);
                                                    $('#edit-asset-photo').show();
                                                }

                                                // clear file selection
                                                $('#fileEditAssetPhoto').val('').next('.custom-file-label').text(tracking.strings.NO_FILE_SELECTED);
                                            }
                                        }
                                    }
                                );

                                // upload icon
                                uploadFile('fileEditAssetIcon',
                                    '/Home/UploadAssetIcon',
                                    { assetId: asset.Id },
                                    function (res) {
                                        if ((res == '') || (res == 'empty')) { // plugin does not handle empty result properly
                                            return;
                                        }
                                        res = JSON.parse(res);
                                        if (res.success == true) {
                                            // update asset icon
                                            asset.Class = "Upload";
                                            asset.IconModified = Math.round(new Date().getTime() / 1000);

                                            // update position colors
                                            updateMarkersForAsset(asset);

                                            // update marker colors in selection list, todo: fix this duplicate
                                            updateAssetListingIcon(asset.Id, asset.Color, isThisAssetActive, asset.Class);

                                            // show icon - only if dialog still open for asset
                                            if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
                                                && parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id')) === asset.Id) {
                                                $j('#edit-asset-icon').find('img').attr('src', '/markers/upload?aid=' + asset.Id + '&lm=' + new Date().getTime());
                                                $j('#edit-asset-icon').show();
                                            }

                                            // clear file selection
                                            $j('#fileEditAssetIcon').val('').next('.custom-file-label').text(tracking.strings.NO_FILE_SELECTED);
                                        }
                                    }
                                );

                                if (isAssetAdded === true) {
                                    openEditAssetDialog(null);
                                    formShowSuccessMessage(status, tracking.strings.MSG_ADD_ASSET_SUCCESS);
                                }
                            } else {
                                // message failure, keep text field to allow retry
                                if (tracking.state.isAddingAsset) {
                                    formShowErrorMessage(status, tracking.strings.MSG_ADD_ASSET_ERROR);
                                } else {
                                    formShowErrorMessage(status, tracking.strings.MSG_EDIT_ASSET_ERROR);
                                }
                                if ((result.ErrorMessage != null) && (result.ErrorMessage != '')) {
                                    formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                                }
                                toggleLoadingMessage(false, 'edit-asset');
                            }
                        }
                    },
                    error: function (xhr, status, error) {
                        btn.disabled = false;
                        utility.handleWebServiceError(tracking.strings.MSG_EDIT_ASSET_FAILURE);
                        toggleLoadingMessage(false, 'edit-asset');
                    }
                });
            }
        }, {
                buttonType: 'secondary',
                icons: { primary: 'ui-icon-close' },
                text: tracking.strings.CANCEL,
                click: function () {
                    if (tracking.state.isAddingAsset) {
                        closeSecondaryPanel();
                    } else {
                        document.getElementById('panel-dialog-back').click();
                    }
                }
            }];

        tracking.data.domNodes.dialogs.editAsset = document.getElementById('edit-asset-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.editAsset, editAssetButtons);

        $(tracking.data.domNodes.dialogs.editAsset).on('change', '#fileEditAssetIcon', function (e) {
            $('#rbEditAssetIcon-Upload').prop('checked', true);
        });
        $(tracking.data.domNodes.dialogs.editAsset).on('click', '#RemoveAssetPhoto', function (e) {
            e.preventDefault();
            $(this).next().val('true');
            $(this).parent().parent().hide();
        });

        $(tracking.data.domNodes.dialogs.editAsset).on('change', '#chkHideInformationAddress', function (e) {
            e.preventDefault();
            var isChecked = this.checked;
            if (!isChecked && tracking.user.displayPreferences.warnings.addresses === true) {
                this.checked = true;
                var $modal = $(tracking.data.domNodes.modals.confirmAssetSetting);
                $('.toggle-content', $modal).removeClass('is-visible');
                $('#ConfirmAssetSettingAddress').addClass('is-visible');
                // confirm they want to uncheck it (enable)
                $modal.data('setting', $(this));
                $modal.modal('show');
            }
        });

        $(tracking.data.domNodes.dialogs.editAsset).on('change', '#chkHideInformationSpeed', function (e) {
            e.preventDefault();
            var isChecked = this.checked;
            if (!isChecked && tracking.user.displayPreferences.warnings.speed === true) {
                // should be devices with estimated speed only
                var deviceId = null;
                if (tracking.state.isAddingAsset) {
                    deviceId = parseInt(document.getElementById('ddlEditAssetDevice').value);
                } else {
                    var asset = findAssetById(document.getElementById('hfEditAssetId').value);
                    if (asset !== null) {
                        deviceId = asset.DeviceId;
                    }
                }
                if (deviceId !== null && tracking.devices.SPOT.indexOf(deviceId) !== -1) {
                    this.checked = true;
                    var $modal = $(tracking.data.domNodes.modals.confirmAssetSetting);
                    $('.toggle-content', $modal).removeClass('is-visible');
                    $('#ConfirmAssetSettingSpeed').addClass('is-visible');
                    // confirm they want to uncheck it (enable)
                    $modal.data('setting', $(this));
                    $modal.modal('show');
                }
            }
        });

        $('#edit-asset-extra-accordion,#edit-asset-accordion,.menu-accordion').on('show.bs.collapse', function (e) {
            var chevron = e.target.previousElementSibling.querySelector('svg.list-item-action use');
            if (chevron !== null) {
                chevron.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#angle-up');
            }
            var icon = e.target.previousElementSibling.querySelector('svg.list-item-icon use');
            if (icon !== null) {
                icon.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#folder-open-solid');
            }
        });
        $('#edit-asset-extra-accordion,#edit-asset-accordion,.menu-accordion').on('hide.bs.collapse', function (e) {
            var chevron = e.target.previousElementSibling.querySelector('svg.list-item-action use');
            if (chevron !== null) {
                e.target.previousElementSibling.querySelector('svg.list-item-action use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#angle-down');
            }
            var icon = e.target.previousElementSibling.querySelector('svg.list-item-icon use');
            if (icon !== null) {
                e.target.previousElementSibling.querySelector('svg.list-item-icon use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#folder-solid');
            }
        });
        $('#edit-asset-extra-accordion,#edit-asset-accordion,.menu-accordion').on('shown.bs.collapse,hidden.bs.collapse', function (e) {
            tracking.data.domNodes.simpleBars.primary.recalculate();
        });

        $('#AssetAlerts').dataTable({
            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': false, 'autoWidth': false,
            'lengthChange': false, 'paging': false, 'pageLength': 3, 'deferRender': true,
            'order': [[2, 'asc']],
            'columnDefs': [{
                'targets': '_all',
                'render': $.fn.dataTable.render.text()
            }],
            'columns': [
                { visible: false }, // id
                { visible: tracking.user.canEditAlerts, sortable: false, class: 'center', render: renderDomElement }, // edit
                { }, // name
                { }, // description
                { }, // condition
                { render: renderDomElement }, // filters
                { visible: tracking.user.canEditAlerts, sortable: false, class: 'center', render: renderDomElement } // remove
            ],
            'language': tracking.strings.DATATABLE,
            'drawCallback': function (oSettings) {
            },
            'initComplete': function (oSettings, json) {
                $('#AssetAlerts').DataTable().clear();
            }
        });

        tracking.data.domNodes.modals.confirmAssetSetting = document.getElementById('confirm-asset-setting-modal');
        $(tracking.data.domNodes.modals.confirmAssetSetting).on('click', '#ConfirmAssetSetting', function (e) {
            e.preventDefault();

            $('#DontShowSettingWarning').prop('checked', false);
            $(tracking.data.domNodes.modals.confirmAssetSetting).data('setting').prop('checked', false);
            $(tracking.data.domNodes.modals.confirmAssetSetting).modal('hide');
            $(tracking.data.domNodes.modals.confirmAssetSetting).removeData('setting')
        });

        $(tracking.data.domNodes.modals.confirmAssetSetting).on('change', '#DontShowSettingWarning', function (e) {
            var setting = $(tracking.data.domNodes.modals.confirmAssetSetting).data('setting')[0];
            if (setting.id === 'chkHideInformationSpeed') {
                tracking.user.displayPreferences.warnings.speed = !this.checked;
            } else if (setting.id === 'chkHideInformationAddress') {
                tracking.user.displayPreferences.warnings.addresses = !this.checked;
            }
            saveDisplayPreferences();
        });

        tracking.data.domNodes.modals.clearAssetHistory = document.getElementById('clear-history-modal');
        $(tracking.data.domNodes.modals.clearAssetHistory).on('click', '#ClearAssetHistoryConfirm', function (e) {
            e.preventDefault();

            var btn = this;
            var statusError = document.getElementById('clear-history-status');
            var statusSuccess = document.getElementById('edit-asset-status');
            var id = $j('#hfClearHistoryAssetId').val();
            var data = {
                assetId: id
            };
            btn.disabled = true;
            toggleLoadingMessage(true, 'clear-history');
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/ClearAssetHistory'),
                data: JSON.stringify(data),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    btn.disabled = false;
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            $(tracking.data.domNodes.modals.clearAssetHistory).modal('hide');
                            formShowSuccessMessage(statusSuccess, tracking.strings.MSG_CLEAR_HISTORY_SUCCESS);
                            var asset = findAssetById(id);
                            if (asset != null) {
                                clearAssetPositions(asset);
                            }
                        } else {
                            formShowErrorMessage(statusError, tracking.strings.MSG_CLEAR_HISTORY_ERROR);
                            if (result.ErrorMessage != null && result.ErrorMessage != '') {
                                formShowErrorMessage(statusError, statusError.textContent + ' ' + result.ErrorMessage);
                            }
                        }
                    }
                    toggleLoadingMessage(false, 'clear-history');
                },
                error: function (xhr, status, error) {
                    btn.disabled = false;
                    utility.handleWebServiceError(tracking.strings.MSG_CLEAR_HISTORY_ERROR);
                    toggleLoadingMessage(false, 'clear-history');
                }
            });
        });
        $('#edit-asset-dialog').on('click', '#ClearAssetHistory', function (e) {
            e.preventDefault();
            // open confirmation dialog
            var asset = findAssetById($j('#hfEditAssetId').val());
            $('#hfClearHistoryAssetId').val(asset.Id);
            // customize confirmation message
            $('.modal-body div.confirm', tracking.data.domNodes.modals.clearAssetHistory).text(tracking.strings.MSG_CLEAR_HISTORY_CONFIRM.replace('{0}', asset.Name));
            $(tracking.data.domNodes.modals.clearAssetHistory).modal('show');
        });

        tracking.data.domNodes.modals.idpActivate = document.getElementById('idp-activate-modal');
        $(tracking.data.domNodes.modals.idpActivate).on('click', '#IDPActivateConfirm', function (e) {
            e.preventDefault();

            var btn = this;
            var statusError = document.getElementById('idp-activate-status');
            var statusSuccess = document.getElementById('edit-asset-status');
            var id = $j('#hfIDPActivateAssetId').val();
            var data = {
                assetId: id
            };
            btn.disabled = true;
            toggleLoadingMessage(true, 'idp-activate');
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/IDPActivateTerminal'),
                data: JSON.stringify(data),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    btn.disabled = false;
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            $(tracking.data.domNodes.modals.idpActivate).modal('hide');
                            formShowSuccessMessage(statusSuccess, tracking.strings.MSG_IDP_ACTIVATE_SUCCESS);
                        } else {
                            formShowErrorMessage(statusError, tracking.strings.MSG_IDP_ACTIVATE_ERROR);
                            if (result.ErrorMessage != null && result.ErrorMessage != '') {
                                formShowErrorMessage(statusError, statusError.textContent + ' ' + result.ErrorMessage);
                            }
                        }
                    }
                    toggleLoadingMessage(false, 'idp-activate');
                },
                error: function (xhr, status, error) {
                    btn.disabled = false;
                    utility.handleWebServiceError(tracking.strings.MSG_IDP_ACTIVATE_ERROR);
                    toggleLoadingMessage(false, 'idp-activate');
                }
            });
        });
        $('#edit-asset-dialog').on('click', '#IDPActivate', function (e) {
            e.preventDefault();
            // open confirmation dialog
            var asset = findAssetById($j('#hfEditAssetId').val());
            $('#hfIDPActivateAssetId').val(asset.Id);
            // customize confirmation message
            var gateway = $j('#ddlEditAssetGatewayAccount option[value=' + asset.GatewayAccountId + ']').text();
            //var status = $j('#idp-activate-status').hide();
            $('.modal-body div.confirm', tracking.data.domNodes.modals.idpActivate).text(tracking.strings.MSG_IDP_ACTIVATE_CONFIRM.replace('{0}', asset.UniqueId).replace('{1}', gateway));
            $(tracking.data.domNodes.modals.idpActivate).modal('show');
        });

        tracking.data.domNodes.modals.idpDisableEncryption = document.getElementById('idp-encryption-disable-modal');
        $(tracking.data.domNodes.modals.idpDisableEncryption).on('click', '#IDPEncryptionDisableConfirm', function (e) {
            e.preventDefault();

            var btn = this;
            var statusError = document.getElementById('idp-encryption-disable-status');
            var statusSuccess = document.getElementById('idp-query-status');
            var id = $(tracking.data.domNodes.dialogs.idpSendCommand).data('assetId');
            var data = {
                assetId: id
            };
            btn.disabled = true;
            toggleLoadingMessage(true, 'idp-encryption-disable');
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/IDPCryptoDisableRequirement'),
                data: JSON.stringify(data),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    btn.disabled = false;
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            $(tracking.data.domNodes.modals.idpDisableEncryption).modal('hide');
                            formShowSuccessMessage(statusSuccess, tracking.strings.MSG_CRYPTO_DISABLE_SUCCESS);
                            requestIDPCryptoInformation(id);
                        } else {
                            formShowErrorMessage(statusError, tracking.strings.MSG_CRYPTO_DISABLE_ERROR);
                            if (result.ErrorMessage != null && result.ErrorMessage != '') {
                                formShowErrorMessage(statusError, statusError.textContent + ' ' + result.ErrorMessage);
                            }
                        }
                    }
                    toggleLoadingMessage(false, 'idp-encryption-disable');
                },
                error: function (xhr, status, error) {
                    btn.disabled = false;
                    utility.handleWebServiceError(tracking.strings.MSG_CRYPTO_DISABLE_ERROR);
                    toggleLoadingMessage(false, 'idp-encryption-disable');
                }
            });
        });

        tracking.data.domNodes.modals.idpDeactivate = document.getElementById('idp-deactivate-modal');
        $(tracking.data.domNodes.modals.idpDeactivate).on('click', '#IDPDeactivateConfirm', function (e) {
            e.preventDefault();

            var btn = this;
            var statusError = document.getElementById('idp-deactivate-status');
            var statusSuccess = document.getElementById('edit-asset-status');
            var id = $j('#hfIDPDeactivateAssetId').val();
            var data = {
                assetId: id
            };
            btn.disabled = true;
            toggleLoadingMessage(true, 'idp-deactivate');
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/IDPDeactivateTerminal'),
                data: JSON.stringify(data),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    btn.disabled = false;
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            $(tracking.data.domNodes.modals.idpDeactivate).modal('hide');
                            formShowSuccessMessage(statusSuccess, tracking.strings.MSG_IDP_DEACTIVATE_SUCCESS);
                        } else {
                            formShowErrorMessage(statusError, tracking.strings.MSG_IDP_DEACTIVATE_ERROR);
                            if (result.ErrorMessage != null && result.ErrorMessage != '') {
                                formShowErrorMessage(statusError, statusError.textContent + ' ' + result.ErrorMessage);
                            }
                        }
                    }
                    toggleLoadingMessage(false, 'idp-deactivate');
                },
                error: function (xhr, status, error) {
                    btn.disabled = false;
                    utility.handleWebServiceError(tracking.strings.MSG_IDP_DEACTIVATE_ERROR);
                    toggleLoadingMessage(false, 'idp-deactivate');
                }
            });
        });
        $('#edit-asset-dialog').on('click', '#IDPDeactivate', function (e) {
            e.preventDefault();
            // open confirmation dialog
            var asset = findAssetById($j('#hfEditAssetId').val());
            $('#hfIDPDeactivateAssetId').val(asset.Id);
            // customize confirmation message
            var gateway = $j('#ddlEditAssetGatewayAccount option[value=' + asset.GatewayAccountId + ']').text();
            //var status = $j('#idp-deactivate-status').hide();
            $('.modal-body div.confirm', tracking.data.domNodes.modals.idpDeactivate).text(tracking.strings.MSG_IDP_DEACTIVATE_CONFIRM.replace('{0}', asset.UniqueId).replace('{1}', gateway));
            $(tracking.data.domNodes.modals.idpDeactivate).modal('show');
        });

        var idpOutputButtons = [{
            text: tracking.strings.BUTTON_SET_OUTPUT,
            click: function () {
                var status = $('#idp-output-status');
                var isFormValid = $(tracking.data.validation.idpOutput.currentForm).valid();
                if (!isFormValid) {
                    tracking.data.validation.idpOutput.focusInvalid();
                    return;
                }

                var id = $('#hfOutputAssetId').val();
                var asset = findAssetById(id);
                if (asset == null) {
                    return;
                }
                var device = findDeviceById(asset.DeviceId);
                var btn = this;
                var status = document.getElementById('idp-output-status');

                // determine which form we should be submitting (IDP vs NAL or others)
                var activeForm = document.querySelector('#idp-output-dialog .output-fields.is-visible');
                if (activeForm.id === 'idp-output-fields') {                    
                    var pin = $('#ddlIDPOutputPin').val();
                    var time = $('#txtIDPOutputTime').val();
                    var vals = pin.split(',');

                    var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                        var data = {
                            assetId: id,
                            assetIds: assetIds,
                            groupIds: groupIds,
                            pin: vals[0],
                            value: vals[1],
                            time: time,
                            gateway: gateway,
                            gatewayTimeout: gatewayTimeout,
                            gatewayRetries: gatewayRetries
                        };

                        $.when(handleAjaxFormSubmission('SendOutputPinRequest', data, btn, status, tracking.strings.MSG_SET_OUTPUT_SUCCESS, tracking.strings.MSG_SET_OUTPUT_ERROR))
                            .done(function () {
                                $(tracking.data.domNodes.modals.messageAction).modal('hide');
                            });
                    };

                    openActionDialog(tracking.strings.BUTTON_SET_OUTPUT, tracking.strings.BUTTON_SET_OUTPUT, callback, id);
                } else if (activeForm.id === 'nal-output-fields') {
                    var pins = {};
                    for (var i = 1; i <= device.OutputPinCount; i++) {
                        var pin = document.getElementById('nal-output-' + i + '-on');
                        pins[i.toString()] = pin.checked;
                    }

                    var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                        var data = {
                            assetId: id,
                            assetIds: assetIds,
                            groupIds: groupIds,
                            pins: pins,
                            gateway: gateway,
                            gatewayTimeout: gatewayTimeout,
                            gatewayRetries: gatewayRetries
                        };

                        $.when(handleAjaxFormSubmission('SetOutputPins', data, btn, status, tracking.strings.MSG_SET_OUTPUT_SUCCESS, tracking.strings.MSG_SET_OUTPUT_ERROR))
                            .done(function () {
                                $(tracking.data.domNodes.modals.messageAction).modal('hide');
                            });
                    };
                    var satelliteOnly = false;
                    if (_.indexOf(tracking.devices.NAL_GSM, asset.DeviceId) === -1) {
                        satelliteOnly = true;
                    }
                    openActionDialog(tracking.strings.BUTTON_SET_OUTPUT, tracking.strings.BUTTON_SET_OUTPUT, callback, id, null, satelliteOnly, tracking.devices.NAL);
                }
            }
        }, closeButton];

        tracking.data.domNodes.dialogs.idpOutput = document.getElementById('idp-output-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.idpOutput, idpOutputButtons);

        var tm3000Buttons = [{
            buttonType: 'primary',
            text: tracking.strings.BUTTON_SET_OUTPUT,
            click: function () {
                var isFormValid = $(tracking.data.validation.tm3000.currentForm).valid();
                if (!isFormValid) {
                    return;
                }

                var id = $(tracking.data.domNodes.dialogs.tm3000).data('assetId');
                var pin = $j('#ddlTM3000Pin').val();
                var vals = pin.split(',');
                var data = {
                    assetId: id,
                    pin: vals[0],
                    value: vals[1],
                    time: null,
                    gateway: null,
                    gatewayTimeout: null,
                    gatewayRetries: null,
                    groupIds: [],
                    assetIds: []
                };

                var status = document.getElementById('tm3000-query-status');
                handleAjaxFormSubmission('SendOutputPinRequest', data, this, status, tracking.strings.MSG_SET_OUTPUT_SUCCESS, tracking.strings.MSG_SET_OUTPUT_ERROR);
            }
        }, closeButton];
        tracking.data.domNodes.dialogs.tm3000 = document.getElementById('tm3000-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.tm3000, tm3000Buttons);

        tracking.data.domNodes.dialogs.calamp = document.getElementById('calamp-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.calamp, [closeButton]);
        $(tracking.data.domNodes.dialogs.calamp).on('click', '#CalampUpdateAPN', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.calampApn.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.calamp).data('assetId');
            var phoneNumber = $j('#ddlCalampPhoneNumberCountry').val() + $j('#txtCalampPhoneNumber').val();
            var apn = $j('#txtCalampAPN').val();
            var username = $j('#txtCalampAPNUsername').val();
            var password = $j('#txtCalampAPNPassword').val();
            var data = {
                assetId: id,
                phoneNumber: phoneNumber,
                apn: apn,
                apnUsername: username,
                apnPassword: password
            };

            var status = document.getElementById('calamp-query-status');
            handleAjaxFormSubmission('CalampUpdateAPN', data, this, status, tracking.strings.MSG_SET_APN_SUCCESS, tracking.strings.MSG_SET_APN_ERROR);
        });
        $(tracking.data.domNodes.dialogs.calamp).on('click', '#CalampSetOutput', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.calampOutput.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.calamp).data('assetId');
            var pin = $j('#ddlCalampPin').val();
            var vals = pin.split(',');
            var data = {
                assetId: id,
                pin: vals[0],
                value: vals[1],
                time: null,
                gateway: null,
                gatewayTimeout: null,
                gatewayRetries: null,
                groupIds: [],
                assetIds: []
            };

            var status = document.getElementById('calamp-query-status');
            handleAjaxFormSubmission('SendOutputPinRequest', data, this, status, tracking.strings.MSG_SET_OUTPUT_SUCCESS, tracking.strings.MSG_SET_OUTPUT_ERROR);
        });


        tracking.data.domNodes.dialogs.inreach = document.getElementById('inreach-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.inreach, [closeButton]);
        $(tracking.data.domNodes.dialogs.inreach).on('click', '#InReachRequestLocation', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.inreach).data('assetId');
            var data = {
                assetId: id,
                pollType: null
            };

            var status = document.getElementById('inreach-query-status');
            handleAjaxFormSubmission('SendLocationRequest', data, this, status, tracking.strings.MSG_LOCATION_REQUEST_SUCCESS, tracking.strings.MSG_LOCATION_REQUEST_ERROR);
        });
        $(tracking.data.domNodes.dialogs.inreach).on('click', '#InReachSetReportingInterval', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.inreachInterval.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.inreach).data('assetId');
            var data = {
                assetId: id,
                interval: $j('#ddlInReachReportingInterval').val()
            };

            var status = document.getElementById('inreach-query-status');
            handleAjaxFormSubmission('SendReportingIntervalRequest', data, this, status, tracking.strings.MSG_INTERVAL_SUCCESS, tracking.strings.MSG_INTERVAL_ERROR);
        });

        tracking.data.domNodes.dialogs.geopro = document.getElementById('geopro-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.geopro, [closeButton]);
        $(tracking.data.domNodes.dialogs.geopro).on('click', '#GeoProSetReportingInterval', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.geoproInterval.currentForm).valid();
            if (!isFormValid)
                return;

            var id = $(tracking.data.domNodes.dialogs.geopro).data('assetId');
            var data = {
                assetId: id,
                interval: $j('#txtGeoProReportingInterval').val()
            };

            var status = document.getElementById('geopro-query-status');
            handleAjaxFormSubmission('SendReportingIntervalRequest', data, this, status, tracking.strings.MSG_INTERVAL_SUCCESS, tracking.strings.MSG_INTERVAL_ERROR);
        });

        tracking.data.domNodes.dialogs.nal = document.getElementById('nal-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.nal, [closeButton]);
        $(tracking.data.domNodes.dialogs.nal).on('click', '#NALSetParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.nal.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.nal).data('assetId');
            var properties = {
                ReportingInterval: getValueIfCheckboxSelected('#txtNALReportingInterval'),
                EmergencyReportingInterval: getValueIfCheckboxSelected('#txtNALEmergencyReportingInterval'),
                RetryInterval: getValueIfCheckboxSelected('#ddlNALRetryInterval'),
                EmergencyRetryInterval: getValueIfCheckboxSelected('#ddlNALEmergencyRetryInterval'),
                ReportFormat: getValueIfCheckboxSelected('#ddlNALReportFormat'),
                EmergencyEnabled: getValueIfCheckboxSelected('input[name=rbNALEmergencyEnabled]:checked'),
                TrackingEnabled: getValueIfCheckboxSelected('input[name=rbNALTrackingEnabled]:checked'),
                IncludeGPSInMessages: getValueIfCheckboxSelected('input[name=rbNALIncludeGPSInMessages]:checked'),
                MailboxChecks: getValueIfCheckboxSelected('#txtNALMailboxChecks'),
                EmergencyMailboxChecks: getValueIfCheckboxSelected('#txtNALEmergencyMailboxChecks')
            };

            var status = document.getElementById('nal-query-status');
            var btn = this;

            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    properties: properties,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries,
                    groupIds: groupIds,
                    assetIds: assetIds
                };

                $.when(handleAjaxFormSubmission('SetNALProperties', data, btn, status, tracking.strings.MSG_PARAMETERS_SUCCESS, tracking.strings.MSG_PARAMETERS_ERROR))
                    .done(function () {
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            }
            var asset = findAssetById(id);
            var satelliteOnly = false;
            if (asset != null) {
                if (_.indexOf(tracking.devices.NAL_GSM, asset.DeviceId) === -1) {
                    satelliteOnly = true;
                }
            }
            openActionDialog(tracking.strings.SET_PARAMETERS, tracking.strings.SET_PARAMETERS, callback, id, null, satelliteOnly, tracking.devices.NAL);
        });
        $(tracking.data.domNodes.dialogs.nal).on('click', '#NALRequestLocation', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.nal).data('assetId');
            var properties = {
                PollReport: true
            };

            var status = document.getElementById('nal-query-status');
            var btn = this;
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    properties: properties,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries,
                    groupIds: groupIds,
                    assetIds: assetIds
                };

                $.when(handleAjaxFormSubmission('SetNALProperties', data, btn, status, tracking.strings.MSG_LOCATION_REQUEST_SUCCESS, tracking.strings.MSG_LOCATION_REQUEST_ERROR))
                    .done(function () {
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            }
            var asset = findAssetById(id);
            var satelliteOnly = false;
            if (asset != null) {
                if (_.indexOf(tracking.devices.NAL_GSM, asset.DeviceId) === -1) {
                    satelliteOnly = true;
                }
            }
            openActionDialog(tracking.strings.BUTTON_REQUEST_LOCATION, tracking.strings.BUTTON_REQUEST_LOCATION, callback, id, null, satelliteOnly, tracking.devices.NAL);
        });

        $j('#IDPParameters').on('click', 'button.remove', function(e) {
            e.preventDefault();

            // remove row
            $j(this).parent().parent().remove();
        });

        $j('#edit-asset-assets').on('click', 'a.select-all', function (e) {
        	e.preventDefault();
        	$j('input[name=EditAssetAssetIds]').prop('checked', true);
        });

        $j('#edit-asset-assets').on('click', 'a.select-none', function (e) {
        	e.preventDefault();
        	$j('input[name=EditAssetAssetIds]').prop('checked', false);
        });

        $('#dialog-functions').on('click', 'a.select-all', function (e) {
            e.preventDefault();
            var container = $(this).closest('.parameters-form');
            $('.parameter-toggle input:checkbox', container).prop('checked', true);
            $('.parameter-toggle input:checkbox', container).parents('.parameter-toggle').next('.parameter-options').find('input,select,label').prop('disabled', false).removeClass('disabled');
        });
        $('#dialog-functions').on('click', 'a.select-none', function (e) {
            e.preventDefault();
            var container = $(this).closest('.parameters-form');
            $('.parameter-toggle input:checkbox', container).prop('checked', false);
            $('.parameter-toggle input:checkbox', container).parents('.parameter-toggle').next('.parameter-options').find('input,select,label').prop('disabled', true).addClass('disabled');
        });
        $('#dialog-functions').on('click', '.parameters-form label', function (e) {
            var col = $(this).next('div');
            col.find('.parameter-toggle input:checkbox').prop('checked', true);
            col.find('.parameter-options').find('input,select,label').not(':checkbox').prop('disabled', false).removeClass('disabled');
            // todo: check .next-cell?
        });
        $('#dialog-functions').on('change', '.parameter-toggle input:checkbox', function (e) {
            var isDisabled = !$(this).prop('checked');
            var col = $(this).parents('.parameter-toggle').next('.parameter-options');
            if (isDisabled) {
                col.find('input,select,label').not(':checkbox').prop('disabled', isDisabled).addClass('disabled');
            } else {
                col.find('input,select,label').not(':checkbox').prop('disabled', isDisabled).removeClass('disabled');
            }
            // todo: check .next-cell?
        });
        //$j('#IDPAVLIOParameters,#TelematicsGarminParameters,.parameters-form').on('click', 'a.select-all', function (e) {
        //    e.preventDefault();
        //    var table = $j(this).closest('table');
        //    $j('input:checkbox', table).prop('checked', true).parents('td').find('input,select,label').not(':checkbox').prop('disabled', false).removeClass('disabled');
        //    $j('input:checkbox.next-cell', table).parents('td').next('td').find('input,select,label').not(':checkbox').prop('disabled', false).removeClass('disabled');
        //    $j('input:checkbox.next-rows', table).parents('td').parent().nextUntil('tr.collapse').find('input,select,label').not(':checkbox').prop('disabled', false).removeClass('disabled');
        //});
        //$j('#IDPAVLIOParameters,#TelematicsGarminParameters,.parameters-form').on('click', 'a.select-none', function (e) {
        //    e.preventDefault();
        //    var table = $j(this).closest('table');
        //    $j('input:checkbox', table).prop('checked', false).parents('td').find('input,select,label').not(':checkbox').prop('disabled', true).addClass('disabled');
        //    $j('input:checkbox.next-cell', table).parents('td').next('td').find('input,select,label').not(':checkbox').prop('disabled', true).addClass('disabled');
        //    $j('input:checkbox.next-rows', table).parents('td').parent().nextUntil('tr.collapse').find('input,select,label').not(':checkbox').prop('disabled', true).addClass('disabled');
        //});
        //$j('#IDPAVLParameters,#IDPAVLIOParameters,#IDPCoreParameters,#IDPGarminParameters,#IDPARCProperties,#NALProperties,#IDPServiceMeters,#TelematicsGarminParameters,.parameters-form').on('click', 'label', function (e) {
        //    var cell = $j(this).parent().next('td');
        //    cell.find('input:checkbox').prop('checked', true);
        //    cell.find('input,select,label').not(':checkbox').prop('disabled', false).removeClass('disabled');
        //    if (cell.hasClass('next-cell')) {
        //        cell = cell.next('td').find('input,select,label').not(':checkbox').prop('disabled', false).removeClass('disabled');
        //    }
        //});
        //$j('#IDPAVLParameters,#IDPAVLIOParameters,#IDPCoreParameters,#IDPGarminParameters,#IDPARCProperties,#NALProperties,#IDPServiceMeters,#TelematicsGarminParameters,.parameters-form').on('change', 'input:checkbox', function (e) {
        //    var isDisabled = !$j(this).prop('checked');
        //    if (isDisabled) {
        //        $j(this).parents('td').find('input,select,label').not(':checkbox').prop('disabled', isDisabled).addClass('disabled');
        //        if ($j(this).hasClass('next-cell')) {
        //            $j(this).parents('td').next('td').find('input,select,label').not(':checkbox').prop('disabled', isDisabled).addClass('disabled');
        //        }
        //    } else {
        //        $j(this).parents('td').find('input,select,label').not(':checkbox').prop('disabled', isDisabled).removeClass('disabled');
        //        if ($j(this).hasClass('next-cell')) {
        //            $j(this).parents('td').next('td').find('input,select,label').not(':checkbox').prop('disabled', isDisabled).removeClass('disabled');
        //        }
        //    }
        //    //e.stopPropagation();
        //});

        $j('#idp-dialog,#queclink-dialog,#quake-dialog').on('click', 'tr.collapse,tr.collapse a', function (e) {
            e.preventDefault();
            $j(this).nextUntil('tr.collapse').toggle();
            $j(this).nextUntil('tr.collapse').filter('.stayhidden').hide();
        });

        var routeGeofenceButtons = [closeButton];
        if (tracking.user.canEditGeofences) {
        	routeGeofenceButtons.unshift(routeAsGeofenceButton);
        }

        $('#RouteGeofenceBuffer').slider({
        	min: 25,
			max: 2000,
			value: 25,
        	slide: function (event, ui) {
        		$('#RouteGeofenceBufferValue').text(ui.value);
        	},
            change: function (event, ui) {
                tracking.throttles.bufferGeofence(ui.value);
            }
        });

        tracking.data.domNodes.modals.runReport = document.getElementById('run-report-modal');
        $(tracking.data.domNodes.modals.runReport).on('click', '#RunReportConfirm', function (e) {
            var isFormValid = $(tracking.data.validation.runReport.currentForm).valid();
            if (!isFormValid) {
                return;
            }
            $('#run-report-form').submit();
        });

        $(tracking.data.domNodes.modals.runReport).on('change', '#RunReportReport', function (e) {
            runReportOptionChanged();
        });
        $(tracking.data.domNodes.modals.messageAction).on('click', 'a.select-all', function (e) {
            e.preventDefault();
            $('input[name=AssetIds]', tracking.data.domNodes.modals.messageAction).prop('checked', true);
        });
        $(tracking.data.domNodes.modals.messageAction).on('click', 'a.select-non', function (e) {
            e.preventDefault();
            $('input[name=AssetIds]', tracking.data.domNodes.modals.messageAction).prop('checked', false);
        });

        tracking.data.domNodes.modals.messageAction = document.getElementById('message-action-modal');
        $(tracking.data.domNodes.modals.messageAction).on('hidden.bs.modal', function (e) {
            if (tracking.state.nextFocus !== undefined && tracking.state.nextFocus !== null) {
                tracking.state.nextFocus.focus();
                tracking.state.nextFocus = null;
            }
        });
        $(tracking.data.domNodes.modals.messageAction).on('shown.bs.modal', function (e) {
            document.getElementById('message-action-button').focus();
        });
        $(tracking.data.domNodes.modals.messageAction).on('click', '#message-action-button', function (e) {
            var btn = this;
            btn.disabled = true;
            var dialog = tracking.data.domNodes.modals.messageAction;
            var isFormValid = $('#message-action-form').valid();
            if (!isFormValid) {
                return;
            }
            var id = $(dialog).data('assetId');
            var groupIds = [];
            var groups = $j('.GroupsContainer input:checked', dialog).each(function () {
                groupIds.push($j(this).val());
            });
            var assetIds = [];
            var assets = $j('.AssetsIncluded option', dialog).each(function () {
                assetIds.push($j(this).val());
            });

            var gateway = $j('input[name=IDPGateway]:checked', dialog).val();
            var gatewayTimeout = $j('#IDPGatewayTimeout').val();

            var gatewayRetries = $j('#IDPGatewayRetries').val();
            if (gateway == 3) {
                gatewayRetries = $j('#IDPGatewayCellRetries').val();
            }

            var callback = $j(dialog).data('message-action-callback');
            $.when(callback(id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries)).done(function () {
                btn.disabled = false;
            });
        });

        var garminResetButtons = [{
            text: tracking.strings.RESET_DEVICE,
            click: resetGarminDevice
        },
        closeButton];

        $j('#garmin-reset-dialog').dialog({
            autoOpen: false,
            modal: true,
            width: 450,
            buttons: garminResetButtons,
            create: function () {
                createDialogTitlebar(this);
                replaceDialogButtons(this, $(this).dialog('option', 'buttons'));
            }
        });

        var garminResetConfigButtons = [{
            text: tracking.strings.RESET_DEVICE_CONFIGURATION,
            click: resetGarminDeviceConfiguration
        },
        closeButton];

        $j('#garmin-reset-config-dialog').dialog({
            autoOpen: false,
            modal: true,
            width: 450,
            buttons: garminResetConfigButtons,
            create: function () {
                createDialogTitlebar(this);
                replaceDialogButtons(this, $(this).dialog('option', 'buttons'));
            }
        });

        // garmin fleet 590 is discontinued
        //tracking.data.domNodes.dialogs.garmin = document.getElementById('garmin-dialog');
        //loadDialogButtons(tracking.data.domNodes.dialogs.garmin, [closeButton]);
        //$j('#garmin-dialog').on('click', '#TelematicsResetConfiguration', function (e) {
        //    e.preventDefault();
        //    var dialog = $j('#garmin-reset-config-dialog');
        //    dialog.dialog('open');
        //});
        //$j('#garmin-dialog').on('click', '#TelematicsResetDevice', function (e) {
        //    e.preventDefault();
        //    var dialog = $j('#garmin-reset-dialog');
        //    dialog.dialog('open');
        //});
        //$j('#garmin-dialog').on('click', '#TelematicsDeleteData', function (e) {
        //    e.preventDefault();
        //    var dialog = $j('#garmin-dialog');
        //    var status = $j('#garmin-query-status');
        //    var form = $j('#garmin-delete-data-form');
        //    var isFormValid = $j('#garmin-delete-data-form').valid();
        //    if (!isFormValid)
        //        return;

        //    var data = {
        //        assetId: dialog.data('assetId'),
        //        stops: $j('#TelematicsDataStops').prop('checked'),
        //        textMessages: $j('#TelematicsDataTextMessages').prop('checked'),
        //        activeRoute: $j('#TelematicsDataActiveRoute').prop('checked'),
        //        cannedMessages: $j('#TelematicsDataCannedMessages').prop('checked'),
        //        cannedResponses: $j('#TelematicsDataCannedResponses').prop('checked'),
        //        breadcrumbs: $j('#TelematicsDataBreadcrumbs').prop('checked'),
        //        timeClockData: $j('#TelematicsDataTimeClockData').prop('checked')
        //    };
        //    var status = $j('#garmin-query-status');
        //    toggleLoadingMessage(true, 'garmin-delete');
        //    return $j.ajax({
        //        type: 'POST',
        //        url: wrapUrl('/services/GPSService.asmx/TelematicsDeleteData'),
        //        data: JSON.stringify(data),
        //        contentType: 'application/json; charset=utf-8',
        //        dataType: 'json',
        //        success: function (msg) {
        //            var result = msg.d;
        //            if (result) {
        //                if (result.Success != true) {
        //                    // message failure, keep text field to allow retry
        //                    status.text('').removeClass('success').addClass('error').show();
        //                    if ((result.ErrorMessage != null) && (result.ErrorMessage != '')) {
        //                        formShowErrorMessage(status, status.text() + ' ' + result.ErrorMessage);
        //                    }
        //                } else {
        //                    dialog.dialog('close');
        //                }
        //            }
        //            toggleLoadingMessage(false, 'garmin-delete');
        //        },
        //        error: function (xhr, status, error) {
        //            utility.handleWebServiceError('');
        //            toggleLoadingMessage(false, 'garmin-delete');
        //        }
        //    });
        //});
        //$j('#garmin-dialog').on('click', '#TelematicsSetOdometer', function (e) {
        //    e.preventDefault();
        //    var dialog = $j('#garmin-dialog');
        //    var status = $j('#garmin-query-status');
        //    var form = $j('#garmin-odometer-form');
        //    var isFormValid = $j('#garmin-odometer-form').valid();
        //    if (!isFormValid)
        //        return;

        //    var data = {
        //        assetId: dialog.data('assetId'),
        //        odometer: $j('#TelematicsOdometer').val()
        //    };
        //    var status = $j('#garmin-query-status');
        //    toggleLoadingMessage(true, 'garmin-odometer');
        //    return $j.ajax({
        //        type: 'POST',
        //        url: wrapUrl('/services/GPSService.asmx/TelematicsSetOdometer'),
        //        data: JSON.stringify(data),
        //        contentType: 'application/json; charset=utf-8',
        //        dataType: 'json',
        //        success: function (msg) {
        //            var result = msg.d;
        //            if (result) {
        //                if (result.Success != true) {
        //                    // message failure, keep text field to allow retry
        //                    status.text(tracking.strings.MSG_SET_ODOMETER_ERROR).removeClass('success').addClass('error').show();
        //                    if ((result.ErrorMessage != null) && (result.ErrorMessage != '')) {
        //                        formShowErrorMessage(status, status.text() + ' ' + result.ErrorMessage);
        //                    }
        //                } else {
        //                    dialog.dialog('close');
        //                }
        //            }
        //            toggleLoadingMessage(false, 'garmin-odometer');
        //        },
        //        error: function (xhr, status, error) {
        //            utility.handleWebServiceError(tracking.strings.MSG_SET_ODOMETER_ERROR);
        //            toggleLoadingMessage(false, 'garmin-odometer');
        //        }
        //    });
        //});
        //$j('#garmin-dialog').on('click', '#btnTelematicsSetDeviceConfiguration', function (e) {
        //    e.preventDefault();
        //    var dialog = $j('#garmin-dialog');
        //    var status = $j('#garmin-query-status');
        //    var form = $j('#garmin-garmin-form');
        //    var isFormValid = $j('#garmin-garmin-form').valid();
        //    if (!isFormValid)
        //        return;

        //    var config = {

        //    };
        //    if ($j('#chkGarminAutoArrivalInfo').prop('checked')) {
        //        config.autoArrivalInfo = {
        //            enabled: $j('input[name=rbGarminAutoArrivalInfoEnabled]:checked', dialog).val(),
        //            stopDistanceInMeters: $j('#txtGarminAutoArrivalInfoStopDistanceInMeters').val(),
        //            stopTimeInSeconds: $j('#txtGarminAutoArrivalInfoStopTimeInSeconds').val()
        //        };
        //    }
        //    if ($j('#chkGarminAggressiveDrivingAccelerometers').prop('checked')) {
        //        config.accelerometerInfo = {
        //            accelerometers: [
        //                {
        //                    forward: {
        //                        enabled: $j('input[name=rbGarminAccelerometerForwardEnabled]:checked', dialog).val(),
        //                        magnitude: $j('#txtGarminAccelerometerForwardMagnitude').val()
        //                    },
        //                    backward: {
        //                        enabled: $j('input[name=rbGarminAccelerometerBackwardEnabled]:checked', dialog).val(),
        //                        magnitude: $j('#txtGarminAccelerometerBackwardMagnitude').val()
        //                    },
        //                    leftward: {
        //                        enabled: $j('input[name=rbGarminAccelerometerLeftwardEnabled]:checked', dialog).val(),
        //                        magnitude: $j('#txtGarminAccelerometerLeftwardMagnitude').val()
        //                    },
        //                    rightward: {
        //                        enabled: $j('input[name=rbGarminAccelerometerRightwardEnabled]:checked', dialog).val(),
        //                        magnitude: $j('#txtGarminAccelerometerRightwardMagnitude').val()
        //                    },
        //                    upward: {
        //                        enabled: $j('input[name=rbGarminAccelerometerUpwardEnabled]:checked', dialog).val(),
        //                        magnitude: $j('#txtGarminAccelerometerUpwardMagnitude').val()
        //                    },
        //                    downward: {
        //                        enabled: $j('input[name=rbGarminAccelerometerDownwardEnabled]:checked', dialog).val(),
        //                        magnitude: $j('#txtGarminAccelerometerDownwardMagnitude').val()
        //                    }
        //                }]
        //        };
        //    }
        //    if ($j('#chkGarminEngineStateDetection').prop('checked')) {
        //        config.engineStateDetection = {
        //            enabled: $j('input[name=rbGarminEngineStateDetectionEnabled]:checked', dialog).val()
        //        };
        //    }
        //    if ($j('#chkGarminPowerOffMode').prop('checked')) {
        //        config.powerOffMode = {
        //            enabled: $j('input[name=rbGarminPowerOffModeEnabled]:checked', dialog).val(),
        //            pin: $j('#txtGarminPowerOffModePin').val()
        //        };
        //    }
        //    if ($j('#chkGarminSpeedLimitEvents').prop('checked')) {
        //        config.speedLimitEvents = {
        //            enabled: $j('input[name=rbGarminSpeedLimitEventsEnabled]:checked', dialog).val(),
        //            alertUser: $j('input[name=rbGarminPowerOffModeEnabled]:checked', dialog).val(),
        //            timeOverValueInSeconds: $j('#txtGarminSpeedLimitEventsTimeOverValue').val(),
        //            timeUnderValueInSeconds: $j('#txtGarminSpeedLimitEventsTimeUnderValue').val(),
        //            speed: $j('#txtGarminSpeedLimitEventsSpeed').val()
        //        };
        //    }
        //    if ($j('#chkGarminEstimatedTimeOfArrival').prop('checked')) {
        //        config.estimatedTimeOfArrival = {
        //            enabled: $j('input[name=rbGarminEstimatedTimeOfArrivalEnabled]:checked', dialog).val()
        //        };
        //    }
        //    if ($j('#chkGarminMotionDetection').prop('checked')) {
        //        config.motionDetection = {
        //            enabled: $j('input[name=rbGarminMotionDetectionEnabled]:checked', dialog).val(),
        //            stopMotionMinimumDurationInSeconds: $j('#txtGarminMotionDetectionStopMotionMinimumDuration').val()
        //        };
        //    }
        //    if ($j('#chkGarminTimeClock').prop('checked')) {
        //        config.timeClockDriverSettings = {
        //            enabled: $j('input[name=rbGarminTimeClockEnabled]:checked', dialog).val(),
        //            driverPasswordEnabled: $j('#rbGarminTimeClockDriverPasswordEnabled').val()
        //        };
        //    }
        //    if ($j('#chkGarminDispatchText').prop('checked')) {
        //        config.uiTextInfo = {
        //            dispatchText: $j('#txtGarminDispatchText').val()
        //        };
        //    }
        //    if ($j('#chkGarminPrivacyControlInfo').prop('checked')) {
        //        config.privacyControlInfo = {
        //            enabled: $j('input[name=rbGarminPrivacyControlInfoEnabled]:checked', dialog).val()
        //        };
        //    }
        //    if ($j('#chkGarminTripCOdometer').prop('checked')) {
        //        config.odometerCalibrationInfo = {
        //            tripCOdometer: $j('#txtGarminTripCOdometer').val()
        //        };
        //    }

        //    // ajax post
        //    var data = {
        //        assetId: $j(dialog).data('assetId'),
        //        config: config
        //    };
        //    toggleLoadingMessage(true, 'garmin-garmin-parameters');
        //    $j.ajax({
        //        type: 'POST',
        //        url: wrapUrl('/services/GPSService.asmx/TelematicsSetDeviceConfiguration'),
        //        data: JSON.stringify(data),
        //        contentType: 'application/json; charset=utf-8',
        //        dataType: 'json',
        //        success: function (msg) {
        //            var result = msg.d;
        //            if (result) {
        //                if (result.Success == true) {
        //                    dialog.dialog('close');
        //                } else {
        //                    // message failure, keep text field to allow retry
        //                    status.text(tracking.strings.MSG_GET_DEVICE_INFORMATION_ERROR).removeClass('success').addClass('error').show();
        //                    if ((result.ErrorMessage != null) && (result.ErrorMessage != '')) {
        //                        formShowErrorMessage(status, status.text() + ' ' + result.ErrorMessage);
        //                    }
        //                }
        //            }
        //            toggleLoadingMessage(false, 'garmin-garmin-parameters');
        //        },
        //        error: function (xhr, status, error) {
        //            utility.handleWebServiceError(tracking.strings.MSG_GET_DEVICE_INFORMATION_ERROR);
        //            toggleLoadingMessage(false, 'garmin-garmin-parameters');
        //        }
        //    });
        //});

        tracking.data.domNodes.dialogs.lt100 = document.getElementById('lt100-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.lt100, [closeButton]);
        $(tracking.data.domNodes.dialogs.lt100).on('click', '#LT100RequestLocation,#LT100DismissHelp,#LT100DismissFall', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.lt100).data('assetId');
            var command = $j(this).data('command');
            var data = {
                assetId: id,
                command: {
                    Type: command
                }
            };

            var status = document.getElementById('lt100-query-status');
            handleAjaxFormSubmission('LT100SendCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });
        $(tracking.data.domNodes.dialogs.lt100).on('click', '#LT100VibrateDevice', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.lt100Vibrate.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.lt100).data('assetId');
            var type = $j(this).data('command');
            var vibrateInterval = $j('#LT100VibrationInterval').val();
            var beepInterval = $j('#LT100BeepInterval').val();
            var data = {
                assetId: id,
                command: {
                    Type: type,
                    VibrationInterval: vibrateInterval,
                    BeepInterval: beepInterval
                }
            };

            var status = document.getElementById('lt100-query-status');
            handleAjaxFormSubmission('LT100SendCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });
        $(tracking.data.domNodes.dialogs.lt100).on('click', '#LT100SetPeriodicMode', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.lt100Periodic.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.lt100).data('assetId');
            var type = $j(this).data('command');
            var interval = $j('#LT100PeriodicInterval').val();
            var data = {
                assetId: id,
                command: {
                    Type: type,
                    StationaryReportingInterval: interval
                }
            };

            var status = document.getElementById('lt100-query-status');
            handleAjaxFormSubmission('LT100SendCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });
        $(tracking.data.domNodes.dialogs.lt100).on('click', '#LT100SetMotionMode', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.lt100Motion.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.lt100).data('assetId');
            var type = $j(this).data('command');
            var stationaryInterval = $j('#LT100StationaryInterval').val();
            var movingInterval = $j('#LT100MovingInterval').val();
            var data = {
                assetId: id,
                command: {
                    Type: type,
                    StationaryReportingInterval: stationaryInterval,
                    MovingReportingInterval: movingInterval
                }
            };

            var status = document.getElementById('lt100-query-status');
            handleAjaxFormSubmission('LT100SendCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });
        $(tracking.data.domNodes.dialogs.lt100).on('click', '#LT100SetStandbyMode', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.lt100).data('assetId');
            var type = $j(this).data('command');
            var data = {
                assetId: id,
                command: {
                    Type: type
                }
            };

            var status = document.getElementById('lt100-query-status');
            handleAjaxFormSubmission('LT100SendCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });

        tracking.data.domNodes.dialogs.lt501 = document.getElementById('lt501-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.lt501, [closeButton]);
        $(tracking.data.domNodes.dialogs.lt501).on('click', '#LT501DismissHelp', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.lt501).data('assetId');
            var command = $j(this).data('command');

            var data = {
                assetId: id,
                command: {
                    Type: command
                }
            };

            var status = document.getElementById('lt501-query-status');
            handleAjaxFormSubmission('LT501SendCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });
        $(tracking.data.domNodes.dialogs.lt501).on('click', '#LT501RequestLocation', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.lt501Location.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.lt501).data('assetId');
            var type = $j(this).data('command');
            var interval = $j('#LT501GpsTime').val();
            var data = {
                assetId: id,
                command: {
                    Type: type,
                    GpsTime: interval
                }
            };

            var status = document.getElementById('lt501-query-status');
            handleAjaxFormSubmission('LT501SendCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });
        $(tracking.data.domNodes.dialogs.lt501).on('click', '#LT501SetPeriodicMode', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.lt501Periodic.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.lt501).data('assetId');
            var type = $j(this).data('command');
            var interval = $j('#LT501PeriodicInterval').val();
            var data = {
                assetId: id,
                command: {
                    Type: type,
                    StationaryReportingInterval: interval
                }
            };

            var status = document.getElementById('lt501-query-status');
            handleAjaxFormSubmission('LT501SendCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });
        $(tracking.data.domNodes.dialogs.lt501).on('click', '#LT501SetMotionMode', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.lt501Motion.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.lt501).data('assetId');
            var type = $j(this).data('command');
            var stationaryInterval = $j('#LT501StationaryInterval').val();
            var movingInterval = $j('#LT501MovingInterval').val();
            var gSensorSensitivity = $j('#LT501GSensorSensitivity').val();
            var data = {
                assetId: id,
                command: {
                    Type: type,
                    StationaryReportingInterval: stationaryInterval,
                    MovingReportingInterval: movingInterval,
                    GSensorSensitivity: gSensorSensitivity
                }
            };

            var status = document.getElementById('lt501-query-status');
            handleAjaxFormSubmission('LT501SendCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });

        tracking.data.domNodes.dialogs.hughes = document.getElementById('hughes-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.hughes, [closeButton]);
        $(tracking.data.domNodes.dialogs.hughes).on('click', '#HughesRequestLocation,#HughesResetDevice,#HughesUpdateFirmware,#HughesRequestUsage', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.hughes).data('assetId');
            var command = $j(this).data('command');

            var data = {
                assetId: id,
                command: command
            };

            var status = document.getElementById('hughes-query-status');
            handleAjaxFormSubmission('HughesSendCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });
        $(tracking.data.domNodes.dialogs.hughes).on('click', '#HughesSendCommand', function (e) {
            e.preventDefault();
            var isFormValid = $(tracking.data.validation.hughesCommands.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.hughes).data('assetId');
            var command = $j('#HughesCommand').val();
            var data = {
                assetId: id,
                command: command
            };

            var status = document.getElementById('hughes-query-status');
            handleAjaxFormSubmission('HughesSendRawCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });
        $(tracking.data.domNodes.dialogs.hughes).on('click', '#HughesDownloadFile', function (e) {
            e.preventDefault();
            var isFormValid = $(tracking.data.validation.hughesDownloadFile.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.hughes).data('assetId');
            var data = {
                assetId: id,
                localDirectory: $('#txtHughesLocalDirectory').val(),
                filename: $('#txtHughesFilename').val(),
                ftpDirectory: $('#txtHughesFTPDirectory').val(),
                ftpServer: $('#txtHughesFTPServer').val(),
                ftpUsername: $('#txtHughesFTPUsername').val(),
                ftpPassword: $('#txtHughesFTPPassword').val(),
                apn: $('#txtHughesAPN').val(),
                apnUsername: $('#txtHughesAPNUsername').val(),
                apnPassword: $('#txtHughesAPNPassword').val()
            };

            var status = document.getElementById('hughes-query-status');
            handleAjaxFormSubmission('HughesDownloadFile', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });

        tracking.data.domNodes.dialogs.flightcell = document.getElementById('flightcell-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.flightcell, [closeButton]);
        $(tracking.data.domNodes.dialogs.flightcell).on('click', '#FlightcellSendPayload', function (e) {
            e.preventDefault();
            var isFormValid = $(tracking.data.validation.flightcellPayload.currentForm).valid();
            if (!isFormValid) {
                return;
            }
            var id = $(tracking.data.domNodes.dialogs.flightcell).data('assetId');
            var data = {
                assetId: id,
                payload: {
                    Sender: $('#FlightcellPayloadSender').val(),
                    Remote: $('#FlightcellPayloadRemote').val(),
                    ContentType: $('#FlightcellPayloadContentType').val(),
                    Subject: $('#FlightcellPayloadSubject').val(),
                    Payload: $('#FlightcellPayloadPayload').val()
                }
            };
            var status = document.getElementById('flightcell-query-status');
            handleAjaxFormSubmission('FlightcellSendPayload', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });

        tracking.data.domNodes.dialogs.edgeSolar = document.getElementById('edge-solar-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.edgeSolar, [closeButton]);
        $(tracking.data.domNodes.dialogs.edgeSolar).on('click', '#QueryEdgeSolarConfig', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.edgeSolar).data('assetId');
            var data = {
                assetId: id
            };

            var status = document.getElementById('edge-solar-query-status');
            handleAjaxFormSubmission('EdgeSolarSendGetConfigurationRequest', data, this, status, tracking.strings.MSG_GET_PARAMETERS_SUCCESS, tracking.strings.MSG_GET_PARAMETERS_ERROR);
        });

        $(tracking.data.domNodes.dialogs.edgeSolar).on('click', '#EdgeSolarSetParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.edgeSolarSettings.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.edgeSolar).data('assetId');
            var config = {
                Interval1: {
                    IsEnabled: !document.getElementById('EdgeSolarReportingInterval1Disabled').checked,
                    IsTimeOfDay: document.getElementById('EdgeSolarReportingInterval1TimeOfDay').checked,
                    TimeMinutesPastUtc: (parseInt(document.getElementById('EdgeSolarReportingInterval1TimeHour').value) * 60) + parseInt(document.getElementById('EdgeSolarReportingInterval1TimeMinute').value),
                    IntervalMinutes: document.getElementById('EdgeSolarReportingInterval1IntervalMinutes').value
                },
                Interval2: {
                    IsEnabled: !document.getElementById('EdgeSolarReportingInterval2Disabled').checked,
                    IsTimeOfDay: document.getElementById('EdgeSolarReportingInterval2TimeOfDay').checked,
                    TimeMinutesPastUtc: (parseInt(document.getElementById('EdgeSolarReportingInterval2TimeHour').value) * 60) + parseInt(document.getElementById('EdgeSolarReportingInterval2TimeMinute').value),
                    IntervalMinutes: document.getElementById('EdgeSolarReportingInterval2IntervalMinutes').value
                },
                Interval3: {
                    IsEnabled: !document.getElementById('EdgeSolarReportingInterval3Disabled').checked,
                    IsTimeOfDay: document.getElementById('EdgeSolarReportingInterval3TimeOfDay').checked,
                    TimeMinutesPastUtc: (parseInt(document.getElementById('EdgeSolarReportingInterval3TimeHour').value) * 60) + parseInt(document.getElementById('EdgeSolarReportingInterval3TimeMinute').value),
                    IntervalMinutes: document.getElementById('EdgeSolarReportingInterval3IntervalMinutes').value
                },
                Interval4: {
                    IsEnabled: !document.getElementById('EdgeSolarReportingInterval4Disabled').checked,
                    IsTimeOfDay: document.getElementById('EdgeSolarReportingInterval4TimeOfDay').checked,
                    TimeMinutesPastUtc: (parseInt(document.getElementById('EdgeSolarReportingInterval4TimeHour').value) * 60) + parseInt(document.getElementById('EdgeSolarReportingInterval4TimeMinute').value),
                    IntervalMinutes: document.getElementById('EdgeSolarReportingInterval4IntervalMinutes').value
                },
                Interval5: {
                    IsEnabled: !document.getElementById('EdgeSolarReportingInterval5Disabled').checked,
                    IsTimeOfDay: document.getElementById('EdgeSolarReportingInterval5TimeOfDay').checked,
                    TimeMinutesPastUtc: (parseInt(document.getElementById('EdgeSolarReportingInterval5TimeHour').value) * 60) + parseInt(document.getElementById('EdgeSolarReportingInterval5TimeMinute').value),
                    IntervalMinutes: document.getElementById('EdgeSolarReportingInterval5IntervalMinutes').value
                },
                Interval6: {
                    IsEnabled: !document.getElementById('EdgeSolarReportingInterval6Disabled').checked,
                    IsTimeOfDay: document.getElementById('EdgeSolarReportingInterval6TimeOfDay').checked,
                    TimeMinutesPastUtc: (parseInt(document.getElementById('EdgeSolarReportingInterval6TimeHour').value) * 60) + parseInt(document.getElementById('EdgeSolarReportingInterval6TimeMinute').value),
                    IntervalMinutes: document.getElementById('EdgeSolarReportingInterval6IntervalMinutes').value
                },
                Interval7: {
                    IsEnabled: !document.getElementById('EdgeSolarReportingInterval7Disabled').checked,
                    IsTimeOfDay: document.getElementById('EdgeSolarReportingInterval7TimeOfDay').checked,
                    TimeMinutesPastUtc: (parseInt(document.getElementById('EdgeSolarReportingInterval7TimeHour').value) * 60) + parseInt(document.getElementById('EdgeSolarReportingInterval7TimeMinute').value),
                    IntervalMinutes: document.getElementById('EdgeSolarReportingInterval7IntervalMinutes').value
                },
                Interval8: {
                    IsEnabled: !document.getElementById('EdgeSolarReportingInterval8Disabled').checked,
                    IsTimeOfDay: document.getElementById('EdgeSolarReportingInterval8TimeOfDay').checked,
                    TimeMinutesPastUtc: (parseInt(document.getElementById('EdgeSolarReportingInterval8TimeHour').value) * 60) + parseInt(document.getElementById('EdgeSolarReportingInterval8TimeMinute').value),
                    IntervalMinutes: document.getElementById('EdgeSolarReportingInterval8IntervalMinutes').value
                },
                EngineeringMessageIntervalHours: document.getElementById('EdgeSolarEngineeringMessageInterval').value,
                MailboxCheckIntervalHours: document.getElementById('EdgeSolarMailboxCheckIntervalHours').value,
                MailboxCheckIsEnabled: document.getElementById('EdgeSolarMailboxCheckIsEnabledTrue').checked,
                IridiumRetryCount: document.getElementById('EdgeSolarIridiumRetryCount').value,
                VbmrMode: document.getElementById('EdgeSolarVbmrMode').value,
                VbmrIntervalMinutes: document.getElementById('EdgeSolarVbmrIntervalMinutes').value,
                VbmrIsEnabledDuringPowerSave: document.getElementById('EdgeSolarVbmrIsEnabledDuringPowerSaveYes').checked,
                PowerSaveIsEnabled: document.getElementById('EdgeSolarPowerSaveIsEnabledYes').checked,
                PowerSaveIsDays: document.getElementById('EdgeSolarPowerSaveIsDays').value === '1',
                PowerSaveCount: document.getElementById('EdgeSolarPowerSaveCount').value,
                PowerSaveIntervalHours: document.getElementById('EdgeSolarPowerSaveIntervalHours').value,
                GbmrIsEnabled: document.getElementById('EdgeSolarGbmrIsEnabledYes').checked,
                GbmrCheckIntervalMinutes: document.getElementById('EdgeSolarGbmrCheckIntervalMinutes').value,
                GbmrHomeRatio: document.getElementById('EdgeSolarGbmrHomeRatio').value,
                GbmrAwayRatio: document.getElementById('EdgeSolarGbmrAwayRatio').value
            };
            var data = {
                assetId: id,
                config: config
            };

            var status = document.getElementById('edge-solar-query-status');
            handleAjaxFormSubmission('EdgeSolarSendSetConfigurationRequest', data, this, status, tracking.strings.MSG_PARAMETERS_SUCCESS, tracking.strings.MSG_PARAMETERS_ERROR);
        });
        $(tracking.data.domNodes.dialogs.edgeSolar).on('change', 'input[name=EdgeSolarGbmrIsEnabled],input[name=EdgeSolarPowerSaveIsEnabled],#EdgeSolarVbmrMode', function (e) {
            e.preventDefault();
            toggleEdgeSolarVisibleElements();
        });
        $(tracking.data.domNodes.dialogs.edgeSolar).on('click', '#EdgeSolarRequestLocation', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.edgeSolar).data('assetId');
            var data = {
                assetId: id,
                pollType: null
            };

            var status = document.getElementById('edge-solar-query-status');
            handleAjaxFormSubmission('SendLocationRequest', data, this, status, tracking.strings.MSG_LOCATION_REQUEST_SUCCESS, tracking.strings.MSG_LOCATION_REQUEST_ERROR);
        });
        $(tracking.data.domNodes.dialogs.edgeSolar).on('click', '#EdgeSolarRequestEngineeringData', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.edgeSolar).data('assetId');
            var data = {
                assetId: id
            };

            var status = document.getElementById('edge-solar-query-status');
            handleAjaxFormSubmission('EdgeSolarSendEngineeringRequest', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });
        $(tracking.data.domNodes.dialogs.edgeSolar).on('click', '#EdgeSolarSetScratchpad', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.edgeSolar).data('assetId');
            var data = {
                assetId: id,
                data: document.getElementById('EdgeSolarUserScratchpad').value
            };

            var status = document.getElementById('edge-solar-query-status');
            handleAjaxFormSubmission('EdgeSolarSendSetScratchpadRequest', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });

        tracking.data.domNodes.dialogs.queclink = document.getElementById('queclink-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.queclink, [closeButton]);
        $(tracking.data.domNodes.dialogs.queclink).on('click', '#QueryQueclinkConfig', function (e) {
            e.preventDefault();

            var id = $j(tracking.data.domNodes.dialogs.queclink).data('assetId');
            var data = {
                assetId: id
            };

            var status = document.getElementById('queclink-query-status');
            handleAjaxFormSubmission('QueclinkSendGetConfigurationRequest', data, this, status, tracking.strings.MSG_GET_PARAMETERS_SUCCESS, tracking.strings.MSG_GET_PARAMETERS_ERROR);
        });
        $(tracking.data.domNodes.dialogs.queclink).on('keyup', '#queclink-apn-form', function (e) {
            // recreate apn/server command based on new values
            queclinkCreateAPNCommand($(tracking.data.domNodes.dialogs.queclink).data('assetId'));
        });
        $(tracking.data.domNodes.dialogs.queclink).on('click', '#QueclinkUpdateAPN', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.queclinkApn.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $j(tracking.data.domNodes.dialogs.queclink).data('assetId');
            var phoneNumber = $j('#ddlQueclinkPhoneNumberCountry').val() + $j('#txtQueclinkPhoneNumber').val();
            var apn = $j('#txtQueclinkAPN').val();
            var username = $j('#txtQueclinkAPNUsername').val();
            var password = $j('#txtQueclinkAPNPassword').val();
            var data = {
                assetId: id,
                phoneNumber: phoneNumber,
                apn: apn,
                apnUsername: username,
                apnPassword: password
            };

            var status = document.getElementById('queclink-query-status');
            handleAjaxFormSubmission('QueclinkUpdateAPN', data, this, status, tracking.strings.MSG_SET_APN_SUCCESS, tracking.strings.MSG_SET_APN_ERROR);
        });
        $(tracking.data.domNodes.dialogs.queclink).on('click', '#QueclinkSetParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.queclinkSettings.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var fri = null;
            if ($j('#QueclinkUpdateFRI').is(':checked')) {
                fri = {
                    ReportInclude: {
                        Speed: $j('#chkQueclinkReportIncludeSpeed').is(':checked'),
                        GSMTowerData: $j('#chkQueclinkReportIncludeGSMTowerData').is(':checked'),
                        Azimuth: $j('#chkQueclinkReportIncludeAzimuth').is(':checked'),
                        SendTime: $j('#chkQueclinkReportIncludeSendTime').is(':checked'),
                        Altitude: $j('#chkQueclinkReportIncludeAltitude').is(':checked')
                    },
                    ReportMode: $j('#ddlQueclinkReportMode').val(),
                    CheckInterval: $j('#txtQueclinkGPSCheckInterval').val(),
                    SendInterval: $j('#txtQueclinkSendInterval').val(),
                    IgnitionCheckInterval: $j('#txtQueclinkIgnitionOnGPSCheckInterval').val(),
                    IgnitionSendInterval: $j('#txtQueclinkIgnitionOnSendInterval').val(),
                    BeginTime: $j('#txtQueclinkStartTime').val(),
                    EndTime: $j('#txtQueclinkEndTime').val(),
                    Distance: $j('#txtQueclinkDistance').val(),
                    Mileage: $j('#txtQueclinkMileage').val(),
                    CheckInterval: $j('#txtQueclinkGPSCheckInterval').val(),
                    MovementDetectMode: $j('input:radio[name=rbQueclinkMovementDetectMode]:checked').val(),
                    MovementSpeed: $j('#txtQueclinkMovementSpeed').val(),
                    MovementDistance: $j('#txtQueclinkMovementDistance').val(),
                    Corner: $j('#txtQueclinkCorner').val(),
                    DiscardNoFix: $j('input:radio[name=rbQueclinkDiscardNoFix]:checked').val(),
                };
            }
            var parameters = {
                UniqueId: 'test',
                ProtocolVersion: 'whatever',
                DeviceName: 'testing',
                FRI: fri
            };

            var data = {
                assetId: $j(tracking.data.domNodes.dialogs.queclink).data('assetId'),
                parameters: parameters
            };

            var status = document.getElementById('queclink-query-status');
            handleAjaxFormSubmission('QueclinkSendSetParametersRequest', data, this, status, tracking.strings.MSG_PARAMETERS_SUCCESS, tracking.strings.MSG_PARAMETERS_ERROR);
        });
        $(tracking.data.domNodes.dialogs.queclink).on('click', '#QueclinkSendCommand', function (e) {
            e.preventDefault();
            var isFormValid = $(tracking.data.validation.queclinkCommands.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.queclink).data('assetId');
            var command = $j('#QueclinkCommand').val();
            var data = {
                assetId: id,
                command: command
            };

            var status = document.getElementById('queclink-query-status');
            handleAjaxFormSubmission('QueclinkSendRawCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });
        $(tracking.data.domNodes.dialogs.queclink).on('click', '#QueclinkRequestLocation,#QueclinkFactoryReset,#QueclinkPowerOff,#QueclinkResetDevice', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.queclink).data('assetId');
            var command = $j(this).data('command');

            var data = {
                assetId: id,
                command: command
            };

            var status = document.getElementById('queclink-query-status');
            handleAjaxFormSubmission('QueclinkSendCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });

        tracking.data.domNodes.dialogs.quake = document.getElementById('quake-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.quake, [closeButton]);
        $(tracking.data.domNodes.dialogs.quake).on('click', '#QuakeSetParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.quake.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.quake).data('assetId');
            var ignition = null;
            //if ($j('#QuakeAICIgnition').is(':checked')) {
                ignition = {
                    ReportIgnitionOff: $j('input:radio[name=rbQuakeIgnitionReportOff]:checked').val(),
                    ReportIgnitionOn: $j('input:radio[name=rbQuakeIgnitionReportOn]:checked').val(),
                    IgnitionOffDebounce: $j('#txtQuakeIgnitionOffDebounce').val(),
                    IgnitionOnDebounce: $j('#txtQuakeIgnitionOnDebounce').val()
                };
            //}
            var parameters = {
                Ignition: ignition
            };

            var status = document.getElementById('quake-query-status');
            var btn = this;

            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    properties: parameters,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries,
                    groupIds: groupIds,
                    assetIds: assetIds
                };

                $.when(handleAjaxFormSubmission('QuakeSendSetParametersRequest', data, btn, status, tracking.strings.MSG_PARAMETERS_SUCCESS, tracking.strings.MSG_PARAMETERS_ERROR))
                    .done(function () {
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            }
            var asset = findAssetById(id);
            var satelliteOnly = false;
            openActionDialog(tracking.strings.SET_PARAMETERS, tracking.strings.SET_PARAMETERS, callback, id, null, false, tracking.devices.QUAKE_AIC);
        });
        // todo: move this to appropriate place
        function quakeCommand(parameters, label) {

            var id = $(tracking.data.domNodes.dialogs.quake).data('assetId');
            var command = $j(this).data('command');

            var status = document.getElementById('quake-query-status');
            var btn = this;
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    properties: parameters,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries,
                    groupIds: groupIds,
                    assetIds: assetIds
                };

                $.when(handleAjaxFormSubmission('QuakeSendSetParametersRequest', data, btn, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR))
                    .done(function () {
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            }
            var asset = findAssetById(id);
            var satelliteOnly = false;
            openActionDialog(label, label, callback, id, null, false, tracking.devices.QUAKE_AIC);
        }
        $(tracking.data.domNodes.dialogs.quake).on('click', '#QuakeRebootDevice', function (e) {
            e.preventDefault();

            var parameters = {
                Reboot: true
            };
            quakeCommand(parameters, tracking.strings.RESET_DEVICE);
        });
        $(tracking.data.domNodes.dialogs.quake).on('click', '#QuakeRequestLocation', function (e) {
            e.preventDefault();
            var parameters = {
                RequestLocation: true
            };
            quakeCommand(parameters, tracking.strings.BUTTON_REQUEST_LOCATION);
        });
        $(tracking.data.domNodes.dialogs.quake).on('click', '#QueryQuakeConfig', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.quake).data('assetId');
            //requestQuakeConfig(id); // TODO
        });

        tracking.data.domNodes.dialogs.dantracker = document.getElementById('dantracker-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.dantracker, [closeButton]);
        $(tracking.data.domNodes.dialogs.dantracker).on('click', '#DanTrackerSetParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.dantracker.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var data = {
                assetId: $j(tracking.data.domNodes.dialogs.dantracker).data('assetId'),
                mode: $j('#ddlDanTrackerMode').val()
            };

            var status = document.getElementById('dantracker-query-status');
            handleAjaxFormSubmission('DanTrackerUpdateSettings', data, this, status, tracking.strings.MSG_PARAMETERS_SUCCESS, tracking.strings.MSG_PARAMETERS_ERROR);
        });

        tracking.data.domNodes.dialogs.gtts = document.getElementById('gtts-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.gtts, [closeButton]);
        $(tracking.data.domNodes.dialogs.gtts).on('click', '#GTTSSetParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.gttsSettings.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var asset = findAssetById($(tracking.data.domNodes.dialogs.gtts).data('assetId'));
            var parameters = {
                GTTS3000: null,
                GTTS2000BI: null
            };

            if (asset.DeviceId == tracking.devices.GTTS_3000) {
                parameters.GTTS3000 = {
                    PositionInterval: $j('#txtGTTSReportingInterval').val(),
                    SecondaryInterval: $j('#txtGTTSSecondaryInterval').val(),
                    MessageBufferSize: $('#txtGTTSMessageBufferSize').val()
                };
            } else if (asset.DeviceId == tracking.devices.GTTS_2000BI) {
                parameters.GTTS2000BI = {
                    PositionInterval: $j('#txtGTTSReportingInterval').val(),
                    SecondaryInterval: $j('#txtGTTSSecondaryInterval').val(),
                    MessageBufferSize: $('#txtGTTSMessageBufferSize').val(),
                    MotionDetection: $j('input:radio[name=rbGTTSMotionDetection]:checked').val(),
                };
            }

            var data = {
                assetId: asset.Id,
                parameters: parameters
            };

            var status = document.getElementById('gtts-query-status');
            handleAjaxFormSubmission('GTTSSendSetParametersRequest', data, this, status, tracking.strings.MSG_PARAMETERS_SUCCESS, tracking.strings.MSG_PARAMETERS_ERROR);
        });
        $(tracking.data.domNodes.dialogs.gtts).on('click', '#GTTSRequestLocation', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.gtts).data('assetId');
            var command = $j(this).data('command');
            var data = {
                assetId: id,
                command: command
            };

            var status = document.getElementById('gtts-query-status');
            handleAjaxFormSubmission('GTTSSendCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });

        tracking.data.domNodes.dialogs.idpSendCommand = document.getElementById('idp-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.idpSendCommand, [closeButton]);
        $(tracking.data.domNodes.dialogs.idpSendCommand).on('change', '#ddlIDPImmobilize', function(e) {
            var immobilizerTime = document.getElementById('IDPImmobilizerTime');
            if (this.value === 'true') {
                immobilizerTime.classList.add('is-visible');
            } else {
                immobilizerTime.classList.remove('is-visible');
            }
        });
        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPImmobilizerStatus', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');

            handleAjaxFormSubmissionWithGatewaySelection('IDPGetImmobilizerStatus', data, this, status,
                tracking.strings.MSG_GET_IMMOBILIZER_STATUS_SUCCESS,
                tracking.strings.MSG_GET_IMMOBILIZER_STATUS_ERROR,
                null, null, null,
                tracking.strings.GET_IMMOBILIZER_STATUS,
                tracking.strings.GET_IMMOBILIZER_STATUS);
        });
        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPDriverLoginStatus', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');

            handleAjaxFormSubmissionWithGatewaySelection('IDPGetDriverLoginState', data, this, status,
                tracking.strings.MSG_GET_DRIVER_LOGIN_STATE_SUCCESS,
                tracking.strings.MSG_GET_DRIVER_LOGIN_STATE_ERROR,
                null, null, null,
                tracking.strings.GET_DRIVER_LOGIN_STATE,
                tracking.strings.GET_DRIVER_LOGIN_STATE);
        });
        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPImmobilizerUpdate', function (e) {
            e.preventDefault();

            var enable = document.getElementById('ddlIDPImmobilize').value;
            var time = document.getElementById('txtIDPImmobilizerTime').value;
            var data = {
                enable: enable,
                time: time
            };

            var status = document.getElementById('idp-query-status');

            handleAjaxFormSubmissionWithGatewaySelection('IDPUpdateImmobilizer', data, this, status,
                tracking.strings.MSG_UPDATE_IMMOBILIZER_SUCCESS,
                tracking.strings.MSG_UPDATE_IMMOBILIZER_ERROR,
                null, null, null,
                tracking.strings.UPDATE_IMMOBILIZER,
                tracking.strings.UPDATE_IMMOBILIZER);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#RefreshIDPCommandLog', function (e) {
            e.preventDefault();

            var assetId = $(tracking.data.domNodes.dialogs.idpSendCommand).data('assetId');;
            requestIDPCommandLog(assetId, false);
        });
        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPFlushGeofenceSpeed', function (e) {
            e.preventDefault();

            var command = 'os.remove("data/svc/AVL/GeoSpeeds.dat")';

            var status = document.getElementById('idp-query-status');
            var btn = this;

            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    assetIds: assetIds,
                    groupIds: groupIds,
                    command: command,
                    isLua: true,
                    isJson: false,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries
                };

                $.when(handleAjaxFormSubmission('IDPSendExecuteCommandRequest', data, btn, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR))
                    .done(function () {
                        requestIDPCommandLog(id, false);
                        $j('#IDPCommand').val('');
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };

            openActionDialog(tracking.strings.FLUSH_GEOFENCE_SPEED, tracking.strings.FLUSH_GEOFENCE_SPEED, callback);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPFlushGeofenceDwell', function (e) {
            e.preventDefault();

            var command = 'os.remove("data/svc/AVL/GeoDwell.dat")';
            var status = document.getElementById('idp-query-status');
            var btn = this;

            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    assetIds: assetIds,
                    groupIds: groupIds,
                    command: command,
                    isLua: true,
                    isJson: false,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries
                };

                $.when(handleAjaxFormSubmission('IDPSendExecuteCommandRequest', data, btn, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR))
                    .done(function () {
                        requestIDPCommandLog(id, false);
                        $j('#IDPCommand').val('');
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };

            openActionDialog(tracking.strings.FLUSH_GEOFENCE_DWELL, tracking.strings.FLUSH_GEOFENCE_DWELL, callback);
        });
        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', 'button.DisableGeofence', function (e) {
            e.preventDefault();
            var fenceId = $j(this).data('fenceId');
            var data = {
                fenceId: fenceId
            };
            var status = document.getElementById('idp-query-status');

            handleAjaxFormSubmissionWithGatewaySelection('IDPSendDisableGeofenceRequest', data, this, status,
                tracking.strings.MSG_DISABLE_GEOFENCE_SUCCESS,
                tracking.strings.MSG_DISABLE_GEOFENCE_ERROR,
                null, null, null,
                tracking.strings.DISABLE_GEOFENCE,
                tracking.strings.DISABLE_GEOFENCE);
        });
        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', 'button.AgentVersionQuery', function (e) {
            e.preventDefault();
            var serviceType = $j(this).data('for');
            var data = {
                serviceType: serviceType
            };
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendServiceInfoRequest', data, this, status,
                tracking.strings.MSG_SERVICE_INFO_SUCCESS,
                tracking.strings.MSG_SERVICE_INFO_ERROR,
                null, null, null,
                tracking.strings.REQUEST_SERVICE_INFO,
                tracking.strings.REQUEST_SERVICE_INFO);
        });
        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPProvisionServices', function (e) {
            e.preventDefault();

            var disable = $j('#IDPServiceListing input:radio:checked.disable');
            var enable = $j('#IDPServiceListing input:radio:checked.enable');
            var services = {
                EnableSINs: [],
                DisableSINs: []
            };
            disable.each(function (index, elem) {
                services.DisableSINs.push($j(elem).data('sin'));
            });
            enable.each(function (index, elem) {
                services.EnableSINs.push($j(elem).data('sin'));
            });

            var status = document.getElementById('idp-query-status');
            var data = {
                services: services
            };
            handleAjaxFormSubmissionWithGatewaySelection('IDPToggleServices', data, this, status,
                tracking.strings.MSG_IDP_TOGGLE_SERVICE_SUCCESS,
                tracking.strings.MSG_IDP_TOGGLE_SERVICE_ERROR,
                null, null, null,
                tracking.strings.PROVISION_SERVICES,
                tracking.strings.PROVISION_SERVICES);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPAVLDiagnostics', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendGetAVLDiagnosticsRequest', data, this, status,
                tracking.strings.MSG_AVL_DIAGNOSTICS_SUCCESS,
                tracking.strings.MSG_AVL_DIAGNOSTICS_ERROR,
                null, null, null,
                tracking.strings.REQUEST_AVL_DIAGNOSTICS,
                tracking.strings.REQUEST_AVL_DIAGNOSTICS);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPSetServiceMeters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.idpMetersSet.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var SM0Time = getValueIfCheckboxSelected('#txtIDPAVLSM0Time');
            var SM0Distance = getValueIfCheckboxSelected('#txtIDPAVLSM0Distance');
            var SM1Time = getValueIfCheckboxSelected('#txtIDPAVLSM1Time');
            var SM1Distance = getValueIfCheckboxSelected('#txtIDPAVLSM1Distance');
            var SM2Time = getValueIfCheckboxSelected('#txtIDPAVLSM2Time');
            var SM2Distance = getValueIfCheckboxSelected('#txtIDPAVLSM2Distance');
            var SM3Time = getValueIfCheckboxSelected('#txtIDPAVLSM3Time');
            var SM3Distance = getValueIfCheckboxSelected('#txtIDPAVLSM3Distance');
            var SM4Time = getValueIfCheckboxSelected('#txtIDPAVLSM4Time');
            var SM4Distance = getValueIfCheckboxSelected('#txtIDPAVLSM4Distance');
            var Odometer = getValueIfCheckboxSelected('#txtIDPServiceMeterOdometer');

            var properties = {
                SM0Time: SM0Time,
                SM0Distance: SM0Distance,
                SM1Time: SM1Time,
                SM1Distance: SM1Distance,
                SM2Time: SM2Time,
                SM2Distance: SM2Distance,
                SM3Time: SM3Time,
                SM3Distance: SM3Distance,
                SM4Time: SM4Time,
                SM4Distance: SM4Distance,
                Odometer: Odometer
            };

            var data = { properties: properties };
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendSetServiceMeterRequest', data, this, status,
                tracking.strings.MSG_IDP_SET_SERVICE_METERS_SUCCESS,
                tracking.strings.MSG_IDP_SET_SERVICE_METERS_ERROR,
                null, null, null,
                tracking.strings.IDP_SET_SERVICE_METERS,
                tracking.strings.IDP_SET_SERVICE_METERS);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPGetServiceMeters', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendGetServiceMeterRequest', data, this, status,
                tracking.strings.MSG_IDP_GET_SERVICE_METERS_SUCCESS,
                tracking.strings.MSG_IDP_GET_SERVICE_METERS_ERROR,
                null, null, null,
                tracking.strings.IDP_GET_SERVICE_METERS,
                tracking.strings.IDP_GET_SERVICE_METERS);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPResetTerminal', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.idpReset.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var satelliteOnly = false;
            var resetType = $j('#IDPResetTerminalType').val();
            if (resetType <= 3) {
                satelliteOnly = true;
            }

            var status = document.getElementById('idp-query-status');
            var btn = this;

            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    assetIds: assetIds,
                    resetType: resetType,
                    groupIds: groupIds,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries
                };

                $.when(handleAjaxFormSubmission('IDPSendResetTerminalRequest', data, btn, status, tracking.strings.MSG_RESET_TERMINAL_SUCCESS, tracking.strings.MSG_RESET_TERMINAL_ERROR))
                    .done(function () {
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };
            openActionDialog(tracking.strings.RESET_TERMINAL, tracking.strings.RESET_TERMINAL, callback, null, null, satelliteOnly);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPGarminInfo', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendGarminInfoRequest', data, this, status,
                tracking.strings.MSG_GARMIN_INFO_SUCCESS,
                tracking.strings.MSG_GARMIN_INFO_ERROR,
                null, null, null,
                tracking.strings.IDP_GET_GARMIN_INFO,
                tracking.strings.IDP_GET_GARMIN_INFO);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPServiceList', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendServiceListRequest', data, this, status,
                tracking.strings.MSG_SERVICE_LIST_SUCCESS,
                tracking.strings.MSG_SERVICE_LIST_ERROR,
                null, null, null,
                tracking.strings.REQUEST_SERVICE_LIST,
                tracking.strings.REQUEST_SERVICE_LIST);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPGetGeofenceStatus', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendGetGeofenceStatusRequest', data, this, status,
                tracking.strings.MSG_GEOFENCE_STATUS_SUCCESS,
                tracking.strings.MSG_GEOFENCE_STATUS_ERROR,
                null, null, null,
                tracking.strings.REQUEST_GEOFENCE_STATUS,
                tracking.strings.REQUEST_GEOFENCE_STATUS);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPGetUpdaterStatus', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPUpdaterGetState', data, this, status,
                tracking.strings.MSG_UPDATER_STATE_SUCCESS,
                tracking.strings.MSG_UPDATER_STATE_ERROR,
                null, null, null,
                tracking.strings.REQUEST_UPDATER_STATE,
                tracking.strings.REQUEST_UPDATER_STATE);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPGetUpdaterPackageVersion', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPUpdaterGetTerminalInfo', data, this, status,
                tracking.strings.MSG_UPDATER_TERMINAL_INFO_SUCCESS,
                tracking.strings.MSG_UPDATER_TERMINAL_INFO_ERROR,
                null, null, null,
                tracking.strings.REQUEST_UPDATER_TERMINAL_INFO,
                tracking.strings.REQUEST_UPDATER_TERMINAL_INFO);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPCryptoRekey', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendCryptoRekeyRequest', data, this, status,
                tracking.strings.MSG_CRYPTO_REKEY_SUCCESS,
                tracking.strings.MSG_CRYPTO_REKEY_ERROR,
                function() {
                    // refresh crypto section
                    var assetId = $(tracking.data.domNodes.dialogs.idpSendCommand).data('assetId');
                    requestIDPCryptoInformation(assetId);
                }, null, null,
                tracking.strings.REKEY_ASSET,
                tracking.strings.REKEY_ASSET);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPCryptoEnableRequirement', function (e) {
            e.preventDefault();

            var assetId = $(tracking.data.domNodes.dialogs.idpSendCommand).data('assetId');
            var data = { assetId: assetId };
            var btn = this;
            var status = document.getElementById('idp-query-status');
            $.when(handleAjaxFormSubmission('IDPCryptoEnableRequirement', data, btn, status, tracking.strings.MSG_CRYPTO_ENABLE_SUCCESS, tracking.strings.MSG_CRYPTO_ENABLE_ERROR))
                .done(function () {
                    
                    requestIDPCryptoInformation(assetId);
                });
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPCryptoDisableRequirement', function (e) {
            e.preventDefault();

            // open confirmation dialog
            var assetId = $(tracking.data.domNodes.dialogs.idpSendCommand).data('assetId');
            $(tracking.data.domNodes.modals.idpDisableEncryption).modal('show');
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPSetSleepSchedule', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.idpSleepSchedule.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var wakeup = $j('#IDPSleepScheduleWakeup').val();
            var status = document.getElementById('idp-query-status');
            var btn = this;
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    assetIds: assetIds,
                    wakeup: wakeup,
                    groupIds: groupIds,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries
                };

                $.when(handleAjaxFormSubmission('IDPSendSetSleepScheduleRequest', data, btn, status, tracking.strings.MSG_SET_SLEEP_SCHEDULE_SUCCESS, tracking.strings.MSG_SET_SLEEP_SCHEDULE_ERROR))
                    .done(function () {
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };

            openActionDialog(tracking.strings.SET_SLEEP_SCHEDULE, tracking.strings.SET_SLEEP_SCHEDULE, callback, null, null, true);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPSetTxMute', function (e) {
            e.preventDefault();
            var isFormValid = $(tracking.data.validation.idpModemRegistration.currentForm).valid();
            if (!isFormValid)
                return;

            var mute = $j('#IDPTxMute').val();
            var status = document.getElementById('idp-query-status');
            var btn = this;
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    assetIds: assetIds,
                    mute: mute,
                    groupIds: groupIds,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries
                };

                $.when(handleAjaxFormSubmission('IDPSendSetTxMuteRequest', data, btn, status, tracking.strings.MSG_SET_TX_MUTE_SUCCESS, tracking.strings.MSG_SET_TX_MUTE_ERROR))
                    .done(function () {
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };

            openActionDialog(tracking.strings.SET_TX_MUTE, tracking.strings.SET_TX_MUTE, callback, null, null, true);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPGSMHealthCheck', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendGSMHealthCheckRequest', data, this, status,
                tracking.strings.MSG_GSM_CHECK_SUCCESS,
                tracking.strings.MSG_GSM_CHECK_ERROR,
                null, null, null,
                tracking.strings.REQUEST_GSM_HEALTH_CHECK,
                tracking.strings.REQUEST_GSM_HEALTH_CHECK);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPEyeAlertImage', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.idpEyeAlertImageRequest.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var imageType = $j('#IDPEyeAlertImageType').val();
            var data = { imageType: imageType };
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendEyeAlertImageRequest', data, this, status,
                tracking.strings.MSG_IDP_REQUEST_IMAGE_SUCCESS,
                tracking.strings.MSG_IDP_REQUEST_IMAGE_ERROR,
                null, null, null,
                tracking.strings.REQUEST_IMAGE,
                tracking.strings.REQUEST_IMAGE);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPARCRequestHeartbeat', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendARCHeartbeatPoll', data, this, status,
                tracking.strings.MSG_IDP_REQUEST_HEARTBEAT_SUCCESS,
                tracking.strings.MSG_IDP_REQUEST_HEARTBEAT_ERROR,
                null, null, null,
                tracking.strings.REQUEST_HEARTBEAT,
                tracking.strings.REQUEST_HEARTBEAT);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#SynchronizeAssetGarmin', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('SynchronizeAssetGarmin', data, this, status,
                tracking.strings.MSG_IDP_SYNCHRONIZE_SUCCESS,
                tracking.strings.MSG_IDP_SYNCHRONIZE_ERROR,
                null, null, null,
                tracking.strings.SYNCHRONIZE_GARMIN,
                tracking.strings.SYNCHRONIZE_GARMIN);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#SynchronizeAssetGeofences', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('SynchronizeAssetGeofences', data, this, status,
                tracking.strings.MSG_IDP_SYNCHRONIZE_SUCCESS,
                tracking.strings.MSG_IDP_SYNCHRONIZE_ERROR,
                null, null, null,
                tracking.strings.SYNCHRONIZE_GEOFENCES,
                tracking.strings.SYNCHRONIZE_GEOFENCES);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#ResetGatewayCounters', function (e) {
            e.preventDefault();

            var assetId = $(tracking.data.domNodes.dialogs.idpSendCommand).data('assetId');
            resetIDPCounters(assetId);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPAddPIN', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.idpParameters.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var sin = $('#txtIDPSIN').val();
            var pin = $('#txtIDPPIN').val();
            var val = $('#txtIDPValue').val();
            var type = $('#ddlIDPType').val();

            if (sin == '' || pin == '' || val == '' || type == '') {
                return;
            }

            // add parameters to table, hidden fields
            var hidden = el('input', { type: 'hidden' }); // used?
            hidden.parameter = { SIN: sin, PIN: pin, Value: val, Type: type };
            $(hidden).data('parameter', hidden.parameter);

            var body = document.getElementById('IDPParameters').querySelector('tbody');
            var lastRow = _.last(body.querySelectorAll('tr'));
            var row = el('tr', [
                el('td', sin),
                el('td', pin),
                el('td', val + ' (' + type + ')'),
                el('td', [
                    el('button.remove.btn.btn-outline-danger', { title: tracking.strings.DELETE }, svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#trash-alt-light' } }))),
                    hidden
                ])
            ]);
            mount(body, row, lastRow);

            $('#txtIDPPIN').val('');
            $('#txtIDPValue').val('');
        });

        function idpSendCommand() {
            var isFormValid = $(tracking.data.validation.idpCommandLog.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var cmd = document.getElementById('IDPCommand');
            cmd.focus();
            var command = cmd.value;
            var type = document.getElementById('IDPCommandType').value;
            var isLua = false;
            var isJson = false;
            if (type === 'lua') {
                isLua = true;
            } else if (type === 'json') {
                isJson = true;
            }

            var status = document.getElementById('idp-query-status');
            var btn = this;

            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    assetIds: assetIds,
                    groupIds: groupIds,
                    command: command,
                    isLua: isLua,
                    isJson: isJson,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries
                };

                $.when(handleAjaxFormSubmission('IDPSendExecuteCommandRequest', data, btn, status, null, tracking.strings.MSG_COMMAND_ERROR))
                    .done(function () {
                        requestIDPCommandLog(id, true);
                        var cmd = document.getElementById('IDPCommand');
                        cmd.value = '';
                        tracking.state.nextFocus = cmd;

                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };

            openActionDialog(tracking.strings.SEND_COMMAND, tracking.strings.SEND_COMMAND, callback);
        }

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#IDPSendCommand', function (e) {
            e.preventDefault();

            idpSendCommand();
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPSetParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.idpParameters.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var parameters = [];
            $j('#IDPParameters input[type=hidden]').each(function (index, elem) {
                parameters.push($j(this).data('parameter'));
            });

            if (parameters.length == 0) {
                return;
            }

            var data = { parameters: parameters };
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendSetParametersRequest', data, this, status,
                tracking.strings.MSG_IDP_PARAMETERS_SUCCESS,
                tracking.strings.MSG_IDP_PARAMETERS_ERROR,
                null, null, null,
                tracking.strings.SET_PARAMETERS,
                tracking.strings.SET_PARAMETERS);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#QueryIDPCoreProperties', function (e) {
            e.preventDefault();

            var btn = this;
            var status = document.getElementById('idp-query-status');
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries,
                    groupIds: groupIds,
                    assetIds: assetIds
                };

                $.when(handleAjaxFormSubmission('IDPSendGetCorePropertiesRequest', data, btn, status, tracking.strings.MSG_GET_IDP_PARAMETERS_SUCCESS, tracking.strings.MSG_GET_IDP_PARAMETERS_ERROR))
                    .done(function () {
                        requestAVLInformation(id);
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };

            openActionDialog(tracking.strings.BUTTON_IDP_GET_CORE_PARAMETERS, tracking.strings.BUTTON_IDP_GET_CORE_PARAMETERS, callback);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#QueryAVLGeofenceProperties', function (e) {
            e.preventDefault();

            var btn = this;
            var status = document.getElementById('idp-query-status');
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries,
                    groupIds: groupIds,
                    assetIds: assetIds
                };

                $.when(handleAjaxFormSubmission('IDPSendGetGeofencePropertiesRequest', data, btn, status, tracking.strings.MSG_GET_IDP_PARAMETERS_SUCCESS, tracking.strings.MSG_GET_IDP_PARAMETERS_ERROR))
                    .done(function () {
                        requestAVLInformation(id);
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };

            openActionDialog(tracking.strings.GET_GEOFENCE_PARAMETERS, tracking.strings.GET_GEOFENCE_PARAMETERS, callback);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#QueryIDPImmobilizerProperties', function (e) {
            e.preventDefault();

            var btn = this;
            var status = document.getElementById('idp-query-status');
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    assetIds: assetIds,
                    groupIds: groupIds,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries
                };

                $.when(handleAjaxFormSubmission('IDPSendGetImmobilizerPropertiesRequest', data, btn, status, tracking.strings.MSG_GET_IMMOBILIZER_PARAMETERS_SUCCESS, tracking.strings.MSG_GET_IMMOBILIZER_PARAMETERS_ERROR))
                    .done(function () {
                        requestAVLInformation(id);
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };
            openActionDialog(tracking.strings.GET_IMMOBILIZER_PARAMETERS, tracking.strings.GET_IMMOBILIZER_PARAMETERS, callback);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#QueryIDPGarminProperties', function (e) {
            e.preventDefault();

            var btn = this;
            var status = document.getElementById('idp-query-status');
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    assetIds: assetIds,
                    groupIds: groupIds,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries
                };

                $.when(handleAjaxFormSubmission('IDPSendGetGarminPropertiesRequest', data, btn, status, tracking.strings.MSG_GET_GARMIN_PARAMETERS_SUCCESS, tracking.strings.MSG_GET_GARMIN_PARAMETERS_ERROR))
                    .done(function () {
                        requestAVLInformation(id);
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };
            openActionDialog(tracking.strings.BUTTON_IDP_GET_GARMIN_PARAMETERS, tracking.strings.BUTTON_IDP_GET_GARMIN_PARAMETERS, callback);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#QueryIDPARCProperties', function (e) {
            e.preventDefault();

            var btn = this;
            var status = document.getElementById('idp-query-status');
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    assetIds: assetIds,
                    groupIds: groupIds,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries
                };

                $.when(handleAjaxFormSubmission('IDPSendGetARCPropertiesRequest', data, btn, status, tracking.strings.MSG_GET_ARC_PROPERTIES_SUCCESS, tracking.strings.MSG_GET_ARC_PROPERTIES_ERROR))
                    .done(function () {
                        requestAVLInformation(id);
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };
            openActionDialog(tracking.strings.BUTTON_IDP_GET_ARC_PROPERTIES, tracking.strings.BUTTON_IDP_GET_ARC_PROPERTIES, callback);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#QueryIDPUtilityConfigurations', function (e) {
            e.preventDefault();

            var btn = this;
            var status = document.getElementById('idp-query-status');
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    assetIds: assetIds,
                    groupIds: groupIds,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries
                };

                $.when(handleAjaxFormSubmission('IDPSendUtilityGetConfigurationsRequest', data, btn, status, tracking.strings.MSG_GET_UTILITY_CONFIGURATIONS_SUCCESS, tracking.strings.MSG_GET_UTILITY_CONFIGURATIONS_ERROR))
                    .done(function () {
                        requestAVLInformation(id);
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };
            openActionDialog(tracking.strings.BUTTON_IDP_GET_UTILITY_CONFIGURATIONS, tracking.strings.BUTTON_IDP_GET_UTILITY_CONFIGURATIONS, callback);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#QueryAVLProperties,#QueryAVLIOProperties', function (e) {
            e.preventDefault();

            var btn = this;
            var status = document.getElementById('idp-query-status');
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries,
                    groupIds: groupIds,
                    assetIds: assetIds
                };

                $.when(handleAjaxFormSubmission('IDPSendGetAVLPropertiesRequest', data, btn, status, tracking.strings.MSG_GET_IDP_PARAMETERS_SUCCESS, tracking.strings.MSG_GET_IDP_PARAMETERS_ERROR))
                    .done(function () {
                        requestAVLInformation(id);
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            };

            openActionDialog(tracking.strings.BUTTON_AVL_GET_PARAMETERS, tracking.strings.BUTTON_AVL_GET_PARAMETERS, callback);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPSetIDPCoreParameters', function (e) {
            e.preventDefault();
            var form = $j('#idp-core-parameters-form');
            var isFormValid = $(tracking.data.validation.idpCoreParameters.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var SystemExecutionWatchdogTimeout = getValueIfCheckboxSelected('#txtIDPSystemExecutionWatchdogTimeout');
            var SystemAutoGCMemThreshold = getValueIfCheckboxSelected('#txtIDPSystemAutoGCMemThreshold');
            var SystemLedControl = getValueIfCheckboxSelected('#ddlIDPSystemLedControl');
            var PowerExtPowerPresentStateDetect = getValueIfCheckboxSelected('#ddlIDPPowerExtPowerPresentStateDetect');
            var MessageRetry = getValueIfCheckboxSelected('input[name=rbIDPMessageRetry]:checked');
            var MessageTransport1 = getValueIfCheckboxSelected('#txtIDPMessageTransport1');
            var MessageTransport2 = getValueIfCheckboxSelected('#txtIDPMessageTransport2');
            var MessageTimeout1 = getValueIfCheckboxSelected('#txtIDPMessageTimeout1');
            var MessageTimeout2 = getValueIfCheckboxSelected('#txtIDPMessageTimeout2');
            var MessageRetrvInterval = getValueIfCheckboxSelected('#txtIDPMessageRetrvInterval');
            var MessageRetrvMultiplier = getValueIfCheckboxSelected('#txtIDPMessageRetrvMultiplier');
            var PositionContinuous = getValueIfCheckboxSelected('#txtIDPPositionContinuous');
            var PositionMode = getValueIfCheckboxSelected('#ddlIDPPositionMode');
            var LogDataLogEnabled = getValueIfCheckboxSelected('input[name=rbIDPLogDataLogEnabled]:checked');
            var LogMaxDataLogSize = getValueIfCheckboxSelected('#txtIDPLogMaxDataLogSize');
            var LogMaxDataLogFiles = getValueIfCheckboxSelected('#txtIDPLogMaxDataLogFiles');
            var LogDebugLogEnabled = getValueIfCheckboxSelected('input[name=rbIDPLogDebugLogEnabled]:checked');
            var LogMaxDebugLogSize = getValueIfCheckboxSelected('#txtIDPLogMaxDebugLogSize');

            // EIO
            var EIOPort1Config = getValueIfCheckboxSelected('#ddlIDPEIOPort1Config');
            var EIOPort1AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEIOPort1AlarmMsg]:checked');
            var EIOPort1AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEIOPort1AlarmLog]:checked');
            var EIOPort1EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEIOPort1EdgeDetect');
            var EIOPort1EdgeSampleCount = getValueIfCheckboxSelected('#txtIDPEIOPort1EdgeDetect');
            var EIOPort1EdgeSampleError = getValueIfCheckboxSelected('#txtIDPEIOPort1EdgeSampleError');
            var EIOPort1AnalogSampleRate = getValueIfCheckboxSelected('#txtIDPEIOPort1AnalogSampleRate');
            var EIOPort1AnalogSampleFilter = getValueIfCheckboxSelected('#txtIDPEIOPort1AnalogSampleFilter');
            var EIOPort1AnalogLowThreshold = getValueIfCheckboxSelected('#txtIDPEIOPort1AnalogLowThreshold');
            var EIOPort1AnalogHighThreshold = getValueIfCheckboxSelected('#txtIDPEIOPort1AnalogHighThreshold');
            var EIOPort2Config = getValueIfCheckboxSelected('#ddlIDPEIOPort2Config');
            var EIOPort2AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEIOPort2AlarmMsg]:checked');
            var EIOPort2AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEIOPort2AlarmLog]:checked');
            var EIOPort2EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEIOPort2EdgeDetect');
            var EIOPort2EdgeSampleCount = getValueIfCheckboxSelected('#txtIDPEIOPort2EdgeDetect');
            var EIOPort2EdgeSampleError = getValueIfCheckboxSelected('#txtIDPEIOPort2EdgeSampleError');
            var EIOPort2AnalogSampleRate = getValueIfCheckboxSelected('#txtIDPEIOPort2AnalogSampleRate');
            var EIOPort2AnalogSampleFilter = getValueIfCheckboxSelected('#txtIDPEIOPort2AnalogSampleFilter');
            var EIOPort2AnalogLowThreshold = getValueIfCheckboxSelected('#txtIDPEIOPort2AnalogLowThreshold');
            var EIOPort2AnalogHighThreshold = getValueIfCheckboxSelected('#txtIDPEIOPort2AnalogHighThreshold');
            var EIOPort3Config = getValueIfCheckboxSelected('#ddlIDPEIOPort3Config');
            var EIOPort3AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEIOPort3AlarmMsg]:checked');
            var EIOPort3AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEIOPort3AlarmLog]:checked');
            var EIOPort3EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEIOPort3EdgeDetect');
            var EIOPort3EdgeSampleCount = getValueIfCheckboxSelected('#txtIDPEIOPort3EdgeDetect');
            var EIOPort3EdgeSampleError = getValueIfCheckboxSelected('#txtIDPEIOPort3EdgeSampleError');
            var EIOPort3AnalogSampleRate = getValueIfCheckboxSelected('#txtIDPEIOPort3AnalogSampleRate');
            var EIOPort3AnalogSampleFilter = getValueIfCheckboxSelected('#txtIDPEIOPort3AnalogSampleFilter');
            var EIOPort3AnalogLowThreshold = getValueIfCheckboxSelected('#txtIDPEIOPort3AnalogLowThreshold');
            var EIOPort3AnalogHighThreshold = getValueIfCheckboxSelected('#txtIDPEIOPort3AnalogHighThreshold');
            var EIOPort4Config = getValueIfCheckboxSelected('#ddlIDPEIOPort4Config');
            var EIOPort4AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEIOPort4AlarmMsg]:checked');
            var EIOPort4AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEIOPort4AlarmLog]:checked');
            var EIOPort4EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEIOPort4EdgeDetect');
            var EIOPort4EdgeSampleCount = getValueIfCheckboxSelected('#txtIDPEIOPort4EdgeDetect');
            var EIOPort4EdgeSampleError = getValueIfCheckboxSelected('#txtIDPEIOPort4EdgeSampleError');
            var EIOPort4AnalogSampleRate = getValueIfCheckboxSelected('#txtIDPEIOPort4AnalogSampleRate');
            var EIOPort4AnalogSampleFilter = getValueIfCheckboxSelected('#txtIDPEIOPort4AnalogSampleFilter');
            var EIOPort4AnalogLowThreshold = getValueIfCheckboxSelected('#txtIDPEIOPort4AnalogLowThreshold');
            var EIOPort4AnalogHighThreshold = getValueIfCheckboxSelected('#txtIDPEIOPort4AnalogHighThreshold');
            var EIOTemperatureAlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEIOTemperatureAlarmMsg]:checked');
            var EIOTemperatureAlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEIOTemperatureAlarmLog]:checked');
            var EIOTemperatureSampleRate = getValueIfCheckboxSelected('#txtIDPEIOTemperatureSampleRate');
            var EIOTemperatureSampleFilter = getValueIfCheckboxSelected('#txtIDPEIOTemperatureSampleFilter');
            var EIOTemperatureLowThreshold = getValueIfCheckboxSelected('#txtIDPEIOTemperatureLowThreshold');
            var EIOTemperatureHighThreshold = getValueIfCheckboxSelected('#txtIDPEIOTemperatureHighThreshold');
            var EIOPowerAlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEIOPowerAlarmMsg]:checked');
            var EIOPowerAlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEIOPowerAlarmLog]:checked');
            var EIOPowerSampleRate = getValueIfCheckboxSelected('#txtIDPEIOPowerSampleRate');
            var EIOPowerSampleFilter = getValueIfCheckboxSelected('#txtIDPEIOPowerSampleFilter');
            var EIOPowerLowThreshold = getValueIfCheckboxSelected('#txtIDPEIOPowerLowThreshold');
            var EIOPowerHighThreshold = getValueIfCheckboxSelected('#txtIDPEIOPowerHighThreshold');
            var EIOOutputSink5Default = getValueIfCheckboxSelected('#ddlIDPEIOOutputSink5Default');
            var EIOOutputSink6Default = getValueIfCheckboxSelected('#ddlIDPEIOOutputSink6Default');

            // GPRS
            var GPRSSIM1PIN = getValueIfCheckboxSelected('#txtIDPGPRSSIM1PIN');
            var GPRSSIM1APN = getValueIfCheckboxSelected('#txtIDPGPRSSIM1APN');
            var GPRSSIM1Username = getValueIfCheckboxSelected('#txtIDPGPRSSIM1Username');
            var GPRSSIM1Password = getValueIfCheckboxSelected('#txtIDPGPRSSIM1Password');
            var GPRSSIM1DNS1 = getValueIfCheckboxSelected('#txtIDPGPRSSIM1DNS1');
            var GPRSSIM1DNS2 = getValueIfCheckboxSelected('#txtIDPGPRSSIM1DNS2');
            var GPRSSIM2PIN = getValueIfCheckboxSelected('#txtIDPGPRSSIM2PIN');
            var GPRSSIM2APN = getValueIfCheckboxSelected('#txtIDPGPRSSIM2APN');
            var GPRSSIM2Username = getValueIfCheckboxSelected('#txtIDPGPRSSIM2Username');
            var GPRSSIM2Password = getValueIfCheckboxSelected('#txtIDPGPRSSIM2Password');
            var GPRSSIM2DNS1 = getValueIfCheckboxSelected('#txtIDPGPRSSIM2DNS1');
            var GPRSSIM2DNS2 = getValueIfCheckboxSelected('#txtIDPGPRSSIM2DNS2');
            var GPRSServer1 = getValueIfCheckboxSelected('#txtIDPGPRSServer1');
            var GPRSPort1 = getValueIfCheckboxSelected('#txtIDPGPRSPort1');
            var GPRSPollingInterval = getValueIfCheckboxSelected('#txtIDPGPRSPollingInterval');
            var GPRSActiveSIM = getValueIfCheckboxSelected('#ddlIDPGPRSActiveSIM');
            var GPRSTargetMode = getValueIfCheckboxSelected('#ddlIDPGPRSTargetMode');

            // EEIO
            var EEIOInput1Config = getValueIfCheckboxSelected('#ddlIDPEEIOInput1Config');
            var EEIOInput1EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEEIOInput1EdgeDetect');
            var EEIOInput1EdgeFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInput1EdgeFilterCount');
            var EEIOInput1AnalogSampleRate = getValueIfCheckboxSelected('#txtIDPEEIOInput1AnalogSampleRate');
            var EEIOInput1AnalogFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInput1AnalogFilterCount');
            var EEIOInput1AnalogLowThreshold = getValueIfCheckboxSelected('#txtIDPEEIOInput1AnalogLowThreshold');
            var EEIOInput1AnalogHighThreshold = getValueIfCheckboxSelected('#txtIDPEEIOInput1AnalogHighThreshold');
            var EEIOInput1AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOInput1AlarmMsg]:checked');
            var EEIOInput1AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOInput1AlarmLog]:checked');
            var EEIOInput2Config = getValueIfCheckboxSelected('#ddlIDPEEIOInput2Config');
            var EEIOInput2EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEEIOInput2EdgeDetect');
            var EEIOInput2EdgeFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInput2EdgeFilterCount');
            var EEIOInput2AnalogSampleRate = getValueIfCheckboxSelected('#txtIDPEEIOInput2AnalogSampleRate');
            var EEIOInput2AnalogFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInput2AnalogFilterCount');
            var EEIOInput2AnalogLowThreshold = getValueIfCheckboxSelected('#txtIDPEEIOInput2AnalogLowThreshold');
            var EEIOInput2AnalogHighThreshold = getValueIfCheckboxSelected('#txtIDPEEIOInput2AnalogHighThreshold');
            var EEIOInput2AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOInput2AlarmMsg]:checked');
            var EEIOInput2AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOInput2AlarmLog]:checked');
            var EEIOInput3Config = getValueIfCheckboxSelected('#ddlIDPEEIOInput3Config');
            var EEIOInput3EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEEIOInput3EdgeDetect');
            var EEIOInput3EdgeFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInput3EdgeFilterCount');
            var EEIOInput3AnalogSampleRate = getValueIfCheckboxSelected('#txtIDPEEIOInput3AnalogSampleRate');
            var EEIOInput3AnalogFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInput3AnalogFilterCount');
            var EEIOInput3AnalogLowThreshold = getValueIfCheckboxSelected('#txtIDPEEIOInput3AnalogLowThreshold');
            var EEIOInput3AnalogHighThreshold = getValueIfCheckboxSelected('#txtIDPEEIOInput3AnalogHighThreshold');
            var EEIOInput3AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOInput3AlarmMsg]:checked');
            var EEIOInput3AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOInput3AlarmLog]:checked');
            var EEIOInput4Config = getValueIfCheckboxSelected('#ddlIDPEEIOInput4Config');
            var EEIOInput4EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEEIOInput4EdgeDetect');
            var EEIOInput4EdgeFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInput4EdgeFilterCount');
            var EEIOInput4AnalogSampleRate = getValueIfCheckboxSelected('#txtIDPEEIOInput4AnalogSampleRate');
            var EEIOInput4AnalogFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInput4AnalogFilterCount');
            var EEIOInput4AnalogLowThreshold = getValueIfCheckboxSelected('#txtIDPEEIOInput4AnalogLowThreshold');
            var EEIOInput4AnalogHighThreshold = getValueIfCheckboxSelected('#txtIDPEEIOInput4AnalogHighThreshold');
            var EEIOInput4AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOInput4AlarmMsg]:checked');
            var EEIOInput4AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOInput4AlarmLog]:checked');
            var EEIOInputDigital5EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEEIOInputDigital5EdgeDetect');
            var EEIOInputDigital5EdgeFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInputDigital5EdgeFilterCount');
            var EEIOInputDigital5AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital5AlarmMsg]:checked');
            var EEIOInputDigital5AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital5AlarmLog]:checked');
            var EEIOInputDigital6EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEEIOInputDigital6EdgeDetect');
            var EEIOInputDigital6EdgeFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInputDigital6EdgeFilterCount');
            var EEIOInputDigital6AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital6AlarmMsg]:checked');
            var EEIOInputDigital6AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital6AlarmLog]:checked');
            var EEIOInputDigital7EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEEIOInputDigital7EdgeDetect');
            var EEIOInputDigital7EdgeFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInputDigital7EdgeFilterCount');
            var EEIOInputDigital7AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital7AlarmMsg]:checked');
            var EEIOInputDigital7AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital7AlarmLog]:checked');
            var EEIOInputDigital8EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEEIOInputDigital8EdgeDetect');
            var EEIOInputDigital8EdgeFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInputDigital8EdgeFilterCount');
            var EEIOInputDigital8AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital8AlarmMsg]:checked');
            var EEIOInputDigital8AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital8AlarmLog]:checked');
            var EEIOInputDigital9EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEEIOInputDigital9EdgeDetect');
            var EEIOInputDigital9EdgeFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInputDigital9EdgeFilterCount');
            var EEIOInputDigital9AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital9AlarmMsg]:checked');
            var EEIOInputDigital9AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital9AlarmLog]:checked');
            var EEIOInputDigital10EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEEIOInputDigital10EdgeDetect');
            var EEIOInputDigital10EdgeFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInputDigital10EdgeFilterCount');
            var EEIOInputDigital10AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital10AlarmMsg]:checked');
            var EEIOInputDigital10AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital10AlarmLog]:checked');
            var EEIOInputDigital11EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEEIOInputDigital11EdgeDetect');
            var EEIOInputDigital11EdgeFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInputDigital11EdgeFilterCount');
            var EEIOInputDigital11AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital11AlarmMsg]:checked');
            var EEIOInputDigital11AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputDigital11AlarmLog]:checked');
            var EEIOInputIgnition12EdgeDetect = getValueIfCheckboxSelected('#ddlIDPEEIOInputIgnition12EdgeDetect');
            var EEIOInputIgnition12EdgeFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOInputIgnition12EdgeFilterCount');
            var EEIOInputIgnition12AlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputIgnition12AlarmMsg]:checked');
            var EEIOInputIgnition12AlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOInputIgnition12AlarmLog]:checked');
            var EEIOutputSink14Default = getValueIfCheckboxSelected('#ddlIDPEEIOutputSink14Default');
            var EEIOutputSink15Default = getValueIfCheckboxSelected('#ddlIDPEEIOutputSink15Default');
            var EEIOutputSink16Default = getValueIfCheckboxSelected('#ddlIDPEEIOutputSink16Default');
            var EEIOutputSink17Default = getValueIfCheckboxSelected('#ddlIDPEEIOutputSink17Default');
            var EEIOutputSink18Default = getValueIfCheckboxSelected('#ddlIDPEEIOutputSink18Default');
            var EEIOTemperatureSampleRate = getValueIfCheckboxSelected('#txtIDPEEIOTemperatureSampleRate');
            var EEIOTemperatureFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOTemperatureFilterCount');
            var EEIOTemperatureLowThreshold = getValueIfCheckboxSelected('#txtIDPEEIOTemperatureLowThreshold');
            var EEIOTemperatureHighThreshold = getValueIfCheckboxSelected('#txtIDPEEIOTemperatureHighThreshold');
            var EEIOTemperatureAlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOTemperatureAlarmMsg]:checked');
            var EEIOTemperatureAlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOTemperatureAlarmLog]:checked');
            var EEIOExternalPowerSampleRate = getValueIfCheckboxSelected('#txtIDPEEIOExternalPowerSampleRate');
            var EEIOExternalPowerFilterCount = getValueIfCheckboxSelected('#txtIDPEEIOExternalPowerFilterCount');
            var EEIOExternalPowerLowThreshold = getValueIfCheckboxSelected('#txtIDPEEIOExternalPowerLowThreshold');
            var EEIOExternalPowerHighThreshold = getValueIfCheckboxSelected('#txtIDPEEIOExternalPowerHighThreshold');
            var EEIOExternalPowerAlarmMsg = getValueIfCheckboxSelected('input[name=rbIDPEEIOExternalPowerAlarmMsg]:checked');
            var EEIOExternalPowerAlarmLog = getValueIfCheckboxSelected('input[name=rbIDPEEIOExternalPowerAlarmLog]:checked');

            // Accel
            var AccelSleepInterval = getValueIfCheckboxSelected('#txtIDPAccelSleepInterval');
            var AccelGRange = getValueIfCheckboxSelected('#txtIDPAccelGRange');
            var AccelMotionEventThreshold = getValueIfCheckboxSelected('#txtIDPAccelMotionEventThreshold');
            var AccelMotionEventXAxisEn = getValueIfCheckboxSelected('input[name=AccelMotionEventXAxisEn]:checked');
            var AccelMotionEventYAxisEn = getValueIfCheckboxSelected('input[name=AccelMotionEventYAxisEn]:checked');
            var AccelMotionEventZAxisEn = getValueIfCheckboxSelected('input[name=AccelMotionEventZAxisEn]:checked');

            // Report
            var Report1Enabled = getValueIfCheckboxSelected('input[name=rbIDPReport1Enabled]:checked');
            var Report1MIN = getValueIfCheckboxSelected('#ddlIDPReport1ReportType');
            var Report1Interval = getValueIfCheckboxSelected('#txtIDPReport1TimeInterval');

            var properties = {
                SystemExecutionWatchdogTimeout: SystemExecutionWatchdogTimeout,
                SystemAutoGCMemThreshold: SystemAutoGCMemThreshold,
                SystemLedControl: SystemLedControl,
                PowerExtPowerPresentStateDetect: PowerExtPowerPresentStateDetect,
                MessageRetry: MessageRetry,
                MessageTransport1: MessageTransport1,
                MessageTransport2: MessageTransport2,
                MessageTimeout1: MessageTimeout1,
                MessageTimeout2: MessageTimeout2,
                MessageRetrvInterval: MessageRetrvInterval,
                MessageRetrvMultiplier: MessageRetrvMultiplier,
                PositionContinuous: PositionContinuous,
                PositionMode: PositionMode,
                LogDataLogEnabled: LogDataLogEnabled,
                LogMaxDataLogSize: LogMaxDataLogSize,
                LogMaxDataLogFiles: LogMaxDataLogFiles,
                LogDebugLogEnabled: LogDebugLogEnabled,
                LogMaxDebugLogSize: LogMaxDebugLogSize,
                EIOPort1Config: EIOPort1Config,
                EIOPort1AlarmMsg: EIOPort1AlarmMsg,
                EIOPort1AlarmLog: EIOPort1AlarmLog,
                EIOPort1EdgeDetect: EIOPort1EdgeDetect,
                EIOPort1EdgeSampleCount: EIOPort1EdgeSampleCount,
                EIOPort1EdgeSampleError: EIOPort1EdgeSampleError,
                EIOPort1AnalogSampleRate: EIOPort1AnalogSampleRate,
                EIOPort1AnalogSampleFilter: EIOPort1AnalogSampleFilter,
                EIOPort1AnalogLowThreshold: EIOPort1AnalogLowThreshold,
                EIOPort1AnalogHighThreshold: EIOPort1AnalogHighThreshold,
                EIOPort2Config: EIOPort2Config,
                EIOPort2AlarmMsg: EIOPort2AlarmMsg,
                EIOPort2AlarmLog: EIOPort2AlarmLog,
                EIOPort2EdgeDetect: EIOPort2EdgeDetect,
                EIOPort2EdgeSampleCount: EIOPort2EdgeSampleCount,
                EIOPort2EdgeSampleError: EIOPort2EdgeSampleError,
                EIOPort2AnalogSampleRate: EIOPort2AnalogSampleRate,
                EIOPort2AnalogSampleFilter: EIOPort2AnalogSampleFilter,
                EIOPort2AnalogLowThreshold: EIOPort2AnalogLowThreshold,
                EIOPort2AnalogHighThreshold: EIOPort2AnalogHighThreshold,
                EIOPort3Config: EIOPort3Config,
                EIOPort3AlarmMsg: EIOPort3AlarmMsg,
                EIOPort3AlarmLog: EIOPort3AlarmLog,
                EIOPort3EdgeDetect: EIOPort3EdgeDetect,
                EIOPort3EdgeSampleCount: EIOPort3EdgeSampleCount,
                EIOPort3EdgeSampleError: EIOPort3EdgeSampleError,
                EIOPort3AnalogSampleRate: EIOPort3AnalogSampleRate,
                EIOPort3AnalogSampleFilter: EIOPort3AnalogSampleFilter,
                EIOPort3AnalogLowThreshold: EIOPort3AnalogLowThreshold,
                EIOPort3AnalogHighThreshold: EIOPort3AnalogHighThreshold,
                EIOPort4Config: EIOPort4Config,
                EIOPort4AlarmMsg: EIOPort4AlarmMsg,
                EIOPort4AlarmLog: EIOPort4AlarmLog,
                EIOPort4EdgeDetect: EIOPort4EdgeDetect,
                EIOPort4EdgeSampleCount: EIOPort4EdgeSampleCount,
                EIOPort4EdgeSampleError: EIOPort4EdgeSampleError,
                EIOPort4AnalogSampleRate: EIOPort4AnalogSampleRate,
                EIOPort4AnalogSampleFilter: EIOPort4AnalogSampleFilter,
                EIOPort4AnalogLowThreshold: EIOPort4AnalogLowThreshold,
                EIOPort4AnalogHighThreshold: EIOPort4AnalogHighThreshold,
                EIOTemperatureAlarmMsg: EIOTemperatureAlarmMsg,
                EIOTemperatureAlarmLog: EIOTemperatureAlarmLog,
                EIOTemperatureSampleRate: EIOTemperatureSampleRate,
                EIOTemperatureSampleFilter: EIOTemperatureSampleFilter,
                EIOTemperatureLowThreshold: EIOTemperatureLowThreshold,
                EIOTemperatureHighThreshold: EIOTemperatureHighThreshold,
                EIOPowerAlarmMsg: EIOPowerAlarmMsg,
                EIOPowerAlarmLog: EIOPowerAlarmLog,
                EIOPowerSampleRate: EIOPowerSampleRate,
                EIOPowerSampleFilter: EIOPowerSampleFilter,
                EIOPowerLowThreshold: EIOPowerLowThreshold,
                EIOPowerHighThreshold: EIOPowerHighThreshold,
                EIOOutputSink5Default: EIOOutputSink5Default,
                EIOOutputSink6Default: EIOOutputSink6Default,
                GPRSSIM1PIN: GPRSSIM1PIN,
                GPRSSIM1APN: GPRSSIM1APN,
                GPRSSIM1Username: GPRSSIM1Username,
                GPRSSIM1Password: GPRSSIM1Password,
                GPRSSIM1DNS1: GPRSSIM1DNS1,
                GPRSSIM1DNS2: GPRSSIM1DNS2,
                GPRSSIM2PIN: GPRSSIM2PIN,
                GPRSSIM2APN: GPRSSIM2APN,
                GPRSSIM2Username: GPRSSIM2Username,
                GPRSSIM2Password: GPRSSIM2Password,
                GPRSSIM2DNS1: GPRSSIM2DNS1,
                GPRSSIM2DNS2: GPRSSIM2DNS2,
                GPRSActiveSIM: GPRSActiveSIM,
                GPRSTargetMode: GPRSTargetMode,
                GPRSServer1: GPRSServer1,
                GPRSPort1: GPRSPort1,
                GPRSPollingInterval: GPRSPollingInterval,
                EEIOInput1Config: EEIOInput1Config,
                EEIOInput1EdgeDetect: EEIOInput1EdgeDetect,
                EEIOInput1EdgeFilterCount: EEIOInput1EdgeFilterCount,
                EEIOInput1AnalogSampleRate: EEIOInput1AnalogSampleRate,
                EEIOInput1AnalogFilterCount: EEIOInput1AnalogFilterCount,
                EEIOInput1AnalogLowThreshold: EEIOInput1AnalogLowThreshold,
                EEIOInput1AnalogHighThreshold: EEIOInput1AnalogHighThreshold,
                EEIOInput1AlarmMsg: EEIOInput1AlarmMsg,
                EEIOInput1AlarmLog: EEIOInput1AlarmLog,
                EEIOInput2Config: EEIOInput2Config,
                EEIOInput2EdgeDetect: EEIOInput2EdgeDetect,
                EEIOInput2EdgeFilterCount: EEIOInput2EdgeFilterCount,
                EEIOInput2AnalogSampleRate: EEIOInput2AnalogSampleRate,
                EEIOInput2AnalogFilterCount: EEIOInput2AnalogFilterCount,
                EEIOInput2AnalogLowThreshold: EEIOInput2AnalogLowThreshold,
                EEIOInput2AnalogHighThreshold: EEIOInput2AnalogHighThreshold,
                EEIOInput2AlarmMsg: EEIOInput2AlarmMsg,
                EEIOInput2AlarmLog: EEIOInput2AlarmLog,
                EEIOInput3Config: EEIOInput3Config,
                EEIOInput3EdgeDetect: EEIOInput3EdgeDetect,
                EEIOInput3EdgeFilterCount: EEIOInput3EdgeFilterCount,
                EEIOInput3AnalogSampleRate: EEIOInput3AnalogSampleRate,
                EEIOInput3AnalogFilterCount: EEIOInput3AnalogFilterCount,
                EEIOInput3AnalogLowThreshold: EEIOInput3AnalogLowThreshold,
                EEIOInput3AnalogHighThreshold: EEIOInput3AnalogHighThreshold,
                EEIOInput3AlarmMsg: EEIOInput3AlarmMsg,
                EEIOInput3AlarmLog: EEIOInput3AlarmLog,
                EEIOInput4Config: EEIOInput4Config,
                EEIOInput4EdgeDetect: EEIOInput4EdgeDetect,
                EEIOInput4EdgeFilterCount: EEIOInput4EdgeFilterCount,
                EEIOInput4AnalogSampleRate: EEIOInput4AnalogSampleRate,
                EEIOInput4AnalogFilterCount: EEIOInput4AnalogFilterCount,
                EEIOInput4AnalogLowThreshold: EEIOInput4AnalogLowThreshold,
                EEIOInput4AnalogHighThreshold: EEIOInput4AnalogHighThreshold,
                EEIOInput4AlarmMsg: EEIOInput4AlarmMsg,
                EEIOInput4AlarmLog: EEIOInput4AlarmLog,
                EEIOInputDigital5EdgeDetect: EEIOInputDigital5EdgeDetect,
                EEIOInputDigital5EdgeFilterCount: EEIOInputDigital5EdgeFilterCount,
                EEIOInputDigital5AlarmMsg: EEIOInputDigital5AlarmMsg,
                EEIOInputDigital5AlarmLog: EEIOInputDigital5AlarmLog,
                EEIOInputDigital6EdgeDetect: EEIOInputDigital6EdgeDetect,
                EEIOInputDigital6EdgeFilterCount: EEIOInputDigital6EdgeFilterCount,
                EEIOInputDigital6AlarmMsg: EEIOInputDigital6AlarmMsg,
                EEIOInputDigital6AlarmLog: EEIOInputDigital6AlarmLog,
                EEIOInputDigital7EdgeDetect: EEIOInputDigital7EdgeDetect,
                EEIOInputDigital7EdgeFilterCount: EEIOInputDigital7EdgeFilterCount,
                EEIOInputDigital7AlarmMsg: EEIOInputDigital7AlarmMsg,
                EEIOInputDigital7AlarmLog: EEIOInputDigital7AlarmLog,
                EEIOInputDigital8EdgeDetect: EEIOInputDigital8EdgeDetect,
                EEIOInputDigital8EdgeFilterCount: EEIOInputDigital8EdgeFilterCount,
                EEIOInputDigital8AlarmMsg: EEIOInputDigital8AlarmMsg,
                EEIOInputDigital8AlarmLog: EEIOInputDigital8AlarmLog,
                EEIOInputDigital9EdgeDetect: EEIOInputDigital9EdgeDetect,
                EEIOInputDigital9EdgeFilterCount: EEIOInputDigital9EdgeFilterCount,
                EEIOInputDigital9AlarmMsg: EEIOInputDigital9AlarmMsg,
                EEIOInputDigital9AlarmLog: EEIOInputDigital9AlarmLog,
                EEIOInputDigital10EdgeDetect: EEIOInputDigital10EdgeDetect,
                EEIOInputDigital10EdgeFilterCount: EEIOInputDigital10EdgeFilterCount,
                EEIOInputDigital10AlarmMsg: EEIOInputDigital10AlarmMsg,
                EEIOInputDigital10AlarmLog: EEIOInputDigital10AlarmLog,
                EEIOInputDigital11EdgeDetect: EEIOInputDigital11EdgeDetect,
                EEIOInputDigital11EdgeFilterCount: EEIOInputDigital11EdgeFilterCount,
                EEIOInputDigital11AlarmMsg: EEIOInputDigital11AlarmMsg,
                EEIOInputDigital11AlarmLog: EEIOInputDigital11AlarmLog,
                EEIOInputIgnition12EdgeDetect: EEIOInputIgnition12EdgeDetect,
                EEIOInputIgnition12EdgeFilterCount: EEIOInputIgnition12EdgeFilterCount,
                EEIOInputIgnition12AlarmMsg: EEIOInputIgnition12AlarmMsg,
                EEIOInputIgnition12AlarmLog: EEIOInputIgnition12AlarmLog,
                EEIOutputSink14Default: EEIOutputSink14Default,
                EEIOutputSink15Default: EEIOutputSink15Default,
                EEIOutputSink16Default: EEIOutputSink16Default,
                EEIOutputSink17Default: EEIOutputSink17Default,
                EEIOutputSink18Default: EEIOutputSink18Default,
                EEIOTemperatureSampleRate: EEIOTemperatureSampleRate,
                EEIOTemperatureFilterCount: EEIOTemperatureFilterCount,
                EEIOTemperatureLowThreshold: EEIOTemperatureLowThreshold,
                EEIOTemperatureHighThreshold: EEIOTemperatureHighThreshold,
                EEIOTemperatureAlarmMsg: EEIOTemperatureAlarmMsg,
                EEIOTemperatureAlarmLog: EEIOTemperatureAlarmLog,
                EEIOExternalPowerSampleRate: EEIOExternalPowerSampleRate,
                EEIOExternalPowerFilterCount: EEIOExternalPowerFilterCount,
                EEIOExternalPowerLowThreshold: EEIOExternalPowerLowThreshold,
                EEIOExternalPowerHighThreshold: EEIOExternalPowerHighThreshold,
                EEIOExternalPowerAlarmMsg: EEIOExternalPowerAlarmMsg,
                EEIOExternalPowerAlarmLog: EEIOExternalPowerAlarmLog,
                AccelSleepInterval: AccelSleepInterval,
                AccelGRange: AccelGRange,
                AccelMotionEventThreshold: AccelMotionEventThreshold,
                AccelMotionEventXAxisEn: AccelMotionEventXAxisEn,
                AccelMotionEventYAxisEn: AccelMotionEventYAxisEn,
                AccelMotionEventZAxisEn: AccelMotionEventZAxisEn,
                Report1Enabled: Report1Enabled,
                Report1MIN: Report1MIN,
                Report1Interval: Report1Interval
            };

            var data = { properties: properties };
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendSetCorePropertiesRequest', data, this, status,
                tracking.strings.MSG_IDP_PARAMETERS_SUCCESS,
                tracking.strings.MSG_IDP_PARAMETERS_ERROR,
                null, null, null,
                tracking.strings.SET_CORE_PARAMETERS,
                tracking.strings.SET_CORE_PARAMETERS);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPSetAVLGeofenceParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.idpAvlGeofence.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var Hysteresis = $('#txtIDPAVLGeofenceHysteresis').val();
            var Interval = $('#txtIDPAVLGeofenceInterval').val();
            var Enabled = $('input[name=rbIDPAVLGeofenceEnabled]:checked').val();
            var SendAlarm = $('input[name=rbIDPAVLGeofenceSendAlarm]:checked').val();
            var data = {
                Enabled: Enabled,
                Interval: Interval,
                Hysteresis: Hysteresis,
                SendAlarm: SendAlarm
            };
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendSetGeofencePropertiesRequest', data, this, status,
                tracking.strings.MSG_IDP_PARAMETERS_SUCCESS,
                tracking.strings.MSG_IDP_PARAMETERS_ERROR,
                null, null, null,
                tracking.strings.SET_GEOFENCE_PARAMETERS,
                tracking.strings.SET_GEOFENCE_PARAMETERS);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPARCSetProperties', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.idpArc.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var eioPorts = [];
            if ((getValueIfCheckboxSelected('#ddlIDPARCEIO1EventType') != null)
                || (getValueIfCheckboxSelected('#txtIDPARCEIO1TriggerDebounce') != null)
                || (getValueIfCheckboxSelected('#txtIDPARCEIO1AnalogThresholds') != null)) {
                var eio1Port = {
                    Port: 1,
                    EventType: getValueIfCheckboxSelected('#ddlIDPARCEIO1EventType'),
                    TriggerDebounce: getValueIfCheckboxSelected('#txtIDPARCEIO1TriggerDebounce'),
                    AnalogThresholds: getValueIfCheckboxSelected('#txtIDPARCEIO1AnalogThresholds')
                };
                eioPorts.push(eio1Port);
            }
            if ((getValueIfCheckboxSelected('#ddlIDPARCEIO2EventType') != null)
                || (getValueIfCheckboxSelected('#txtIDPARCEIO2TriggerDebounce') != null)
                || (getValueIfCheckboxSelected('#txtIDPARCEIO2AnalogThresholds') != null)) {
                var eio2Port = {
                    Port: 2,
                    EventType: getValueIfCheckboxSelected('#ddlIDPARCEIO2EventType'),
                    TriggerDebounce: getValueIfCheckboxSelected('#txtIDPARCEIO2TriggerDebounce'),
                    AnalogThresholds: getValueIfCheckboxSelected('#txtIDPARCEIO2AnalogThresholds')
                };
                eioPorts.push(eio2Port);
            }
            if ((getValueIfCheckboxSelected('#ddlIDPARCEIO3EventType') != null)
                || (getValueIfCheckboxSelected('#txtIDPARCEIO3TriggerDebounce') != null)
                || (getValueIfCheckboxSelected('#txtIDPARCEIO3AnalogThresholds') != null)) {
                var eio3Port = {
                    Port: 3,
                    EventType: getValueIfCheckboxSelected('#ddlIDPARCEIO3EventType'),
                    TriggerDebounce: getValueIfCheckboxSelected('#txtIDPARCEIO3TriggerDebounce'),
                    AnalogThresholds: getValueIfCheckboxSelected('#txtIDPARCEIO3AnalogThresholds')
                };
                eioPorts.push(eio3Port);
            }
            if ((getValueIfCheckboxSelected('#ddlIDPARCEIO4EventType') != null)
                || (getValueIfCheckboxSelected('#txtIDPARCEIO4TriggerDebounce') != null)
                || (getValueIfCheckboxSelected('#txtIDPARCEIO4AnalogThresholds') != null)) {
                var eio4Port = {
                    Port: 4,
                    EventType: getValueIfCheckboxSelected('#ddlIDPARCEIO4EventType'),
                    TriggerDebounce: getValueIfCheckboxSelected('#txtIDPARCEIO4TriggerDebounce'),
                    AnalogThresholds: getValueIfCheckboxSelected('#txtIDPARCEIO4AnalogThresholds')
                };
                eioPorts.push(eio4Port);
            }

            var properties = {
                EIOPorts: eioPorts,
                HeartbeatInterval: getValueIfCheckboxSelected('#txtIDPARCHeartbeatInterval'),
                IsSatQualityReported: getValueIfCheckboxSelected('input[name=rbIDPARCIsSatQualityReported]:checked'),
                ModemPowerMode: getValueIfCheckboxSelected('#ddlIDPARCModemPowerMode'),
                ModemWakeupInterval: getValueIfCheckboxSelected('#ddlIDPARCModemWakeUpInterval'),
                FlashLedOnTransmit: getValueIfCheckboxSelected('input[name=rbIDPARCFlashLedOnTransmit]:checked'),
                EnablePositionReporting: getValueIfCheckboxSelected('input[name=rbIDPARCEnablePositionReporting]:checked'),
                MotionTestInterval: getValueIfCheckboxSelected('#txtIDPARCMotionTestInterval'),
                DistanceThreshold: getValueIfCheckboxSelected('#txtIDPARCDistanceThreshold'),
                ContractHour: getValueIfCheckboxSelected('#txtIDPARCContractHour'),
                LedInstallFlashTime: getValueIfCheckboxSelected('#txtIDPARCLedInstallFlashTime'),
                MaxContractDispersion: getValueIfCheckboxSelected('#txtIDPARCDispersionOffsetSeconds'),
                SendContractDayValues: getValueIfCheckboxSelected('input[name=rbIDPARCSendContractDayValues]:checked'),
                SupplyVoltageThreshold: getValueIfCheckboxSelected('#txtIDPARCSupplyVoltageThreshold'),
                SupplyVoltageTriggerDebounce: getValueIfCheckboxSelected('#txtIDPARCSupplyVoltageTriggerDebounce'),
                IsDebugEnabled: getValueIfCheckboxSelected('input[name=rbIDPARCIsDebugEnabled]:checked')
            };

            var data = {
                properties: properties
            };
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendSetARCPropertiesRequest', data, this, status,
                tracking.strings.MSG_SET_ARC_PROPERTIES_SUCCESS,
                tracking.strings.MSG_SET_ARC_PROPERTIES_ERROR,
                null, null, null,
                tracking.strings.BUTTON_IDP_SET_ARC_PROPERTIES,
                tracking.strings.BUTTON_IDP_SET_ARC_PROPERTIES);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPImmobilizerSetParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.idpImmobilizer.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var properties = {
                NotifyOnlyWhenMoving: getValueIfCheckboxSelected('input[name=rbIDPImmobilizerNotifyOnlyWhenMoving]:checked'),
                DriverLoginCheckInterval: getValueIfCheckboxSelected('#txtIDPImmobilizerDriverLoginCheckInterval')
            };
            var data = {
                properties: properties
            };
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendSetImmobilizerPropertiesRequest', data, this, status,
                tracking.strings.MSG_SET_IMMOBILIZER_PARAMETERS_SUCCESS,
                tracking.strings.MSG_SET_IMMOBILIZER_PARAMETERS_ERROR,
                null, null, null,
                tracking.strings.SET_IMMOBILIZER_PARAMETERS,
                tracking.strings.SET_IMMOBILIZER_PARAMETERS);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPGarminSetParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.idpGarmin.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var properties = {
                FmiEnabled: getValueIfCheckboxSelected('input[name=rbIDPGarminFmiEnabled]:checked'),
                SafeModeEnabled: getValueIfCheckboxSelected('input[name=rbIDPGarminSafeModeEnabled]:checked'),
                DispatchIconText: getValueIfCheckboxSelected('#txtIDPGarminDispatchIconText'),
                DeleteGarminData: getValueIfCheckboxSelected('#ddlIDPGarminDeleteGarminData'),
                WelcomeMessage: getValueIfCheckboxSelected('#txtIDPGarminWelcomeMessage'),
                OpenTextMsgAllowed: getValueIfCheckboxSelected('input[name=rbIDPGarminOpenTextMsgAllowed]:checked'),
                TextingRequiresDriverId: getValueIfCheckboxSelected('input[name=rbIDPGarminTextingRequiresDriverId]:checked'),
                MultiDriverEnabled: getValueIfCheckboxSelected('input[name=rbIDPGarminMultiDriverEnabled]:checked'),
                DriverPwdEnabled: getValueIfCheckboxSelected('input[name=rbIDPGarminDriverPwdEnabled]:checked'),
                DriverIdCaseSensitive: getValueIfCheckboxSelected('input[name=rbIDPGarminDriverIdCaseSensitive]:checked'),
                PasswordCaseSensitive: getValueIfCheckboxSelected('input[name=rbIDPGarminPasswordCaseSensitive]:checked'),
                ReportFailedLogins: getValueIfCheckboxSelected('input[name=rbIDPGarminReportFailedLogins]:checked'),
                AutoArrivalTime: getValueIfCheckboxSelected('#txtIDPGarminAutoArrivalTime'),
                AutoArrivalDistance: getValueIfCheckboxSelected('#txtIDPGarminAutoArrivalDistance'),
                SpeedLimitMode: getValueIfCheckboxSelected('#ddlIDPGarminSpeedLimitMode'),
                SpeedLimitThreshold: getValueIfCheckboxSelected('#txtIDPGarminSpeedLimitThreshold'),
                SpeedLimitTimeOver: getValueIfCheckboxSelected('#txtIDPGarminSpeedLimitTimeOver'),
                SpeedLimitTimeUnder: getValueIfCheckboxSelected('#txtIDPGarminSpeedLimitTimeUnder'),
                SpeedLimitAlertUser: getValueIfCheckboxSelected('input[name=rbIDPGarminSpeedLimitAlertUser]:checked'),
                HosEnabled: getValueIfCheckboxSelected('input[name=rbIDPGarminHosEnabled]:checked'),
                SubmitFormAsFile: getValueIfCheckboxSelected('input[name=rbIDPGarminSubmitFormAsFile]:checked')
            };
            var data = {
                properties: properties
            };
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendSetGarminPropertiesRequest', data, this, status,
                tracking.strings.MSG_GET_GARMIN_PARAMETERS_SUCCESS,
                tracking.strings.MSG_GET_GARMIN_PARAMETERS_ERROR,
                null, null, null,
                tracking.strings.BUTTON_IDP_SET_GARMIN_PARAMETERS,
                tracking.strings.BUTTON_IDP_SET_GARMIN_PARAMETERS);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPSetAVLParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.idpAvlParameters.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            // AVL 1.7 Core properties
            var StationarySpeedThreshold = getValueIfCheckboxSelected('#txtIDPAVLStationarySpeedThld');
            var StationaryDebounceTime = getValueIfCheckboxSelected('#txtIDPAVLStationaryDebounceTime');
            var MovingDistanceThreshold = getValueIfCheckboxSelected('#txtIDPAVLMovingDistanceThld');
            var MovingDebounceTime = getValueIfCheckboxSelected('#txtIDPAVLMovingDebounceTime');
            var DefaultSpeedLimit = getValueIfCheckboxSelected('#txtIDPAVLDefaultSpeedLimit');
            var SpeedingTimeOver = getValueIfCheckboxSelected('#txtIDPAVLSpeedingTimeOver');
            var SpeedingTimeUnder = getValueIfCheckboxSelected('#txtIDPAVLSpeedingTimeUnder');
            var LoggingPositionsInterval = getValueIfCheckboxSelected('#txtIDPAVLLoggingPositionsInterval');
            var StationaryIntervalCell = getValueIfCheckboxSelected('#txtIDPAVLStationaryIntervalCell');
            var MovingIntervalCell = getValueIfCheckboxSelected('#txtIDPAVLMovingIntervalCell');
            var MovingIntervalSat = getValueIfCheckboxSelected('#txtIDPAVLMovingIntervalSat');
            var StationaryIntervalSat = getValueIfCheckboxSelected('#txtIDPAVLStationaryIntervalSat');
            var ShmReportingHour = getValueIfCheckboxSelected('#txtIDPAVLShmReportingHour');
            var OdometerDistanceIncrement = getValueIfCheckboxSelected('#txtIDPAVLOdometerDistanceIncrement');
            var Odometer = getValueIfCheckboxSelected('#txtIDPAVLOdometer');
            var TurnThreshold = getValueIfCheckboxSelected('#txtIDPAVLTurnThreshold');
            var TurnDebounceTime = getValueIfCheckboxSelected('#txtIDPAVLTurnDebounceTime');
            var DistanceCellThreshold = getValueIfCheckboxSelected('#txtIDPAVLDistanceCellThld');
            var DistanceSatThreshold = getValueIfCheckboxSelected('#txtIDPAVLDistanceSatThld');
            var MaxDrivingTime = getValueIfCheckboxSelected('#txtIDPAVLMaxDrivingTime');
            var MinRestTime = getValueIfCheckboxSelected('#txtIDPAVLMinRestTime');
            var AirBlockageTime = getValueIfCheckboxSelected('#txtIDPAVLAirBlockageTime');
            var MaxIdlingTime = getValueIfCheckboxSelected('#txtIDPAVLMaxIdlingTime');
            var IdleTimerAutoRestart = getValueIfCheckboxSelected('input[name=rbIDPAVLIdleTimerAutoRestart]:checked');
            var DefaultGeoDwellTime = getValueIfCheckboxSelected('#txtIDPAVLDefaultGeoDwellTime');
            var GeoDwellTimerAutoRestart = getValueIfCheckboxSelected('input[name=rbIDPAVLGeoDwellTimerAutoRestart]:checked');
            var ImmobilizeOnBlockage = getValueIfCheckboxSelected('#txtIDPAVLImmobilizeOnBlockage');
            var GpsJamDebounceTime = getValueIfCheckboxSelected('#txtIDPAVLGpsJamDebounceTime');
            var CellJamDebounceTime = getValueIfCheckboxSelected('#txtIDPAVLCellJamDebounceTime');
            //var LpmTrigger = getValueIfCheckboxSelected('#txtIDPAVLLpmTrigger');
            if ($j('#chkIDPAVLLpmTrigger').is(':checked')) {
                var LpmTrigger = {
                    IgnitionOff: $j('input[name=rbIDPAVLLpmTriggerIgnitionOff]:checked').val(),
                    BuiltInBattery: $j('input[name=rbIDPAVLLpmTriggerBuiltInBattery]:checked').val()
                };
            }

            var LpmEntryDelay = getValueIfCheckboxSelected('#txtIDPAVLLpmEntryDelay');
            var LpmGeoInterval = getValueIfCheckboxSelected('#txtIDPAVLLpmGeoInterval');
            var LpmModemWakeupInterval = getValueIfCheckboxSelected('#ddlIDPAVLLpmModemWakeupInterval');
            var ExitLpmOnTowingMaxTime = getValueIfCheckboxSelected('#txtIDPAVLExitLpmOnTowingMaxTime');
            var TowStartCheckInterval = getValueIfCheckboxSelected('#txtIDPAVLTowStartCheckInterval');
            var TowStartDebCount = getValueIfCheckboxSelected('#txtIDPAVLTowStartDebCount');
            var TowStopCheckInterval = getValueIfCheckboxSelected('#txtIDPAVLTowStopCheckInterval');
            var TowStopDebCount = getValueIfCheckboxSelected('#txtIDPAVLTowStopDebCount');
            var TowInterval = getValueIfCheckboxSelected('#txtIDPAVLTowInterval');
            var PositionMsgInterval = getValueIfCheckboxSelected('#txtIDPAVLPositionMsgInterval');

            // AVL 2.0+ Core Properties

            // AVL 3.0+ Properties
            var HarshBrakingThld = getValueIfCheckboxSelected('#txtIDPAVLHarshBrakingThld');
            var MinHarshBrakingTime = getValueIfCheckboxSelected('#txtIDPAVLMinHarshBrakingTime');
            var ReArmHarshBrakingTime = getValueIfCheckboxSelected('#txtIDPAVLReArmHarshBrakingTime');
            var HarshAccelThld = getValueIfCheckboxSelected('#txtIDPAVLHarshAccelThld');
            var MinHarshAccelTime = getValueIfCheckboxSelected('#txtIDPAVLMinHarshAccelTime');
            var ReArmHarshAccelTime = getValueIfCheckboxSelected('#txtIDPAVLReArmHarshAccelTime');
            var AccidentThld = getValueIfCheckboxSelected('#txtIDPAVLAccidentThld');
            var MinAccidentTime = getValueIfCheckboxSelected('#txtIDPAVLMinAccidentTime');
            var SeatbeltDebounceTime = getValueIfCheckboxSelected('#txtIDPAVLSeatbeltDebounceTime');
            var DriverIdPort = getValueIfCheckboxSelected('#ddlIDPAVLDriverIdPort');
            var DriverIdPollingInterval = getValueIfCheckboxSelected('#txtIDPAVLDriverIdPollingInterval');
            var DriverIdAutoLogoutDelay = getValueIfCheckboxSelected('#txtIDPAVLDriverIdAutoLogoutDelay');
            var AccidentAccelDataCapture = getValueIfCheckboxSelected('input[name=rbIDPAVLAccidentAccelDataCapture]:checked');
            var AccidentGpsDataCapture = getValueIfCheckboxSelected('input[name=rbIDPAVLAccidentGpsDataCapture]:checked');
            var HarshTurnThld = getValueIfCheckboxSelected('#txtIDPAVLHarshTurnThld');
            var MinHarshTurnTime = getValueIfCheckboxSelected('#txtIDPAVLMinHarshTurnTime');
            var ReArmHarshTurnTime = getValueIfCheckboxSelected('#txtIDPAVLReArmHarshTurnTime');
            var avlProperties = {
                StationarySpeedThreshold: StationarySpeedThreshold,
                StationaryDebounceTime: StationaryDebounceTime,
                MovingDistanceThreshold: MovingDistanceThreshold,
                MovingDebounceTime: MovingDebounceTime,
                DefaultSpeedLimit: DefaultSpeedLimit,
                SpeedingTimeOver: SpeedingTimeOver,
                SpeedingTimeUnder: SpeedingTimeUnder,
                LoggingPositionsInterval: LoggingPositionsInterval,
                StationaryIntervalCell: StationaryIntervalCell,
                MovingIntervalCell: MovingIntervalCell,
                MovingIntervalSat: MovingIntervalSat,
                StationaryIntervalSat: StationaryIntervalSat,
                ShmReportingHour: ShmReportingHour,
                OdometerDistanceIncrement: OdometerDistanceIncrement,
                Odometer: Odometer,
                TurnThreshold: TurnThreshold,
                TurnDebounceTime: TurnDebounceTime,
                DistanceCellThreshold: DistanceCellThreshold,
                DistanceSatThreshold: DistanceSatThreshold,
                MaxDrivingTime: MaxDrivingTime,
                MinRestTime: MinRestTime,
                AirBlockageTime: AirBlockageTime,
                MaxIdlingTime: MaxIdlingTime,
                IdleTimerAutoRestart: (IdleTimerAutoRestart === undefined) ? null : IdleTimerAutoRestart,
                DefaultGeoDwellTime: DefaultGeoDwellTime,
                GeoDwellTimerAutoRestart: (GeoDwellTimerAutoRestart === undefined) ? null : GeoDwellTimerAutoRestart,
                ImmobilizeOnBlockage: ImmobilizeOnBlockage,
                GpsJamDebounceTime: GpsJamDebounceTime,
                CellJamDebounceTime: CellJamDebounceTime,
                LpmTrigger: LpmTrigger,
                LpmEntryDelay: LpmEntryDelay,
                LpmGeoInterval: LpmGeoInterval,
                LpmModemWakeupInterval: LpmModemWakeupInterval,
                ExitLpmOnTowingMaxTime: ExitLpmOnTowingMaxTime,
                TowStartCheckInterval: TowStartCheckInterval,
                TowStartDebCount: TowStartDebCount,
                TowStopCheckInterval: TowStopCheckInterval,
                TowStopDebCount: TowStopDebCount,
                TowInterval: TowInterval,
                DigStatesDefBitmap: null
            };

            var avl2Properties = jQuery.extend({}, avlProperties);
            avl2Properties.SmReportingHour = avlProperties.ShmReportingHour;
            if ($j('#chkIDPAVLOptionalFieldsInMsgs').is(':checked')) {
                var OptionalFieldsDef = {
                    Odometer: $j('input[name=rbIDPAVLOptionalFieldsInMsgsOdometer]:checked').val(),
                    Sensor1: $j('input[name=rbIDPAVLOptionalFieldsInMsgsSensor1]:checked').val(),
                    Sensor2: $j('input[name=rbIDPAVLOptionalFieldsInMsgsSensor2]:checked').val(),
                    Sensor3: $j('input[name=rbIDPAVLOptionalFieldsInMsgsSensor3]:checked').val(),
                    Sensor4: $j('input[name=rbIDPAVLOptionalFieldsInMsgsSensor4]:checked').val(),
                    DriverId: $j('input[name=rbIDPAVLOptionalFieldsInMsgsDriverId]:checked').val(),
                    DigitalPorts: $j('input[name=rbIDPAVLOptionalFieldsInMsgsDigitalPorts]:checked').val(),
                    AvlStates: $j('input[name=rbIDPAVLOptionalFieldsInMsgsAvlStates]:checked').val()
                };
            }
            avl2Properties.OptionalFieldsDef = OptionalFieldsDef;
            avl2Properties.PositionMsgInterval = PositionMsgInterval;
            delete avl2Properties.ShmReportingHour;
            delete avl2Properties.Odometer;
            delete avl2Properties.GeoDwellTimerAutoRestart;
            delete avl2Properties.IdleTimerAutoRestart;
            delete avl2Properties.MovingDistanceThreshold;
            delete avl2Properties.ImmobilizeOnBlockage;
            delete avl2Properties.ExitLpmOnTowingMaxTime;

            var avl3Properties = jQuery.extend({}, avl2Properties);
            avl3Properties.HarshBrakingThld = HarshBrakingThld;
            avl3Properties.MinHarshBrakingTime = MinHarshBrakingTime;
            avl3Properties.ReArmHarshBrakingTime = ReArmHarshBrakingTime;
            avl3Properties.HarshAccelThld = HarshAccelThld;
            avl3Properties.MinHarshAccelTime = MinHarshAccelTime;
            avl3Properties.ReArmHarshAccelTime = ReArmHarshAccelTime;
            avl3Properties.AccidentThld = AccidentThld;
            avl3Properties.MinAccidentTime = MinAccidentTime;
            avl3Properties.SeatbeltDebounceTime = SeatbeltDebounceTime;
            avl3Properties.DriverIdPort = DriverIdPort;
            avl3Properties.DriverIdPollingInterval = DriverIdPollingInterval;
            avl3Properties.DriverIdAutoLogoutDelay = DriverIdAutoLogoutDelay;
            avl3Properties.AccidentAccelDataCapture = AccidentAccelDataCapture;
            avl3Properties.AccidentGpsDataCapture = AccidentGpsDataCapture;
            avl3Properties.HarshTurnThld = HarshTurnThld;
            avl3Properties.MinHarshTurnTime = MinHarshTurnTime;
            avl3Properties.ReArmHarshTurnTime = ReArmHarshTurnTime;

            var data = {
                avlProperties: avlProperties,
                avl2Properties: avl2Properties,
                avl3Properties: avl3Properties,
            };
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendSetAVLPropertiesRequest', data, this, status,
                tracking.strings.MSG_IDP_PARAMETERS_SUCCESS,
                tracking.strings.MSG_IDP_PARAMETERS_ERROR,
                null, null, null,
                tracking.strings.SET_AVL_PARAMETERS,
                tracking.strings.SET_AVL_PARAMETERS);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPSetAVLIOParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.idpIoParameters.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            // I/O Parameters
            if ($j('#chkIDPAVLDigStatesDefBitmap').is(':checked')) {
                var DigStatesDef = {
                    IgnitionOn: $j('input[name=rbIDPAVLDigStatesDefIgnitionOn]:checked').val(),
                    SeatbeltBuckled: $j('input[name=rbIDPAVLDigStatesDefSeatbeltOff]:checked').val(),
                    SM1Active: $j('input[name=rbIDPAVLDigStatesDefSM1Active]:checked').val(),
                    SM2Active: $j('input[name=rbIDPAVLDigStatesDefSM2Active]:checked').val(),
                    SM3Active: $j('input[name=rbIDPAVLDigStatesDefSM3Active]:checked').val(),
                    SM4Active: $j('input[name=rbIDPAVLDigStatesDefSM4Active]:checked').val()
                };
            }
            var FuncDigOut1 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigOut1');
            var FuncDigOut2 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigOut2');
            var FuncDigOut3 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigOut3');
            var FuncDigOut4 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigOut4');
            var FuncDigOut5 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigOut5');
            var FuncDigOut6 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigOut6');

            var FuncDigInp1 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigInp1');
            var FuncDigInp2 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigInp2');
            var FuncDigInp3 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigInp3');
            var FuncDigInp4 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigInp4');
            var FuncDigInp5 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigInp5');
            var FuncDigInp6 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigInp6');
            var FuncDigInp7 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigInp7');
            var FuncDigInp8 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigInp8');
            var FuncDigInp9 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigInp9');
            var FuncDigInp10 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigInp10');
            var FuncDigInp11 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigInp11');
            var FuncDigInp12 = getValueIfCheckboxSelected('#ddlIDPAVLFuncDigInp12');
            var SensorReportingInterval = getValueIfCheckboxSelected('#txtIDPAVLSensorReportingInterval');
            var Sensor1SourceSIN = getValueIfCheckboxSelected('#txtIDPAVLSensor1SourceSIN');
            var Sensor1SourcePIN = getValueIfCheckboxSelected('#txtIDPAVLSensor1SourcePIN');
            var Sensor1NormalSampleInterval = getValueIfCheckboxSelected('#txtIDPAVLSensor1NormalSampleInterval');
            var Sensor1LpmSampleInterval = getValueIfCheckboxSelected('#txtIDPAVLSensor1LpmSampleInterval');
            var Sensor1MaxReportInterval = getValueIfCheckboxSelected('#txtIDPAVLSensor1MaxReportInterval');
            var Sensor1ChangeThld = getValueIfCheckboxSelected('#txtIDPAVLSensor1ChangeThld');
            var Sensor1MinThld = getValueIfCheckboxSelected('#txtIDPAVLSensor1MinThld');
            var Sensor1MaxThld = getValueIfCheckboxSelected('#txtIDPAVLSensor1MaxThld');
            var Sensor2SourceSIN = getValueIfCheckboxSelected('#txtIDPAVLSensor2SourceSIN');
            var Sensor2SourcePIN = getValueIfCheckboxSelected('#txtIDPAVLSensor2SourcePIN');
            var Sensor2NormalSampleInterval = getValueIfCheckboxSelected('#txtIDPAVLSensor2NormalSampleInterval');
            var Sensor2LpmSampleInterval = getValueIfCheckboxSelected('#txtIDPAVLSensor2LpmSampleInterval');
            var Sensor2MaxReportInterval = getValueIfCheckboxSelected('#txtIDPAVLSensor2MaxReportInterval');
            var Sensor2ChangeThld = getValueIfCheckboxSelected('#txtIDPAVLSensor2ChangeThld');
            var Sensor2MinThld = getValueIfCheckboxSelected('#txtIDPAVLSensor2MinThld');
            var Sensor2MaxThld = getValueIfCheckboxSelected('#txtIDPAVLSensor2MaxThld');
            var Sensor3SourceSIN = getValueIfCheckboxSelected('#txtIDPAVLSensor3SourceSIN');
            var Sensor3SourcePIN = getValueIfCheckboxSelected('#txtIDPAVLSensor3SourcePIN');
            var Sensor3NormalSampleInterval = getValueIfCheckboxSelected('#txtIDPAVLSensor3NormalSampleInterval');
            var Sensor3LpmSampleInterval = getValueIfCheckboxSelected('#txtIDPAVLSensor3LpmSampleInterval');
            var Sensor3MaxReportInterval = getValueIfCheckboxSelected('#txtIDPAVLSensor3MaxReportInterval');
            var Sensor3ChangeThld = getValueIfCheckboxSelected('#txtIDPAVLSensor3ChangeThld');
            var Sensor3MinThld = getValueIfCheckboxSelected('#txtIDPAVLSensor3MinThld');
            var Sensor3MaxThld = getValueIfCheckboxSelected('#txtIDPAVLSensor3MaxThld');
            var Sensor4SourceSIN = getValueIfCheckboxSelected('#txtIDPAVLSensor4SourceSIN');
            var Sensor4SourcePIN = getValueIfCheckboxSelected('#txtIDPAVLSensor4SourcePIN');
            var Sensor4NormalSampleInterval = getValueIfCheckboxSelected('#txtIDPAVLSensor4NormalSampleInterval');
            var Sensor4LpmSampleInterval = getValueIfCheckboxSelected('#txtIDPAVLSensor4LpmSampleInterval');
            var Sensor4MaxReportInterval = getValueIfCheckboxSelected('#txtIDPAVLSensor4MaxReportInterval');
            var Sensor4ChangeThld = getValueIfCheckboxSelected('#txtIDPAVLSensor4ChangeThld');
            var Sensor4MinThld = getValueIfCheckboxSelected('#txtIDPAVLSensor4MinThld');
            var Sensor4MaxThld = getValueIfCheckboxSelected('#txtIDPAVLSensor4MaxThld');

            var ioProperties = {
                DigStatesDef: DigStatesDef,
                FuncDigInp1: FuncDigInp1,
                FuncDigInp2: FuncDigInp2,
                FuncDigInp3: FuncDigInp3,
                FuncDigInp4: FuncDigInp4,
                FuncDigInp5: FuncDigInp5,
                FuncDigInp6: FuncDigInp6,
                FuncDigInp7: FuncDigInp7,
                FuncDigInp8: FuncDigInp8,
                FuncDigInp9: FuncDigInp9,
                FuncDigInp10: FuncDigInp10,
                FuncDigInp11: FuncDigInp11,
                FuncDigInp12: FuncDigInp12,
                FuncDigOut1: FuncDigOut1,
                FuncDigOut2: FuncDigOut2,
                FuncDigOut3: FuncDigOut3,
                FuncDigOut4: FuncDigOut4,
                FuncDigOut5: FuncDigOut5,
                FuncDigOut6: FuncDigOut6,
                SensorReportingInterval: SensorReportingInterval,
                Sensor1SourceSIN: Sensor1SourceSIN,
                Sensor1SourcePIN: Sensor1SourcePIN,
                Sensor1NormalSampleInterval: Sensor1NormalSampleInterval,
                Sensor1LpmSampleInterval: Sensor1LpmSampleInterval,
                Sensor1MaxReportInterval: Sensor1MaxReportInterval,
                Sensor1ChangeThld: Sensor1ChangeThld,
                Sensor1MinThld: Sensor1MinThld,
                Sensor1MaxThld: Sensor1MaxThld,
                Sensor2SourceSIN: Sensor2SourceSIN,
                Sensor2SourcePIN: Sensor2SourcePIN,
                Sensor2NormalSampleInterval: Sensor2NormalSampleInterval,
                Sensor2LpmSampleInterval: Sensor2LpmSampleInterval,
                Sensor2MaxReportInterval: Sensor2MaxReportInterval,
                Sensor2ChangeThld: Sensor2ChangeThld,
                Sensor2MinThld: Sensor2MinThld,
                Sensor2MaxThld: Sensor2MaxThld,
                Sensor3SourceSIN: Sensor3SourceSIN,
                Sensor3SourcePIN: Sensor3SourcePIN,
                Sensor3NormalSampleInterval: Sensor3NormalSampleInterval,
                Sensor3LpmSampleInterval: Sensor3LpmSampleInterval,
                Sensor3MaxReportInterval: Sensor3MaxReportInterval,
                Sensor3ChangeThld: Sensor3ChangeThld,
                Sensor3MinThld: Sensor3MinThld,
                Sensor3MaxThld: Sensor3MaxThld,
                Sensor4SourceSIN: Sensor4SourceSIN,
                Sensor4SourcePIN: Sensor4SourcePIN,
                Sensor4NormalSampleInterval: Sensor4NormalSampleInterval,
                Sensor4LpmSampleInterval: Sensor4LpmSampleInterval,
                Sensor4MaxReportInterval: Sensor4MaxReportInterval,
                Sensor4ChangeThld: Sensor4ChangeThld,
                Sensor4MinThld: Sensor4MinThld,
                Sensor4MaxThld: Sensor4MaxThld,
                DigStatesDefBitmap: null
            };

            var data = {
                properties: ioProperties
            };
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendSetIOPropertiesRequest', data, this, status,
                tracking.strings.MSG_IDP_PARAMETERS_SUCCESS,
                tracking.strings.MSG_IDP_PARAMETERS_ERROR,
                null, null, null,
                tracking.strings.SET_IO_PARAMETERS,
                tracking.strings.SET_IO_PARAMETERS);
        });

        $(tracking.data.domNodes.dialogs.idpSendCommand).on('click', '#btnIDPRequestLocation', function (e) {
            e.preventDefault();

            var data = {};
            var status = document.getElementById('idp-query-status');
            handleAjaxFormSubmissionWithGatewaySelection('IDPSendLocationRequest', data, this, status,
                tracking.strings.MSG_LOCATION_REQUEST_SUCCESS,
                tracking.strings.MSG_LOCATION_REQUEST_ERROR,
                null, null, null,
                tracking.strings.BUTTON_REQUEST_LOCATION,
                tracking.strings.BUTTON_REQUEST_LOCATION);
        });

        tracking.data.domNodes.dialogs.extreme = document.getElementById('extreme-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.extreme, [closeButton]);
        $(tracking.data.domNodes.dialogs.extreme).on('click', '#btn9575LocationRequest', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.extreme).data('assetId');
            var data = {
                assetId: id,
                pollType: null
            };

            var status = document.getElementById('extreme-query-status');
            handleAjaxFormSubmission('SendLocationRequest', data, this, status, tracking.strings.MSG_LOCATION_REQUEST_SUCCESS, tracking.strings.MSG_LOCATION_REQUEST_ERROR);
        });
        $(tracking.data.domNodes.dialogs.extreme).on('click', '#btn9575DiagnosticRequest', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.extreme).data('assetId');
            var data = {
                assetId: id
            };

            var status = document.getElementById('extreme-query-status');
            handleAjaxFormSubmission('SendDiagnosticsRequest', data, this, status, tracking.strings.MSG_DIAGNOSTIC_REQUEST_SUCCESS, tracking.strings.MSG_DIAGNOSTIC_REQUEST_ERROR);
        });
        $(tracking.data.domNodes.dialogs.extreme).on('click', '#btn9575EmergencyCancel', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.extreme).data('assetId');
            var data = {
                assetId: id
            };

            var status = document.getElementById('extreme-query-status');
            handleAjaxFormSubmission('EmergencyCancel', data, this, status, tracking.strings.MSG_CANCEL_EMERGENCY_SUCCESS, tracking.strings.MSG_CANCEL_EMERGENCY_ERROR);
        });
        $(tracking.data.domNodes.dialogs.extreme).on('click', '#btn9575SetEmergencyRecipient', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.extremeEmergencyRecipient.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.extreme).data('assetId');
            var recipient = $j('#txt9575EmergencyRecipient').val();
            var data = {
                assetId: id,
                recipient: recipient
            };

            var status = document.getElementById('extreme-query-status');
            handleAjaxFormSubmission('SetEmergencyRecipient', data, this, status, tracking.strings.MSG_SET_EMERGENCY_RECIPIENT_SUCCESS, tracking.strings.MSG_SET_EMERGENCY_RECIPIENT_ERROR);
        });
        $(tracking.data.domNodes.dialogs.extreme).on('click', '#btn9575SetEmergencyDestination', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.extremeEmergencyDestination.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.extreme).data('assetId');
            var destination = $j('#ddl9575EmergencyDestination').val();
            var data = {
                assetId: id,
                destination: destination
            };

            var status = document.getElementById('extreme-query-status');
            handleAjaxFormSubmission('SetEmergencyDestination', data, this, status, tracking.strings.MSG_SET_EMERGENCY_DESTINATION_SUCCESS, tracking.strings.MSG_SET_EMERGENCY_DESTINATION_ERROR);
        });
        $(tracking.data.domNodes.dialogs.extreme).on('click', '#btn9575SetInterval', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.extremeInterval.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.extreme).data('assetId');
            var interval = $j('#ddl9575ScheduleInterval').val();
            var data = {
                assetId: id,
                interval: interval
            };

            var status = document.getElementById('extreme-query-status');
            handleAjaxFormSubmission('SendReportingIntervalRequest', data, this, status, tracking.strings.MSG_INTERVAL_SUCCESS, tracking.strings.MSG_INTERVAL_ERROR);
        });

        tracking.data.domNodes.dialogs.skywaveM2M = document.getElementById('skywave-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.skywaveM2M, [closeButton]);
        $(tracking.data.domNodes.dialogs.skywaveM2M).on('click', '#btnSkywaveRequestLocation', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.skywaveM2MLocation.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.skywaveM2M).data('assetId');
            var pollType = $j('#ddlSkywavePollType').val();
            var data = {
                assetId: id,
                pollType: pollType
            };

            var status = document.getElementById('skywave-query-status');
            handleAjaxFormSubmission('SendLocationRequest', data, this, status, tracking.strings.MSG_LOCATION_REQUEST_SUCCESS, tracking.strings.MSG_LOCATION_REQUEST_ERROR);
        });
        $(tracking.data.domNodes.dialogs.skywaveM2M).on('click', '#btnSkywaveSetParameters', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.skywaveM2MParameters.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.skywaveM2M).data('assetId');
            var stationaryInterval = $j('#txtSkywaveStationaryInterval').val();
            var movingInterval = $j('#txtSkywaveMovingInterval').val();
            var stopInterval = $j('#txtSkywaveStopInterval').val();
            var stopSpeed = $j('#txtSkywaveStopSpeed').val();
            var data = {
                assetId: id,
                movingInterval: movingInterval,
                stationaryInterval: stationaryInterval,
                stopSpeedThreshold: stopSpeed,
                stopTimer: stopInterval
            };

            var status = document.getElementById('skywave-query-status');
            handleAjaxFormSubmission('SetSkywaveParameters', data, this, status, tracking.strings.MSG_PARAMETERS_SUCCESS, tracking.strings.MSG_PARAMETERS_ERROR);
        });

        var fbbButtons = [{
            buttonType: 'primary',
            text: tracking.strings.BUTTON_SET_INTERVAL,
            click: function () {
                var isFormValid = $(tracking.data.validation.fbbInterval.currentForm).valid();
                if (!isFormValid) {
                    return;
                }
                var id = $(tracking.data.domNodes.dialogs.fbb).data('assetId');
                var interval = $j('#ddlFBBScheduleInterval').val();
                var data = {
                    assetId: id,
                    interval: interval
                };

                var status = document.getElementById('fbb-query-status');
                handleAjaxFormSubmission('SendReportingIntervalRequest', data, this, status, tracking.strings.MSG_DPLUS_INTERVAL_SUCCESS, tracking.strings.MSG_INTERVAL_ERROR);
            }
        },
        closeButton];
        tracking.data.domNodes.dialogs.fbb = document.getElementById('fbb-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.fbb, fbbButtons);

        tracking.data.domNodes.dialogs.dPlus = document.getElementById('dplus-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.dPlus, [closeButton]);
        $j(tracking.data.domNodes.dialogs.dPlus).on('click', '#btnDPlusSendRequest', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.dPlusQuery.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $j(tracking.data.domNodes.dialogs.dPlus).data('assetId');
            var ocean = $j('#ddlDPlusRegion').val();
            var report = $j('#ddlDPlusReportType').val();
            var data = {
                assetId: id,
                ocean: ocean,
                reportId: report
            };

            var status = document.getElementById('dplus-query-status');
            handleAjaxFormSubmission('SendDPlusReportRequest', data, this, status, tracking.strings.MSG_DPLUS_REPORT_SUCCESS, tracking.strings.MSG_DPLUS_REPORT_ERROR);
        });
        $j(tracking.data.domNodes.dialogs.dPlus).on('click', '#btnDPlusSubmitInterval', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.dPlusInterval.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $j(tracking.data.domNodes.dialogs.dPlus).data('assetId');
            var ocean = $j('#ddlDPlusIntervalRegion').val();
            var reportType = $j('input[name=rblDPlusOption]:checked', tracking.data.domNodes.dialogs.dPlus).val();
            var reportId = $j('#ddlDPlusScheduleReportType').val();
            var interval = $j('#ddlDPlusScheduleInterval').val();
            var data = {
                assetId: id,
                ocean: ocean,
                reportType: reportType,
                reportId: reportId,
                interval: interval
            };

            var status = document.getElementById('dplus-query-status');
            handleAjaxFormSubmission('SendDPlusIntervalRequest', data, this, status, tracking.strings.MSG_DPLUS_INTERVAL_SUCCESS, tracking.strings.MSG_DPLUS_INTERVAL_ERROR);
        });

        tracking.data.domNodes.dialogs.inmarsatC = document.getElementById('inmarsatc-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.inmarsatC, [closeButton]);
        $(tracking.data.domNodes.dialogs.inmarsatC).on('click', 'button.dnid-download', function (e) {
            e.preventDefault();

            var id = $(tracking.data.domNodes.dialogs.inmarsatC).data('assetId');
            var ocean = $(this).data('region');
            var data = {
                assetId: id,
                oceanRegion: ocean
            };

            var status = document.getElementById('inmarsatc-query-status');
            var asset = findAssetById(id);
            handleAjaxFormSubmission('InmarsatCDownloadDNID', data, this, status, tracking.strings.DNID_DOWNLOAD_SUCCESS, tracking.strings.DNID_DOWNLOAD_ERROR, function() {
                requestInmarsatCInformation(asset);
            });
        });
        $(tracking.data.domNodes.dialogs.inmarsatC).on('click', '#InmarsatCDeleteDnid', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.inmarsatCDelete.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.inmarsatC).data('assetId');
            var ocean = $('#ddlInmarsatCDeleteDnidRegion').val();
            var data = {
                assetId: id,
                oceanRegion: ocean
            };

            var status = document.getElementById('inmarsatc-query-status');
            var asset = findAssetById(id);
            handleAjaxFormSubmission('InmarsatCDeleteDNID', data, this, status, tracking.strings.DNID_DELETE_SUCCESS, tracking.strings.DNID_DELETE_ERROR, function() {
                requestInmarsatCInformation(asset);
            });
        });
        $(tracking.data.domNodes.dialogs.inmarsatC).on('click', '#InmarsatCRequestLocation', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.inmarsatC.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $(tracking.data.domNodes.dialogs.inmarsatC).data('assetId');
            var ocean = $('#ddlInmarsatCRequestLocationRegion').val();
            var data = {
                assetId: id,
                oceanRegion: ocean
            };

            var status = document.getElementById('inmarsatc-query-status');
            handleAjaxFormSubmission('InmarsatCSendPollCommand', data, this, status, tracking.strings.MSG_LOCATION_REQUEST_SUCCESS, tracking.strings.MSG_LOCATION_REQUEST_ERROR);
        });

        $(tracking.data.domNodes.dialogs.inmarsatC).on('click', '#InmarsatCScheduleReports', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.inmarsatCSchedule.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var id = $j(tracking.data.domNodes.dialogs.inmarsatC).data('assetId');
            var commandType = document.getElementById('ddlInmarsatCPollType').value;
            var startHour = document.getElementById('ddlInmarsatCStartTimeHour').value;
            var startMinute = document.getElementById('ddlInmarsatCStartTimeMinute').value;
            var reportsPerDay = document.getElementById('txtInmarsatCReportsPerDay').value;
            var ocean = document.getElementById('ddlInmarsatCPollRegion').value;
            var data = {
                assetId: id,
                commandType: commandType,
                hour: startHour,
                minute: startMinute,
                reportsPerDay: reportsPerDay,
                oceanRegion: ocean
            };

            var status = document.getElementById('inmarsatc-query-status');
            handleAjaxFormSubmission('InmarsatCSendScheduledPollCommand', data, this, status, tracking.strings.MSG_COMMAND_SUCCESS, tracking.strings.MSG_COMMAND_ERROR);
        });
        $(tracking.data.domNodes.dialogs.inmarsatC).on('change', '#ddlInmarsatCPollType', function (e) {
            e.preventDefault();
            var options = document.getElementById('InmarsatCScheduleOptions');
            if (parseInt(this.value) === 4) {
                options.classList.add('is-visible');
            } else {
                options.classList.remove('is-visible');
            }
        });

        tracking.data.domNodes.dialogs.edge = document.getElementById('edge-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.edge, [closeButton]);
        $(tracking.data.domNodes.dialogs.edge).on('click', '#EdgeRequestLocation', function (e) {
            e.preventDefault();

            var isFormValid = $(tracking.data.validation.iridiumEdge.currentForm).valid();
            if (!isFormValid) {
                return;
            }

            var assetId = $j(tracking.data.domNodes.dialogs.edge).data('assetId');
            var status = document.getElementById('edge-query-status');

            var btn = this;
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: assetId,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries,
                    groupIds: groupIds,
                    assetIds: assetIds
                };

                $.when(handleAjaxFormSubmission('GSatMicroSendGetLocationRequest', data, btn, status, tracking.strings.MSG_LOCATION_REQUEST_SUCCESS, tracking.strings.MSG_LOCATION_REQUEST_ERROR))
                    .done(function () {
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    });
            }

            var asset = findAssetById(assetId);
            var satelliteOnly = false;
            if (asset != null) {
                if (_.indexOf(tracking.devices.GSATMICRO_GSM, asset.DeviceId) === -1) {
                    satelliteOnly = true;
                }
            }

            openActionDialog(tracking.strings.BUTTON_REQUEST_LOCATION, tracking.strings.BUTTON_REQUEST_LOCATION, callback, assetId, null, satelliteOnly, tracking.devices.IRIDIUM_EDGE);
        });

        // add/edit group dialog
        var addGroupButtons = [{
            buttonType: 'primary',
            text: tracking.strings.CREATE_GROUP,
            id: 'AssetGroupEditButton',
            click: function () {
                var isFormValid = $j(tracking.data.validation.addGroup.currentForm).valid();
                if (!isFormValid) {
                    tracking.data.validation.addGroup.focusInvalid();
                    return;
                }
                var btn = this;
                btn.disabled = true;
                var status = document.getElementById('add-group-status');
                var dialog = $j(tracking.data.domNodes.dialogs.editGroup);
                    var id = dialog.data('groupId');
                    var name = $j('#txtGroupName').val();
                    var color = $j('#txtColor').val();
                    var parentId = $j('#ddlGroupParent').val();
                    var destinationId = $j('#txtGroupDestinationId').val();
                    var isChatEnabled = $j('#EditAssetGroupAllowChat').prop('checked');
                    var isLocationSharingEnabled = $j('#EditAssetGroupAllowLocationSharing').prop('checked');
                    var assetIds = new Array();
                    $j('input[name=EditAssetGroupAssetIds]:checked', dialog).each(function (index, elem) {
                        assetIds.push(parseInt($j(this).val()));
                    });
                    var userIds = new Array();
                    $j('input[name=EditAssetGroupUserIds]:checked', dialog).each(function (index, elem) {
                        userIds.push($j(this).val());
                    });

                    var data = {
                        Id: id,
                        Name: name,
                        Color: color,
                        ParentId: parentId,
                        DestinationId: destinationId,
                        AssetIds: assetIds,
                        UserIds: userIds,
                        IsChatEnabled: isChatEnabled,
                        IsLocationSharingEnabled: isLocationSharingEnabled
                    };
                    var url = '/services/GPSService.asmx/AddAssetGroup';
                    if (tracking.state.isEditingAssetGroup) {
                        url = '/services/GPSService.asmx/UpdateAssetGroup';
                    } else {
                        delete data.Id;
                }
                toggleLoadingMessage(true, url);
                    $j.ajax({
                        type: 'POST',
                        url: wrapUrl(url),
                        data: '{ assetGroup: ' + JSON.stringify(data) + '}',
                        contentType: 'application/json; charset=utf-8',
                        dataType: 'json',
                        success: function (msg) {
                            toggleLoadingMessage(false, url);
                            btn.disabled = false;
                            var result = msg.d;
                            if (result) {
                                if (result.Success == true) {
                                    if (tracking.state.isEditingAssetGroup) {
                                        formShowSuccessMessage(status, tracking.strings.MSG_EDIT_GROUP_SUCCESS);

                                        var group = findGroupById(id);
                                        group.Name = name;
                                        group.Color = color;
                                        group.DestinationId = destinationId;
                                        group.IsChatEnabled = isChatEnabled;
                                        group.IsLocationSharingEnabled = isLocationSharingEnabled;
                                        updateAssetGroup(group);

                                        // update assets in group
                                        for (var i = 0; i < tracking.data.assets.length; i++) {
                                            var asset = tracking.data.assets[i];
                                            var assetGroupIndex = $j.inArray(group.Id, asset.GroupIds);
                                            var wasInGroup = (assetGroupIndex != -1);
                                            var isNowInGroup = ($j.inArray(asset.Id, assetIds) != -1);
                                            if (wasInGroup && !isNowInGroup) {
                                                asset.GroupIds.splice(assetGroupIndex, 1);
                                                removeAssetFromGroup(asset, group.Id);
                                            } else if (!wasInGroup && isNowInGroup) {
                                                asset.GroupIds.push(group.Id);
                                                addAssetToGroup(asset, group.Id);
                                                toggleItemSorting('assets', tracking.user.displayPreferences.sortMode.assets === tracking.sortModes.CUSTOM);
                                            }
                                        }

                                        var previousUserIds = findAssetGroupUsersByAssetGroupId(id);
                                        if (previousUserIds != null) {
                                            if (previousUserIds.toString() != userIds.toString()) {
                                                for (var i = 0; i < previousUserIds.length; i++) {
                                                    value = previousUserIds[i];
                                                    if ($j.inArray(value, userIds) == -1) {
                                                        removeUserFromAssetGroup(value, id);
                                                        i--;
                                                    }
                                                }

                                                $j.each(userIds, function (index, value) {
                                                    if ($j.inArray(value, previousUserIds) == -1) {
                                                        addUserToAssetGroup(value, id);
                                                    }
                                                });
                                            }
                                        }
                                    } else {
                                        formShowSuccessMessage(status, tracking.strings.MSG_ADD_GROUP_SUCCESS);
                                        // reset the form to add another
                                        tracking.data.validation.addGroup.resetForm();
                                        tracking.data.validation.addGroup.currentForm.reset();

                                        result.Group.Groups = [];
                                        result.Group.GroupIds = [];
                                        result.Group.ColorSorted = convertHexToSortable(result.Group.Color);
                                        tracking.data.groups.push(result.Group);
                                        tracking.data.groupsById = _.keyBy(tracking.data.groups, 'Id');
                                        addAssetGroup(result.Group, assetIds);

                                        if (tracking.data.assetGroupUsers != null) {
                                            tracking.data.assetGroupUsers.push({ AssetGroupId: result.Group.Id, UserIds: [] });
                                        }
                                        $j.each(userIds, function (index, value) {
                                            addUserToAssetGroup(value, result.Group.Id);
                                        });
                                    }
                                    populateGroupList();
                                    indexAssetGroupsForSearch();
                                } else {
                                    if (tracking.state.isEditingAssetGroup) {
                                        formShowErrorMessage(status, tracking.strings.MSG_EDIT_GROUP_ERROR);
                                    } else {
                                        formShowErrorMessage(status, tracking.strings.MSG_ADD_GROUP_ERROR);
                                    }
                                    if ((result.ErrorMessage != null) && (result.ErrorMessage != '')) {
                                        formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                                    }
                                }
                            }
                        },
                        error: function (xhr, status, error) {
                            btn.disabled = false;
                            toggleLoadingMessage(false, url);
                            if (tracking.state.isEditingAssetGroup) {
                                utility.handleWebServiceError(tracking.strings.MSG_EDIT_GROUP_ERROR);
                            } else {
                                utility.handleWebServiceError(tracking.strings.MSG_ADD_GROUP_ERROR);
                            }
                        }
                    });
                }
            },
            {
                buttonType: 'secondary',
                icons: { primary: 'ui-icon-close' },
                text: tracking.strings.CANCEL,
                click: function () {
                    if (!tracking.state.isEditingAssetGroup) {
                        closeSecondaryPanel();
                    } else {
                        document.getElementById('panel-dialog-back').click();
                    }
                }
        }];

        tracking.data.domNodes.dialogs.editGroup = document.getElementById('add-group-dialog');
        loadDialogButtons(tracking.data.domNodes.dialogs.editGroup, addGroupButtons);

        $(document).on('change', '#panel-secondary-item-options', function (e) {
            var id = this.value;
            var selection = document.getElementById('panel-secondary-header');
            var callbackItem = null;
            var itemType = selection.getAttribute('data-group-for');
            switch (itemType) {
                case 'groups':
                    callbackItem = findGroupById(id);
                    break;
                case 'assets':
                    callbackItem = findAssetById(id);
                    break;
                case 'places':
                    callbackItem = findPlaceById(id);
                    break;
                case 'fences':
                    callbackItem = findFenceById(id);
                    break;
                case 'trips':
                    callbackItem = findTripById(id);
                    break;
                case 'journeys':
                    callbackItem = findJourneyById(id);
                    break;
                case 'shared-views':
                    callbackItem = findSharedViewById(id);
                    break;
                default:
                    console.warn('Unknown item type for navigation:' + itemType);
                    break;
            }
            var callback = tracking.data.domNodes.panels.secondary.nextItemCallback;
            if (callback !== undefined && callback !== null) {
                callback(callbackItem);
            } else {
                console.warn('No asset selection callback defined.');
            }
        });

        $(document).on('click', 'a.item-prev,button.item-prev', function (e) {
            e.preventDefault();
            if ($(this).hasClass('disabled')) {
                return;
            }
            var selection = document.getElementById('panel-secondary-header');
            var option = selection.querySelector('select').querySelector('option:checked');
            if (option === null) {
                return;
            }
            var id = null;
            // get previous option not disabled
            while (option.previousElementSibling !== null) {
                option = option.previousElementSibling;
                if (!option.disabled) {
                    id = option.value;
                    break;
                }
            }

            if (id === null) {
                return;
            }

            var callbackItem = null;
            var itemType = selection.getAttribute('data-group-for');
            switch (itemType) {
                case 'groups':
                    callbackItem = findGroupById(id);
                    break;
                case 'assets':
                    callbackItem = findAssetById(id);
                    break;
                case 'places':
                    callbackItem = findPlaceById(id);
                    break;
                case 'fences':
                    callbackItem = findFenceById(id);
                    break;
                case 'trips':
                    callbackItem = findTripById(id);
                    break;
                case 'journeys':
                    callbackItem = findJourneyById(id);
                    break;
                case 'shared-views':
                    callbackItem = findSharedViewById(id);
                    break;
                default:
                    console.warn('Unknown item type for navigation:' + itemType);
                    break;
            }
            var callback = tracking.data.domNodes.panels.secondary.nextItemCallback;
            if (callback !== undefined && callback !== null) {
                callback(callbackItem);
            } else {
                console.warn('No asset selection callback defined.');
            }
        });

        $(document).on('click', 'a.item-next,button.item-next', function (e) {
            e.preventDefault();
            if ($(this).hasClass('disabled')) {
                return;
            }
            var selection = document.getElementById('panel-secondary-header');
            var option = selection.querySelector('select').querySelector('option:checked');
            if (option === null) {
                return;
            }
            var id = null;

            // get next option not disabled
            while (option.nextElementSibling !== null) {
                option = option.nextElementSibling;
                if (!option.disabled) {
                    id = option.value;
                    break;
                }
            }

            var callbackItem = null;
            var itemType = selection.getAttribute('data-group-for');
            switch (itemType) {
                case 'groups':
                    callbackItem = findGroupById(id);
                    break;
                case 'assets':
                    callbackItem = findAssetById(id);
                    break;
                case 'places':
                    callbackItem = findPlaceById(id);
                    break;
                case 'fences':
                    callbackItem = findFenceById(id);
                    break;
                case 'trips':
                    callbackItem = findTripById(id);
                    break;
                case 'journeys':
                    callbackItem = findJourneyById(id);
                    break;
                case 'shared-views':
                    callbackItem = findSharedViewById(id);
                    break;
                default:
                    console.warn('Unknown item type for navigation:' + itemType);
                    break;
            }
            var callback = tracking.data.domNodes.panels.secondary.nextItemCallback;
            if (callback !== undefined && callback !== null) {
                callback(callbackItem);
            } else {
                console.warn('No asset selection callback defined.');
            }
        });

        $('#panel-primary').on('click', '#panel-primary-close', function (e) {
            e.preventDefault();
            closePrimaryPanel();
        });

        $('#panel-secondary').on('click', '#panel-secondary-close', function (e) {
            e.preventDefault();
            closeSecondaryPanel();
        });

        $('#panel-secondary').on('click', '#panel-dialog-back', function (e) {
            e.preventDefault();
            if (tracking.data.domNodes.panels.secondary.closeCallback !== undefined
                && tracking.data.domNodes.panels.secondary.closeCallback !== null) {
                tracking.data.domNodes.panels.secondary.closeCallback();
                tracking.data.domNodes.panels.secondary.closeCallback = undefined;
            }
            var itemSettings = $.proxy(itemSettingsClick, this);
            itemSettings();
        });

        $('#nav-header').on('click', '#toggle-nav-primary', function (e) {
            e.preventDefault();

            if (this.classList.contains('active')) {
                hidePrimaryPanel();
            } else {
                if (tracking.state.openPanels.secondary === true) {
                    openSecondaryPanel(false);
                } else {
                    openPrimaryPanel();
                }
            }
        });
        $('#iconApp').on('click', '#nav-toggle', function (e) {
            e.preventDefault();
            if (tracking.data.domNodes.nav.toggle.classList.contains('is-active')) {
                tracking.data.domNodes.nav.toggle.classList.remove('is-active');
                tracking.data.domNodes.nav.primary.classList.add('is-closed');
            } else {
                openPrimaryPanel();
                tracking.data.domNodes.nav.toggle.classList.add('is-active');
                tracking.data.domNodes.nav.primary.classList.remove('is-closed');
            }
            closeSecondaryPanel();
        });

        function handlePanelAction(e) {
            e.preventDefault();
            console.log('option click');
            switch (this.getAttribute('data-option-type')) {
                case 'sorting':
                    changeItemSort(this.getAttribute('data-sort-group'), parseInt(this.getAttribute('data-sort')));
                    break;
                case 'quick-actions':
                    switch (this.getAttribute('data-action')) {
                        case 'run-report':
                            openRunReportModal();
                            break;
                        case 'upload-file':
                            document.getElementById('ImportPlacesAndFences').disabled = true;
                            var status = document.getElementById('upload-file-status');
                            clearStatusMessage(status);
                            $(tracking.data.domNodes.modals.uploadFile).modal('show');
                            $('#UploadGeofencesMerge').prop('checked', false);
                            $('#MergeGeofenceName').val('');
                            $j('#UploadFilePreview,#UploadFileGeofencesMerge').hide();
                            break;
                        case 'add-place':
                            openPlaceDialog(null);
                            break;
                        case 'add-geofence':
                            openGeofenceDialog(null);
                            break;
                        case 'add-journey':
                            window.location = '/Settings/CreateJourney';
                            break;
                        case 'add-shared-view':
                            openSharedViewDialog(null);
                            break;
                        case 'copy-to-shared-view':
                            var form = document.createElement('form');
                            form.method = 'POST';
                            form.action = '/Settings/CreateSharedView';

                            _.each(tracking.data.visible.assets, function (assetId) {
                                var formAssetId = document.createElement('input');
                                formAssetId.name = 'AssetIds';
                                formAssetId.value = assetId;
                                form.appendChild(formAssetId);
                            });
                            _.each(tracking.data.visible.fences, function (fenceId) {
                                var formFenceId = document.createElement('input');
                                formFenceId.name = 'FenceIds';
                                formFenceId.value = fenceId;
                                form.appendChild(formFenceId);
                            });
                            _.each(tracking.data.visible.places, function (placeId) {
                                var formPlaceId = document.createElement('input');
                                formPlaceId.name = 'PlaceIds';
                                formPlaceId.value = placeId;
                                form.appendChild(formPlaceId);
                            });
                            _.each(tracking.data.visible.groups, function (groupId) {
                                var formAssetGroupId = document.createElement('input');
                                formAssetGroupId.name = 'AssetGroupIds';
                                formAssetGroupId.value = groupId;
                                form.appendChild(formAssetGroupId);
                            });

                            if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
                                var formFrom = document.createElement('input');
                                formFrom.name = 'From';
                                formFrom.value = $('#txtDateFrom').val();
                                form.appendChild(formFrom);

                                var formTo = document.createElement('input');
                                formTo.name = 'To';
                                formTo.value = $('#txtDateTo').val();
                                form.appendChild(formTo);
                            }

                            var formMode = document.createElement('input');
                            formMode.name = 'QuickActionMode';
                            formMode.value = tracking.state.activeMapMode === tracking.mapModes.LIVE ? 0 : 1;
                            form.appendChild(formMode);

                            var formAction = document.createElement('input');
                            formAction.name = 'IsQuickAction';
                            formAction.value = 'true';
                            form.appendChild(formAction);

                            var token = $('input[name="__RequestVerificationToken"]')[0].cloneNode();
                            form.appendChild(token);
                            document.body.appendChild(form);
                            form.submit();
                            break;
                        case 'position-to-place':
                            var addOrUpdatePlace = $.proxy(addOrUpdatePlaceClick, this);
                            addOrUpdatePlace();
                            break;
                        case 'path-asset-options':
                        case 'path-trip-options':
                        case 'position-options':
                        case 'place-options':
                        case 'fence-options':
                        case 'waypoint-options':
                            var itemSettings = $.proxy(itemSettingsClick, this);
                            itemSettings();
                            break;
                        case 'position-activity':
                            var assetId = parseInt(this.getAttribute('data-asset-id'));
                            var asset = findAssetById(assetId);
                            openActivityForAsset(asset);
                            break;
                        case 'position-hide':
                        case 'waypoint-hide':
                            var assetId = parseInt(this.getAttribute('data-asset-id'));
                            toggleAssetActive(assetId, false, true);
                            $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('close');
                            break;
                        case 'path-asset-hide':
                            var assetId = parseInt(this.getAttribute('data-asset-id'));
                            toggleAssetActive(assetId, false, true);
                            $(tracking.data.domNodes.infoDialogs.positionHistory).dialog('close');
                            break;
                        case 'path-trip-hide':
                            var journeyId = parseInt(this.getAttribute('data-journey-id'));
                            var tripId = parseInt(this.getAttribute('data-trip-id'));
                            toggleTripActive(journeyId, tripId, false, true);
                            $(tracking.data.domNodes.infoDialogs.positionHistory).dialog('close');
                            break;
                        case 'path-history':
                            var from = this.getAttribute('data-from');
                            var to = this.getAttribute('data-to');
                            var assetId = parseInt(this.getAttribute('data-asset-id'));
                            var dateFrom = document.getElementById('txtDateFrom');
                            var dateTo = document.getElementById('txtDateTo');
                            dateFrom.value = from;
                            dateTo.value = to;

                            // hide all assets except this one
                            // todo: helper function for this?
                            hideAllAssetsExcept(assetId);

                            // switch mode to history and run it
                            if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                                switchMapMode(false, null, true);
                            } else {
                                queryActiveAssets(null);
                            }
                            //var dateFormat = tracking.user.dateFormat.substring(0, tracking.user.dateFormat.indexOf(' '));
                            //historyDateFrom.value = fromDate.format(dateFormat + ' HH:mm');

                            break;
                        case 'place-route-from':
                        case 'position-route-from':
                            var lat = this.getAttribute('data-lat');
                            var lng = this.getAttribute('data-lng');
                            openRouting();
                            tracking.data.routing.mapClickDestination = $(document.getElementById('RoutingDestination1'));
                            addPointToRouting(L.latLng(lat, lng));
                            break;
                        case 'place-route-to':
                        case 'position-route-to':
                            var lat = this.getAttribute('data-lat');
                            var lng = this.getAttribute('data-lng');
                            openRouting();
                            tracking.data.routing.mapClickDestination = $(document.getElementById('RoutingDestination2'));
                            addPointToRouting(L.latLng(lat, lng));
                            break;
                        case 'place-measure-distance':
                        case 'position-measure-distance':
                            var lat = this.getAttribute('data-lat');
                            var lng = this.getAttribute('data-lng');
                            openRuler();
                            addPointToRuler(L.latLng(lat, lng));
                            break;
                        case 'position-toggle-map':
                            var id = this.getAttribute('data-position-id');
                            var assetId = this.getAttribute('data-asset-id');
                            var isNowHidden = this.getAttribute('data-hidden') !== 'true';
                            var sharedViewId = null;
                            if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW && tracking.data.sharedView.current !== null) {
                                sharedViewId = tracking.data.sharedView.current.Id;
                            }
                            $.when(togglePositionVisibility(assetId, id, isNowHidden, sharedViewId)).then(function () {
                                if (!isNowHidden) {
                                    // click it?
                                }
                            });
                            break;
                        case 'place-hide':
                            var placeId = parseInt(this.getAttribute('data-place-id'));
                            togglePlaceActive(placeId, false, true);
                            $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('close');
                            break;
                        case 'place-route-asset':
                            var placeId = parseInt(this.getAttribute('data-place-id'));
                            var place = findPlaceById(placeId);
                            openPlaceRoutingForPlace(place);
                            break;
                        case 'fence-hide':
                            toggleFenceActive(this.getAttribute('data-fence-id'), false, true);
                            $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('close');
                            break;
                        case 'fence-send-message':
                            var fence = findFenceById(this.getAttribute('data-fence-id'));
                            openSendMessageDialog(null, null, fence);
                            break;
                        case 'fence-create-alert':
                            var fence = findFenceById(this.getAttribute('data-fence-id'));
                            var assetIds = findAssetIdsInGeofence(fence);
                            window.location = '/Alerts/CreateAlert?assetIds=' + assetIds.join() + '&fenceId=' + fence.Id;
                            break;
                        case 'fence-group-assets':
                            var fence = findFenceById(this.getAttribute('data-fence-id'));
                            openAssetGroupDialog(null);
                            var assetIds = findAssetIdsInGeofence(fence);
                            var assets = new Array();
                            for (var i = 0; i < assetIds.length; i++) {
                                var asset = findAssetById(assetIds[i]);
                                if (asset == null) {
                                    continue;
                                }
                                assets.push(asset);
                                $('#edit-asset-group-assets-list input[value=' + asset.Id + ']').prop('checked', true);
                            }
                            break;
                        case 'fence-location-report':
                            window.location = '/Reports/Location?fenceId=' + this.getAttribute('data-fence-id');
                            break;
                        case 'fence-history':
                            var fence = findFenceById(this.getAttribute('data-fence-id'));
                            var assetIds = findAssetIdsInGeofence(fence);
                            loadHistory(assetIds);
                            break;
                        case 'waypoint-route-asset':
                            var waypoint = findWaypointById(parseInt(this.getAttribute('data-waypoint-id')));
                            waypointGetRoute(waypoint);
                            break;
                        case 'waypoint-mark-complete':
                            var waypoint = findWaypointById(parseInt(this.getAttribute('data-waypoint-id')));
                            waypointMarkComplete(waypoint);
                            break;
                        case 'shared-view-status':
                            var sharedView = findSharedViewById(parseInt(this.getAttribute('data-shared-view-id')));
                            changeSharedViewStatus(sharedView);
                            break;
                        case 'shared-view-options':
                            var sharedView = findSharedViewById(parseInt(this.getAttribute('data-shared-view-id')));
                            openSharedViewSettingsPanel(sharedView);
                            break;
                        default:
                            console.log('no action handler for: ' + this.getAttribute('data-action'));
                            break;
                    }
                    break;
                case 'list-sort':
                    //// for asset notifications, since the sorting options are outside of the list containers
                    //var sortBy = this.getAttribute('data-sort');
                    //var isActive = this.classList.contains('active');
                    //var sortDirection = this.classList.contains('desc') ? 'asc' : 'desc'; // toggle sort direction if active
                    //this.classList.remove('desc');
                    //this.classList.remove('asc');
                    //this.classList.add(sortDirection);
                    //this.classList.add('active');
                    //// todo: toggle other active options in this group (when we have more than 1 option!)
                    //if (!isActive) {
                    //    sortDirection = 'asc';
                    //}
                    //this.setAttribute('data-sort-dir', sortDirection);
                    //if (tracking.data.domNodes.panels.secondary.currentList !== undefined) {
                    //    tracking.data.domNodes.panels.secondary.currentList.sort(sortBy, { order: sortDirection });
                    //}
                    break;
                default:
                    console.warn('Unknown panel option type: ' + this.getAttribute('data-option-type'));
                    break;
            }
        }

        $('.filter-options-list, .panel-options-list').on('click', 'a.dropdown-item', handlePanelAction);
        $('.option-actions-list').on('click', 'button.option-action', handlePanelAction);

        $('.filter-box').on('keyup', '.filter-input', function (e) {
            switch (this.id) {
                case 'filter-asset-text':
                    filterAssets(this.value);
                    break;
                case 'filter-fence-text':
                    filterFences(this.value);
                    break;
                case 'filter-place-text':
                    filterPlaces(this.value);
                    break;
                case 'filter-shared-view-text':
                    filterSharedViews(this.value);
                    break;
            }
            if (this.value === '') {
                this.parentElement.nextElementSibling.classList.remove('is-visible');
            } else {
                this.parentElement.nextElementSibling.classList.add('is-visible');
            }
        });

        $(document).on('click', '.input-clear', function(e) {
            var input = this.parentNode.querySelector('.filter-input');
            input.value = '';
            $(input).trigger('keyup');

            // jquery .trigger doesn't work in notifying listjs bound event for some reason
            var e = document.createEvent('HTMLEvents');
            e.initEvent('keyup', false, true);
            input.dispatchEvent(e);
        });

        $('#nav-primary').on('click', 'a', function (e) {
            e.preventDefault();
            $(this).bsTooltip('hide');
            if (this.parentNode.classList.contains('disabled')) {
                return;
            }
            openPrimaryPanel();
            var toPanel = parseInt(this.parentNode.getAttribute('data-panel'));
            //switch (toPanel) {
            //    case tracking.panels.LIVE:
            //        switchView(true);
            //        break;
            //    case tracking.panels.HISTORY:
            //        switchView(false);
            //        break;
            //    default:
            //        break;
            //}
            changeActivePanel(toPanel, false);
        });

        $('#filter-history-range').on('click', 'button', function (e) {
            e.preventDefault();
            filterDateClick(this);
        });

        var assetsContainer = $j('#panel-content-wrapper');
        assetsContainer.on('mouseover mouseout', 'a.location,div.asset-recent-location', function (event) {
            var link = $j(this);
            var markerId = link.attr('data-marker');
            var markers = getMapMarkersForDataGroup(getAssetDataGroupForCurrentViewMode()).concat(getMapMarkersForDataGroup(getJourneyDataGroupForCurrentViewMode()));
            if (markers !== undefined) {
                for (var i = 0; i < markers.length; i++) {
                    var loc = markers[i].data.location;
                    if (loc.Id === markerId) {
                        if (event.type === 'mouseover') {
                            markers[i].fire('mouseover');
                        } else {
                            markers[i].fire('mouseout');
                        }
                        break;
                    }
                }
            }
        });

        $('#dialog-functions').on('mouseover mouseout', 'a.location,div.asset-recent-location', function (e) {
            var markerId = this.getAttribute('data-marker');
            var markers = getMapMarkersForDataGroup(getAssetDataGroupForCurrentViewMode()).concat(getMapMarkersForDataGroup(getJourneyDataGroupForCurrentViewMode()));
            if (markers !== undefined) {
                for (var i = 0; i < markers.length; i++) {
                    var loc = markers[i].data.location;
                    if (loc.Id === markerId) {
                        if (event.type === 'mouseover') {
                            markers[i].fire('mouseover');
                        } else {
                            markers[i].fire('mouseout');
                        }
                        break;
                    }
                }
            }
        });

        assetsContainer.on('click', 'a.location', function(e) {
            e.preventDefault();
            highlightPosition(this.getAttribute('data-marker'), null);
        });

        function clearCachedListingsRelatedToPositionId(positionId, isNowHidden, viewMode) {
            // any listing caches need to be cleared, but only for this view mode
            var dataSource = (viewMode === tracking.viewModes.SHARED_VIEW ? tracking.data.sharedView : tracking.data);
            var nodeSource = (viewMode === tracking.viewModes.SHARED_VIEW ? tracking.data.domNodes.sharedView : tracking.data.domNodes);
            var corePosition = dataSource.positionsById[positionId];
            if (corePosition !== undefined) {
                delete tracking.data.domNodes.positionListingById[positionId];

                _.each(corePosition.Position.Events, function (positionEvent) {
                    delete nodeSource.eventListingById[positionEvent.Id];
                    delete nodeSource.eventListingById[positionEvent.Id];
                });

                var positionMessages = _.filter(tracking.data.messagesById, function (item) { return item.Position !== undefined && item.Position !== null && item.Position.Id === positionId; });
                _.each(positionMessages, function (item) {
                    delete nodeSource.messageListingById[item.Id];
                });

                delete nodeSource.activityListingById[corePosition.Epoch + '-' + corePosition.AssetId];

                var notificationLists = [tracking.notificationLists.activityList, tracking.notificationLists.alertsList, tracking.notificationLists.chatList, tracking.notificationLists.eventsList, tracking.notificationLists.messagesList, tracking.notificationLists.positionsList];
                _.each(notificationLists, function (list) {
                    if (list === null) {
                        return;
                    }
                    _.each(list.data.items, function (item) {
                        if (item.Position !== undefined && item.Position !== null && item.PositionId === positionId) {
                            item.Position.IsHidden = isNowHidden;
                        }
                    });
                });
            }
        }

        function togglePositionVisibility(assetId, positionId, isNowHidden, sharedViewId) {
            var key = 'position-visibility-' + positionId;
            toggleLoadingMessage(true, key);
            var data = {
                AssetId: assetId,
                PositionId: positionId,
                IsVisible: !isNowHidden,
                SharedViewId: sharedViewId
            };
            return $.ajax({
                type: 'POST',
                url: wrapUrl('/api/ui/updatepositionvisibility'),
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify(data),
                dataType: 'json',
                success: function (msg) {
                    if (msg.success) {
                        var asset = findAssetById(assetId);
                        if (sharedViewId === null) {
                            // if it was hidden, then will it have a marker?
                            var liveMarker = tracking.data.live.markersByPositionId[positionId];
                            var historyMarker = tracking.data.history.markersByPositionId[positionId];
                            var tripMarkers = _.filter(tracking.data.trips.markers, function (item) { return item.data.location.Id === positionId; });

                            var isAssetCurrentlyActive = !isItemIncluded(tracking.user.displayPreferences.hiddenAssets, assetId);
                            if (liveMarker !== undefined) {
                                liveMarker.data.location.IsHidden = isNowHidden;
                                if (isAssetCurrentlyActive && tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                                    if (isNowHidden) {
                                        removeItemFromMap(liveMarker);
                                    } else {
                                        addItemToMap(liveMarker);
                                    }
                                }
                            }

                            if (historyMarker !== undefined) {
                                historyMarker.data.location.IsHidden = isNowHidden;
                                if (tracking.preferences.PREFERENCE_GROUP_POSITIONS && tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined) {
                                    // add/remove marker from asset's marker cluster
                                    if (isNowHidden) {
                                        tracking.data.history.markerClustersByAssetId[asset.Id].removeLayer(historyMarker);
                                    } else {
                                        tracking.data.history.markerClustersByAssetId[asset.Id].addLayer(historyMarker);
                                    }
                                } else if (isAssetCurrentlyActive && tracking.state.activeMapMode !== tracking.mapModes.LIVE) {
                                    // markers added individually to map
                                    if (isNowHidden) {
                                        removeItemFromMap(historyMarker);
                                    } else {
                                        addItemToMap(historyMarker);
                                    }
                                }
                            }

                            // position could be in multiple trips
                            _.each(tripMarkers, function (tripMarker) {
                                tripMarker.data.location.IsHidden = isNowHidden;
                                // add/remove from map - trip may be hidden
                                var tripId = tripMarker.data.tripId;
                                var isTripCurrentlyActive = isItemIncluded(tracking.user.displayPreferences.visibleTrips, tripId);
                                var trip = findTripById(tripId);
                                if (tracking.preferences.PREFERENCE_GROUP_POSITIONS && tracking.data.trips.markerClustersByTripId[trip.Id] !== undefined) {
                                    if (isNowHidden) {
                                        tracking.data.trips.markerClustersByTripId[trip.Id].removeLayer(tripMarker);
                                    } else {
                                        tracking.data.trips.markerClustersByTripId[trip.Id].addLayer(tripMarker);
                                    }
                                } else if (isTripCurrentlyActive) {
                                    // trip data active in both live and history mode
                                    if (isNowHidden) {
                                        removeItemFromMap(tripMarker);
                                    } else {
                                        addItemToMap(tripMarker);
                                    }
                                }
                            });

                            updateAssetPositionLines(asset, true);
                            clearCachedListingsRelatedToPositionId(positionId, isNowHidden, tracking.viewModes.NORMAL);
                        } else {
                            var positionMarkers = _.filter(tracking.data.sharedView.markers, function (item) { return item.data.location.Id === positionId; });
                            _.each(positionMarkers, function (sharedViewMarker) {
                                sharedViewMarker.data.location.IsHidden = isNowHidden;

                                if (tracking.preferences.PREFERENCE_GROUP_POSITIONS && tracking.data.sharedView.markerClustersByAssetId[assetId] !== undefined) {
                                    if (isNowHidden) {
                                        tracking.data.sharedView.markerClustersByAssetId[assetId].removeLayer(sharedViewMarker);
                                    } else {
                                        tracking.data.sharedView.markerClustersByAssetId[assetId].addLayer(sharedViewMarker);
                                    }
                                } else {
                                    if (isNowHidden) {
                                        removeItemFromMap(sharedViewMarker, null, tracking.viewModes.SHARED_VIEW);
                                    } else {
                                        addItemToMap(sharedViewMarker, null, tracking.viewModes.SHARED_VIEW);
                                    }
                                }
                            });
                            updateAssetPositionLines(asset, true, tracking.viewModes.SHARED_VIEW);
                            clearCachedListingsRelatedToPositionId(positionId, isNowHidden, tracking.viewModes.SHARED_VIEW);
                        }
                        // any map-toggles with this positionId need updated for this view mode
                        var viewSelector = '';
                        if (sharedViewId !== null) {
                            viewSelector = '#panel-secondary[data-item-type="shared-views"] ';
                        }
                        var icons = document.querySelectorAll(viewSelector + '.map-toggle[data-marker="' + positionId + '"]');
                        _.each(icons, function (icon) {
                            // update parent container as well, up two levels
                            var container = icon.parentNode.parentNode;
                            if (isNowHidden) {
                                container.classList.add('is-hidden');
                            } else {
                                container.classList.remove('is-hidden');
                            }

                            icon.setAttribute('data-hidden', isNowHidden);
                            icon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#' + (isNowHidden ? 'invisible' : 'visible'));
                            icon.title = isNowHidden ? tracking.strings.SHOW_ON_MAP : tracking.strings.HIDE_ON_MAP;
                        });

                        // any quick actions for this position need updated (position information dialog)
                        var togglePositionActions = document.querySelectorAll('[data-action="position-toggle-map"][data-position-id="' + positionId + '"]');
                        _.each(togglePositionActions, function (actionPositionVisibility) {
                            actionPositionVisibility.setAttribute('data-hidden', isNowHidden);
                            actionPositionVisibility.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#' + (isNowHidden ? 'visible' : 'invisible'));
                            actionPositionVisibility.querySelector('span').innerText = isNowHidden ? tracking.strings.SHOW_POSITION : tracking.strings.HIDE_POSITION;
                            actionPositionVisibility.title = isNowHidden ? tracking.strings.SHOW_POSITION : tracking.strings.HIDE_POSITION;
                        });
                    } else {
                        utility.handleWebServiceError(msg.ErrorMessage);
                    }
                    toggleLoadingMessage(false, key);
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.ERROR_POSITION_VISIBILITY);
                    toggleLoadingMessage(false, key);
                }
            });
        }

        $('#dialog-functions').on('click', 'a.map-toggle', function(e) {
            e.preventDefault();

            if (!tracking.user.canEditAssets) {
                return;
            }

            var markerId = this.getAttribute('data-marker');
            var assetId = this.getAttribute('data-asset');
            var isNowHidden = this.getAttribute('data-hidden') !== 'true';
            var sharedViewId = null;
            if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW && tracking.data.sharedView.current !== null) {
                sharedViewId = tracking.data.sharedView.current.Id;
            }

            togglePositionVisibility(assetId, markerId, isNowHidden, sharedViewId);

            // any map-toggles with this positionId need updated
            var icon = this;
            icon.setAttribute('data-hidden', isNowHidden);
            icon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#' + (isNowHidden ? 'invisible' : 'visible'));            
        });

        $('#dialog-functions').on('click', 'a.location', function (e) {
            e.preventDefault();
            highlightPosition(this.getAttribute('data-marker'), null);
        });

        // position history date groupings
        assetsContainer.on('click', 'span.date a', function (e) {
            e.preventDefault();
            var $ol = $(this).parent().next().next('ol');
            if ($ol.hasClass('is-visible')) {
                $ol.removeClass('is-visible');
            } else {
                $ol.addClass('is-visible');
            }
        });

        assetsContainer.on('click', '.trip-name', function (e) {
            console.log('trip click');
            e.preventDefault();
            var tripNode = this.parentNode.parentNode.parentNode;
            var thisTripId = parseInt(tripNode.getAttribute('data-trip-id'));
            //expandItemNotificationList(tripNode, true);
            var trip = findTripById(thisTripId);
            itemClickedInPrimaryPanel(trip, 'trips');

            // if invisible, do nothing? (or auto-make visible?)
            if (_.indexOf(tracking.data.visible.trips, thisTripId) === -1) {
                return;
            }

            // zoom to trip bounds and open trip info panel
            var tripPositions = tracking.data.trips.positionsByTripId[trip.Id];
            var tripBounds = L.latLngBounds();
            _.each(tripPositions.Positions, function(position) {
                tripBounds.extend(L.latLng(position.Lat, position.Lng));
            });

            if (tripBounds.isValid()) {
                tracking.map.fitBounds(tripBounds, { padding: [10, 10] });
                if (tracking.map.getZoom() > tracking.options.maximumZoom) {
                    tracking.map.setZoom(tracking.options.maximumZoom);
                }
            }

            var journey = findJourneyById(trip.JourneyId);
            var asset = findAssetById(journey.AssetId);
            createPositionHistorySummary(asset, trip);
        });

        // place name click
        assetsContainer.on('click', '.place-name', function(e) {
            e.preventDefault();
            var thisPlaceId = parseInt(this.parentNode.parentNode.parentNode.getAttribute('data-place-id'));
            var place = findPlaceById(thisPlaceId);
            itemClickedInPrimaryPanel(place, 'places');
            if (_.indexOf(tracking.data.visible.places, thisPlaceId) === -1) {
                return;
            }

            // take to place
            for (var i = 0; i < tracking.data.placeMarkers.length; i++) {
                var placeId = tracking.data.placeMarkers[i].data.placeId;
                if (placeId == thisPlaceId) {
                	//tracking.data.placeMarkers[i].fire('click');
                    markerClick(tracking.data.placeMarkers[i], 'place', null, false);
                    break;
                }
            }
        });

        assetsContainer.on('click', '.shared-view-name', function (e) {
            e.preventDefault();
            var thisSharedViewId = parseInt(this.parentNode.parentNode.parentNode.getAttribute('data-shared-view-id'));
            var sharedView = findSharedViewById(thisSharedViewId);
            itemClickedInPrimaryPanel(sharedView, 'shared-views');
            
            selectSharedView(sharedView);
        });

        assetsContainer.on('mouseover mouseout', 'div.fence-name', function (e) {
            e.preventDefault();
            if (e.type == 'mouseover') {
                var thisFenceId = this.parentNode.parentNode.parentNode.getAttribute('data-fence-id');
                var fence = findFenceById(thisFenceId);
                showFenceTooltip(fence);
            } else {
                hideMouseTooltip();
            }
        });

        // fence name click
        assetsContainer.on('click', '.fence-name', function(e) {
            e.preventDefault();
            var thisFenceId = this.parentNode.parentNode.parentNode.getAttribute('data-fence-id');
            var fence = findFenceById(thisFenceId);

            itemClickedInPrimaryPanel(fence, 'fences');
            if (_.indexOf(tracking.data.visible.fences, thisFenceId) === -1) {
                return;
            }

            var centeredFence = centerOnFence(thisFenceId);
            if (centeredFence != null && centeredFence.markers.length > 0) {
                markerClick(centeredFence.markers[0], 'fence', centeredFence.center, false);
            }
        });

        assetsContainer.on('click', 'a.item-action', function (e) {
            e.preventDefault();

            var actionType = this.getAttribute('data-action');
            switch (actionType) {
                case 'current-position':
                    var thisAssetId = parseInt(this.getAttribute('data-asset-id'));
                    var asset = findAssetById(parseInt(this.getAttribute('data-asset-id')));
                    openAssetLatestPosition(asset);
                    break;
                default:
                    break;
            }
        });

        assetsContainer.on('click', '.asset-notify-item', function (e) {
            var assetId = this.parentNode.parentNode.parentNode.parentNode.getAttribute('data-asset-id');
            var asset = findAssetById(assetId);
            if (asset === undefined || asset === null) {
                return;
            }
            if (this.classList.contains('notify-position')) {
                openPositionsForAsset(asset);
            } else if (this.classList.contains('notify-alert')) {
                openAlertsForAsset(asset);
            } else if (this.classList.contains('notify-event')) {
                openEventsForAsset(asset);
            } else if (this.classList.contains('notify-status')) {
                openStatusForAsset(asset);
            } else if (this.classList.contains('notify-message')) {
                openMessagesForAsset(asset);
            } else if (this.classList.contains('notify-chat')) {
                openChatForAsset(asset);
            } else {
                console.warn('No match for notify item');
            }
        });

        assetsContainer.on('mouseover mouseout', 'div.trip-name', function (e) {
            var tripNode = this.parentNode.parentNode.parentNode;
            var thisTripId = parseInt(tripNode.getAttribute('data-trip-id'));
            var trip = findTripById(thisTripId);

            if (_.indexOf(tracking.data.visible.trips, thisTripId) === -1)
                return;

            // highlight latest position
            var tripPositions = tracking.data.trips.positionsByTripId[trip.Id];
            if (tripPositions !== undefined && tripPositions.Positions !== undefined && tripPositions.Positions.length > 0) {
                var latestVisiblePosition = _.find(tripPositions.Positions, function(item) { return !item.IsHidden; });
                if (latestVisiblePosition !== undefined) {
                    var latestVisibleMarker = _.find(tracking.data.trips.markersByTripId[trip.Id], function(item) { return item.data.location.Id === latestVisiblePosition.Id; });
                    if (latestVisibleMarker !== undefined) {
                        latestVisibleMarker.fire(e.type);
                    }
                }
            }
        });

        assetsContainer.on('mouseover mouseout', 'div.asset-name', function (e) {
            var assetNode = this.parentNode.parentNode.parentNode;
            var thisAssetId = parseInt(assetNode.getAttribute('data-asset-id'));
            var asset = findAssetById(thisAssetId);

            if (_.indexOf(tracking.data.visible.assets, thisAssetId) === -1) {
                return;
            }

            if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                // take to latest position
                var latestPosition = tracking.data.live.latestPositionsByAssetId[asset.Id];
                if (latestPosition !== undefined && latestPosition.Position !== undefined && latestPosition.Position !== null) {
                    var positionMarker = tracking.data.live.markersByPositionId[latestPosition.Position.Id];
                    if (positionMarker !== undefined) {
                        if (tracking.map.getBounds().contains(positionMarker.getLatLng())) {
                            positionMarker.fire(e.type);
                        }
                    }
                }
            } else {
                // take to most recent position
                if (tracking.data.history.positionsByAssetId[asset.Id] !== undefined) {
                    var firstPosition = _.first(tracking.data.history.positionsByAssetId[asset.Id].Positions);
                    if (firstPosition !== undefined) {
                        var positionMarker = tracking.data.history.markersByPositionId[firstPosition.Id];
                        if (positionMarker !== undefined) {
                            if (tracking.map.getBounds().contains(positionMarker.getLatLng())) {
                                positionMarker.fire(e.type);
                            }
                        }
                    }
                }
            }
        });

        assetsContainer.on('click', 'a.asset-name,div.asset-name', function(e) {
            e.preventDefault();
            var assetNode = this.parentNode.parentNode.parentNode;
            var thisAssetId = parseInt(assetNode.getAttribute('data-asset-id'));
            //expandItemNotificationList(assetNode, false);
            var asset = findAssetById(thisAssetId);
            itemClickedInPrimaryPanel(asset, 'assets');
            //toggleAssetNotificationList(assetNode, true);

            // if invisible, do nothing? (or auto-make visible?)
            if (_.indexOf(tracking.data.visible.assets, thisAssetId) === -1) {
                return;
            }

            if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                // take to latest position
                var latestPosition = tracking.data.live.latestPositionsByAssetId[asset.Id];
                if (latestPosition !== undefined && latestPosition.Position !== undefined && latestPosition.Position !== null) {
                    var positionMarker = tracking.data.live.markersByPositionId[latestPosition.Position.Id];
                    if (positionMarker !== undefined) {
                        markerClick(positionMarker, 'position', null, false);
                    }
                }
            } else {
                // take to most recent position
                if (tracking.data.history.positionsByAssetId[asset.Id] !== undefined) {
                    var firstPosition = _.first(tracking.data.history.positionsByAssetId[asset.Id].Positions);
                    if (firstPosition !== undefined) {
                        var positionMarker = tracking.data.history.markersByPositionId[firstPosition.Id];
                        if (positionMarker !== undefined) {
                            markerClick(positionMarker, 'position', null, false);
                        }
                    }
                }
            }
        });

        // asset/fence show/hide checkboxes
        assetsContainer.on('click', 'input.showhide,svg.showhide', function (e) {
            var $item = $(this);
            var assetId = $item.data('assetId');
            var fenceId = $item.data('fenceId');
            var placeId = $item.data('placeId');
            var groupId = $item.data('groupId');
            var tripId = $item.data('tripId');
            var journeyId = $item.data('journeyId');
            var wasActive = $item.hasClass('active') || $item.hasClass('indeterminate');
            var isNowActive = !wasActive;
            //var isChecked = $item.prop('checked');
            if (assetId != null) {
                toggleAssetActive(assetId, isNowActive, true);
            } else if (fenceId != null) {
                toggleFenceActive(fenceId, isNowActive, true);
            } else if (placeId != null) {
                togglePlaceActive(placeId, isNowActive, true);
            } else if (tripId != null && journeyId != null) {
                toggleTripActive(journeyId, tripId, isNowActive, true);
            } else if (groupId != null) {
                switch (groupId) {
                    case 'all-assets':
                        // toggle all assets
                        _.each(tracking.data.assets, function (asset) {
                            toggleAssetActive(asset.Id, isNowActive, false);
                        });

                        // update group visibility statuses
                        _.each(tracking.data.groups, function (group) {
                            updateGroupVisibilityStatus(group.Id);
                        });
                        updateGroupVisibilityStatus('all-assets');

                        // update UI for asset count now
                        updateActiveAssetInformation();

                        //// uncheck all groups if inactive
                        //if (!isChecked) {
                        //    _.each(tracking.data.groups, function (group) {
                        //        $('#group-' + group.Id).children('input.showhide').prop('checked', false);
                        //    });
                        //}
                        break;
                    case 'all-fences':
                        // toggle all fences
                        _.each(tracking.data.fences, function (fence) {
                            toggleFenceActive(fence.Id, isNowActive, false);
                        });
                        updateGroupVisibilityStatus('all-fences');
                        break;
                    case 'all-places':
                        // toggle all places
                        _.each(tracking.data.places, function (place) {
                            togglePlaceActive(place.Id, isNowActive, false);
                        });
                        updateGroupVisibilityStatus('all-places');
                        break;
                    default:
                    console.log(groupId);
                    console.log(isNowActive);
                        toggleGroupActive(groupId, isNowActive);
                        updateGroupVisibilityStatus(groupId);
                        updateGroupVisibilityStatus('all-assets');
                        updateActiveAssetInformation();
                        break;
                }
            }
        });

        $('#form-history-date-range').on('click', '#HistoryChangeDates', function (e) {
            e.preventDefault();
            document.getElementById('txtDateFrom').focus();
        });

        $('#form-history-date-range').on('click', '#LoadLimitedData', function (e) {
            e.preventDefault();
            
            var btn = this;
            btn.disabled = true;
            if (tracking.state.activeViewMode === tracking.viewModes.NORMAL && tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
                $.when(queryActiveAssets(undefined, undefined, true)).done(function() {
                    btn.disabled = false;
                });
            } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
                // requery shared view with dates entered in panel
                // user may have chosen different dates
                var dateFilter = {
                    // TODO UTC
                    fromUtc: document.getElementById('txtDateFrom').value,
                    toUtc: document.getElementById('txtDateTo').value
                };
                if (tracking.data.sharedView.temp !== null) {
                    querySharedViewData(tracking.data.sharedView.temp, dateFilter, true);
                } else if (tracking.data.sharedView.current !== null) {
                    querySharedViewData(tracking.data.sharedView.current, dateFilter, true);
                }
                btn.disabled = false;
            }
        });

        $('#map-mode-details-limited-nav').on('click', '#limited-data-prev', function (e) {
            e.preventDefault();
            var source = null;
            if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
                source = tracking.data.history;
            } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
                source = tracking.data.sharedView;
            }

            var data = source.limitedData;
            data.currentPage++;
            var newFromUtc = null;
            var newToUtc = null;
            var newFromEpoch = null;
            var newToEpoch = null;
            var newFromLocal = null;
            var newToLocal = null;
            if (data.pageDates[data.currentPage] !== undefined) {
                var existingPage = data.pageDates[data.currentPage];
                newFromUtc = existingPage.fromUtc;
                newToUtc = existingPage.toUtc;
            } else {
                // one second prior to current from date - must use epoch
                newToUtc = moment.utc(data.visibleFromDateEpoch - 1000).format(tracking.user.dateWithStandardTimeFormat);
                newFromUtc = data.fromDateUtc;
            }

            if (newFromUtc !== null) {
                newFromEpoch = moment.utc(newFromUtc, tracking.user.dateWithStandardTimeFormat).valueOf();
            }
            if (newToUtc !== null) {
                newToEpoch = moment.utc(newToUtc, tracking.user.dateWithStandardTimeFormat).valueOf();
            }
            // requery shared view with new page of data            
            var dateFilter = {
                fromLocal: newFromLocal,
                toLocal: newToLocal,
                fromUtc: newFromUtc,
                toUtc: newToUtc,
                fromEpoch: newFromEpoch,
                toEpoch: newToEpoch
            };

            console.log('dateFilter', dateFilter);
            if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
                // requery history with limited date range (page of data)                
                queryActiveAssets(undefined, undefined, true, dateFilter);
            } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {                
                if (tracking.data.sharedView.temp !== null) {
                    querySharedViewData(tracking.data.sharedView.temp, dateFilter, true);
                } else if (tracking.data.sharedView.current !== null) {
                    querySharedViewData(tracking.data.sharedView.current, dateFilter, true);
                }
            }
        });

        $('#map-mode-details-limited-nav').on('click', '#limited-data-next', function (e) {
            e.preventDefault();
            var source = null;
            if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
                source = tracking.data.history;
            } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
                source = tracking.data.sharedView;
            }

            var data = source.limitedData;
            data.currentPage--;
            if (data.pageDates[data.currentPage] !== undefined) {
                var existingPage = data.pageDates[data.currentPage];
                var newFromEpoch = null;
                var newToEpoch = null;
                if (existingPage.fromUtc !== null) {
                    newFromEpoch = moment.utc(existingPage.fromUtc, tracking.user.dateWithStandardTimeFormat).valueOf();
                }
                if (existingPage.toUtc !== null) {
                    newToEpoch = moment.utc(existingPage.toUtc, tracking.user.dateWithStandardTimeFormat).valueOf();
                }
                var dateFilter = {
                    fromUtc: existingPage.fromUtc,
                    toUtc: existingPage.toUtc,
                    fromEpoch: newFromEpoch,
                    toEpoch: newToEpoch
                };

                if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
                    // requery history with limited date range (page of data)
                    queryActiveAssets(undefined, undefined, true, dateFilter);
                } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
                    if (tracking.data.sharedView.temp !== null) {
                        querySharedViewData(tracking.data.sharedView.temp, dateFilter, true);
                    } else if (tracking.data.sharedView.current !== null) {
                        querySharedViewData(tracking.data.sharedView.current, dateFilter, true);
                    }
                }
            } 
        });

        $('#form-history-date-range').on('click', '#btnGo', function (e) {
            e.preventDefault();
            if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
                if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                    switchMapMode(false, null, true);
                } else {
                    queryActiveAssets();
                }
            } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
                // this is disabled currently
                //// requery shared view with dates entered in panel, limits apply
                //var dateFilter = {
                //    from: document.getElementById('txtDateFrom').value,
                //    to: document.getElementById('txtDateTo').value
                //};
                //if (tracking.data.sharedView.temp !== null) {
                //    querySharedViewData(tracking.data.sharedView.temp, dateFilter, false);
                //} else if (tracking.data.sharedView.current !== null) {
                //    querySharedViewData(tracking.data.sharedView.current, dateFilter, false);
                //}
                //// TODO this is a mess - move to proper spot
                //var historyCustom = document.getElementById('history-custom');
                //var historyButtons = document.getElementById('filter-history-range').querySelectorAll('button');
                //_.each(historyButtons, function (button) {
                //    button.classList.remove('active');
                //});
                //historyCustom.classList.add('active');
                //historyCustom.classList.add('btn-primary');
            }
        });

        assetsContainer.on('click', 'a.mailbox', function(e) {
            e.preventDefault();
            var asset = findAssetById($j(this).parent().parent().data('assetId'));
            loadMessageHistoryDialog(asset);
        });

        assetsContainer.on('click', 'div.status a', function (e) {
            e.preventDefault();
        });

        assetsContainer.on('click', 'svg.waypoint', function (e) {
            e.preventDefault();
            var thisWaypointId = parseInt(this.getAttribute('data-waypoint-id'));
            if (waypointMarkers == null) {
                return;
            }
            for (var i = 0; i < waypointMarkers.length; i++) {
                var waypointId = waypointMarkers[i].data.waypointId;
                if (waypointId === thisWaypointId) {
                    markerClick(waypointMarkers[i], 'waypoint', null, false);
                	//waypointMarkers[i].fire('click');
                    break;
                }
            }
        });

        assetsContainer.on('click', 'svg.asset-alert', function (e) {
            e.preventDefault();

            var asset = findAssetById(parseInt(this.getAttribute('data-asset-id')));
            if (asset == null)
                return;
            var alert = null;
            for (var i = 0; i < tracking.data.alerts.length; i++) {
                if (tracking.data.alerts[i].AssetId == asset.Id) {
                    alert = tracking.data.alerts[i];
                    break;
                }
            }
            if (alert == null)
                return;
            openAcknowledgeAlertDialog(asset, alert);
            //openAlertsRequiringAcknowledgementDialog(alt);
        });

        assetsContainer.on('click', '.group-info', function (e) {
            // pan/zoom to visible assets/fences/places within group
            var parentNode = this.parentNode.parentNode;
            var groupId = parentNode.getAttribute('id').substring(6);
            var groupType = parentNode.getAttribute('data-group-for');
            var bounds = L.latLngBounds();

            // toggle expand/contract for groups only
            toggleGroupExpanded(groupId);

            switch (groupType) {
                case 'assets':
                    var groupAssetIds = [];
                    if (groupId == 'all-assets') {
                        groupAssetIds = _.map(tracking.data.assets, 'Id');
                    } else {
                        var group = findGroupById(groupId);
                        if (group !== null) {
                            groupAssetIds = findAssetIdsUnderGroup(group);

                            itemClickedInPrimaryPanel(group, 'groups');
                        }
                    }
                    if (groupAssetIds.length === 0) {
                        return;
                    }

                    _.each(groupAssetIds, function (assetId, index, list) {
                        // don't include in bounds if it's not visible
                        if (_.indexOf(tracking.data.visible.assets, assetId) === -1) {
                            return;
                        }

                        if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                            // include latest position
                            if (tracking.data.live.latestPositionsByAssetId[assetId] !== undefined) {
                                bounds.extend(L.latLng(tracking.data.live.latestPositionsByAssetId[assetId].Position.Lat, tracking.data.live.latestPositionsByAssetId[assetId].Position.Lng));
                            }
                        } else {
                            // include all positions for asset
                            if (tracking.data.history.positionsByAssetId[assetId] !== undefined) {
                                _.each(tracking.data.history.positionsByAssetId[assetId].Positions, function (position) {
                                    bounds.extend(L.latLng(position.Lat, position.Lng));
                                });
                            }
                        }
                    });
                    break;
                case 'journeys':
                    var groupTripIds = [];
                    var journeyId = parseInt(groupId.substring(8));
                    var journey = findJourneyById(journeyId);
                    _.each(journey.Trips, function(trip) {
                        if (_.indexOf(tracking.data.visible.trips, trip.Id) === -1) {
                            return;
                        }

                        if (tracking.data.trips.positionsByTripId[trip.Id] !== undefined) {
                            _.each(tracking.data.trips.positionsByTripId[trip.Id].Positions, function (position) {
                                bounds.extend(L.latLng(position.Lat, position.Lng));
                            });
                        }
                    });

                    break;
                case 'fences':
                    var groupFenceIds = [];
                    if (groupId == 'all-fences') {
                        groupFenceIds = _.map(tracking.data.fences, 'Id');
                    } else {
                        // todo: support fence groups
                        var group = findGroupById(groupId);
                        if (group !== null) {
                            //groupFenceIds = findFenceIdsUnderGroup(group);
                        }
                    }
                    if (groupFenceIds.length === 0) {
                        return;
                    }
                    _.each(groupFenceIds, function (fenceId, index, list) {
                        // don't include in bounds if it's not visible
                        if (_.indexOf(tracking.data.visible.fences, fenceId) === -1) {
                            return;
                        }

                        var fenceMarkers = _.filter(tracking.data.fenceMarkers, function (item) { return item.data.fenceId == fenceId; });
                        _.each(fenceMarkers, function (fenceMarker) {
                            bounds.extend(fenceMarker.getBounds());
                        })
                    });
                    break;
                case 'places':
                    var groupPlaceIds = [];
                    if (groupId == 'all-places') {
                        groupPlaceIds = _.map(tracking.data.places, 'Id');
                    } else {
                        // todo: support place groups
                        var group = findGroupById(groupId);
                        if (group !== null) {
                            //groupPlaceIds = findPlaceIdsUnderGroup(group);
                        }
                    }
                    if (groupPlaceIds.length === 0) {
                        return;
                    }

                    var visiblePlaces = _.filter(tracking.data.places, function (place) {
                        return _.indexOf(tracking.data.visible.places, place.Id) !== -1;
                    });
                    _.each(visiblePlaces, function (place) {
                        if (place.Location.marker !== undefined) {
                            bounds.extend(place.Location.marker.getLatLng());
                        }
                    });
                    break;
                default:
                    console.log('no group handler for: ' + groupType);
                    break;
            }

            // setmap bounds to bounds
            if (bounds.isValid()) {
                tracking.map.fitBounds(bounds, { padding: [10, 10] });
                if (tracking.map.getZoom() > tracking.options.maximumZoom) {
                    tracking.map.setZoom(tracking.options.maximumZoom);
                }
            }
        });

        assetsContainer.on('click', 'svg.group-toggle,.item-count', function (e) {
            e.preventDefault();
            var groupId = this.parentNode.parentNode.parentNode.getAttribute('id').substring(6);
            toggleGroupExpanded(groupId);
        });

        $(document).on('click', 'a.add-first-item', function (e) {
            e.preventDefault();
            var itemType = this.getAttribute('data-item-type');
            switch (itemType) {
                case 'shared-views':
                    openSharedViewDialog(null);
                    break;
                default:
                    console.warn('No add first item handler for: ' + itemType);
                    break;
            }
        });
        // end init

        $('#nav-primary a').bsTooltip({ delay: { show: 500, hide: 100 } });
        $('#map-tools button').bsTooltip({ delay: { show: 500, hide: 100 }, placement: 'left' });
        $('#panel-content-journeys').bsTooltip({ selector: '.notifications', placement: 'right' });
        $('#panel-content-assets').bsTooltip({ selector: '.notifications', placement: 'right' });
        $('#shared-view-banner a').bsTooltip({ placement: 'top' });
        $('#panel-secondary-nav-tabs a').bsTooltip({ delay: { show: 500, hide: 100 } });

        initializeMouseTooltip();
        tracking.log('Initialize Map.');
        tracking.initMap();
    };

    tracking.toggleMaritime = function () {
        toggleOverlay(maritimeOverlay);
    };

    tracking.toggleSeamap = function () {
        toggleOverlay(seamapOverlay);
    };

    tracking.toggleOil = function () {
        toggleOverlay(oilOverlay);
    };

    var Conv=({
        toMercator: function(lat, lng) {
            var x = lng * 20037508.34 / 180;
            var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
            y = y * 20037508.34 / 180;
            return L.point(x,y);
        },
        pointToLatLng: function(x, y) {
            var lng = (x/20037508.34) * 180;
            var lat = (y / 20037508.34) * 180;
            lat = 180/Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2);
            return L.latLng(lat,lng);
        }
    });

    tracking.tileToBbox = function (tile) {
        var mapSize = Math.pow(2, tile.z);
        var west = ((tile.x * 360) / mapSize) - 180;
        var east = (((tile.x + 1) * 360) / mapSize) - 180;

        var efactor = Math.exp((0.5 - tile.y / mapSize) * 4 * Math.PI);
        var north = (Math.asin((efactor - 1) / (efactor + 1))) * (180 / Math.PI);

        efactor = Math.exp((0.5 - (tile.y + 1) / mapSize) * 4 * Math.PI);
        var south = (Math.asin((efactor - 1) / (efactor + 1))) * (180 / Math.PI);
        var rect = L.latLngBounds(L.latLng(north, west), L.latLng(south, east));
        return rect;
    };

    tracking.throttles.tilesLoaded = _.throttle(function () {
        if (tracking.data.tileLoadedQueue.length == 0) {
            return;
        }
        var data = {
            Uid: tracking.user.id,
            Loads: tracking.data.tileLoadedQueue
        };
        tracking.data.tileLoadedQueue = [];
        $.ajax({
            type: 'POST',
            url: wrapUrl('/api/ui/tls'),
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(data),
            dataType: 'json'
        });
    }, 3000, { leading: false });

    tracking.initMap = function () {
        // map initialization
        tracking.state.bounds = L.latLngBounds();

        var tileLoaded = function (layer) {
            return function (evt) {
                var coords = evt.coords;
                var data = {
                    //Uid: tracking.user.id,
                    Type: layer,
                    Z: coords.z,
                    X: coords.x,
                    Y: coords.y
                };
                tracking.data.tileLoadedQueue.push(data);
                // add track to queue and throttle/debounce
                tracking.throttles.tilesLoaded();
            }
        };

        // todo: remove as it does not exist anymore
        moroccoOverlay = L.tileLayer('http://api.navcities.com/tilecache/tilecache.cgi/1.0.0/navcities31v2/{z}/{x}/{y}.png?type=google', { zIndex: 2 });

        seamapOverlay = L.tileLayer('https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png?type=google', { maxZoom: 18, attribution: 'OpenSeaMap', zIndex: 4 });

        oilOverlay = L.tileLayer('https://gis.boem.gov/arcgis/rest/services/BOEM_BSEE/MMC_Layers/MapServer/export?f=image&dpi=96&bboxSR=102100&imageSR=102100&format=png8&transparent=true&bbox={bbox}&size=256,256&layers=show:7,8,15,11,1,0',
            {
                bbox: function (tile) {
                    var rect = TileToLocationRect(tile);
                    var mbb1 = Conv.toMercator(rect.getSouth(), rect.getWest());
                    var mbb2 = Conv.toMercator(rect.getNorth(), rect.getEast());
                    return mbb1.x + ',' + mbb1.y + ',' + mbb2.x + ',' + mbb2.y;
                },
                zIndex: 4
            });

        maritimeOverlay = L.tileLayer('//gis.charttools.noaa.gov/arcgis/rest/services/MCS/ENCOnline/MapServer/exts/MaritimeChartService/MapServer/export?dpi=96&transparent=true&format=png8&bbox={bbox}&bboxSR=3857&size=256%2C256&f=image&layers=show:2,3,4,5,6,7',
            {
                bbox: function (tile) {
                    var rect = TileToLocationRect(tile);
                    var mbb1 = Conv.toMercator(rect.getSouth(), rect.getWest());
                    var mbb2 = Conv.toMercator(rect.getNorth(), rect.getEast());
                    return mbb1.x + ',' + mbb1.y + ',' + mbb2.x + ',' + mbb2.y;
                },
                zIndex: 4
            });

        radarAustraliaOverlay = L.tileLayer('/services/layer.ashx?type=radar-aus&bbox={bbox}&rnd={time}',
            {
                time: function (tile) {
                    return (new Date()).getTime();
                },
                base: function (tile) {
                    return tracking.data.radarAustralia.basetime;
                },
                issue: function (tile) {
                    return tracking.data.radarAustralia.issuetime;
                },
                bbox: function (tile) {
                    var rect = TileToLocationRect(tile);
                    //var mbb1 = Conv.toMercator(rect.getSouth(), rect.getWest());
                    //var mbb2 = Conv.toMercator(rect.getNorth(), rect.getEast());
                    return rect.getWest() + ',' + rect.getSouth() + ',' + rect.getEast() + ',' + rect.getNorth();
                },
                bounds: L.latLngBounds([-7.740789, 108.318991], [-44.235258, 154.901023]),
                zIndex: 5,
                minZoom: 3
            });

        radarOverlay = L.tileLayer('https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-900913/{z}/{x}/{y}.png?{time}',
            {
                time: function (tile) {
                    return (new Date()).getTime();
                },
                bounds: L.latLngBounds([71.48, -174.25], [17.0, -51.42]),
                zIndex: 5
            });

        worldCloudOverlay = L.tileLayer('https://earthlive.maptiles.arcgis.com/arcgis/rest/services/GOES/GOES31C/MapServer/tile/{z}/{y}/{x}?{time}',
            {
                time: function (tile) {
                    return (new Date()).getTime();
                },
                zIndex: 4,
                opacity: 0.4
            });

        baseLayerOpenStreetMap = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',
            {
                maxZoom: 18
            }
        );

        hemaOverlay = L.tileLayer('http://skippy.hema-labs.com/AUS/ExplorerMap_v1_2/{z}/{y}/{x}.png',
            {
                minZoom: 5,
                maxZoom: 14,
                bounds: L.latLngBounds([10, 95], [-50, 180]),
                zIndex: 2
            }
        );

        bethelOverlay = L.tileLayer('https://{s}.tiles.mapbox.com/v4/digitalglobe.nal0mpda/{z}/{x}/{y}.jpg?access_token={accessToken}',
            {
                minZoom: 13,
                maxZoom: 18,
                bounds: L.latLngBounds([60.816316, -161.896179], [60.762626, -161.719694]),
                accessToken: tracking.options.keys.digitalGlobe,
                zIndex: 2
            }
        );

        sentinel2SatelliteOverlay = L.tileLayer.wms("https://{s}.tiles.maps.eox.at/wms?",
            {
                subdomains: ['a', 'b', 'c', 'd', 'e'],
                layers: 's2cloudless',
                format: 'image/jpeg',
                crs: L.CRS.EPSG4326,
                attribution: "Sentinel-2 cloudless � https://s2maps.eu by EOX IT Services GmbH (Contains modified Copernicus Sentinel data 2016 & 2017)",
                zIndex: 2
            }
        );
        hemaOverlay.on('tileload', tileLoaded('hema'));
        bethelOverlay.on('tileload', tileLoaded('bethel'));
        moroccoOverlay.on('tileload', tileLoaded('morocco'));
        sentinel2SatelliteOverlay.on('tileload', tileLoaded('satellitealt'));
        radarOverlay.on('tileload', tileLoaded('radar'));
        maritimeOverlay.on('tileload', tileLoaded('maritime'));
        oilOverlay.on('tileload', tileLoaded('oil'));
        baseTileLayers = [hemaOverlay, bethelOverlay, moroccoOverlay, sentinel2SatelliteOverlay];

        var modifyMapBoxUrl = function (url) {
            return url.replace('{$z}', '{z}')
                .replace('{$x}', '{x}')
                .replace('{$y}', '{y}')
                .replace('/256/', '/512/')
                .replace('api.mapbox.com', '{s}.tiles.mapbox.com')
                .replace('a.tiles.mapquest.com', '{s}.tiles.mapquest.com');
        }

        for (var baseLayer in tracking.options.baseLayers) {
            var item = tracking.options.baseLayers[baseLayer];
            if (item.arcgis !== null && item.arcgis !== '' && tracking.options.keys.arcGis !== '') {
                item.layer = L.esri.Vector.vectorBasemapLayer(item.arcgis, { apiKey: tracking.options.keys.arcGis });
            } else if (item.isVector === true) {
                item.layer = L.maplibreGL({ style: item.url });
            } else {
                switch (baseLayer) {
                    case 'road':
                        item.url = typeof MBMAP_OPEN === 'undefined' ? item.url : modifyMapBoxUrl(MBMAP_OPEN);
                        break;
                    case 'roadlight':
                        item.url = typeof MBLIGHT_OPEN === 'undefined' ? item.url : modifyMapBoxUrl(MBLIGHT_OPEN);
                        break;
                    case 'roaddark':
                        item.url = typeof MBDARK_OPEN === 'undefined' ? item.url : modifyMapBoxUrl(MBDARK_OPEN);
                        break;
                }
                item.layer = L.tileLayer(item.url,
                {
                    subdomains: ((item.subdomains != null) ? item.subdomains : ['a', 'b', 'c', 'd']),
                    minZoom: 1, // hmm
                    maxZoom: item.maxZoom,
                    zIndex: (baseLayer == "satellitelabels") ? 3 : 2,
                    tileSize: item.tileSize,
                    zoomOffset: item.tileSize == 512 ? -1 : 0,
                    opacity: 0.9999,
                    subdomains: item.subdomains !== '' ? item.subdomains : 'abc',
                });
                item.layer.on('tileload', tileLoaded(baseLayer));
            }
            baseTileLayers.push(item.layer);
        }

        if (tracking.preferences.PREFERENCE_REMOVE_ROADS || tracking.preferences.PREFERENCE_MOROCCO_OVERLAY) {
            tracking.data.isSatelliteLabelOverlayEnabled = false;
        }
        var mapOptions = {
            layers: [tracking.options.baseLayers.road.layer],
            closePopupOnClick: false, // test
            attributionControl: false,
            zoomControl: false,
            preferCanvas: true, // todo: revisit rendering performance
            worldCopyJump: true, // test
            maxBounds: tracking.options.maxBounds,
            maxBoundsViscosity: 1,
            minZoom: tracking.options.minimumZoom
        };

        tracking.map = L.map('map', mapOptions).on('load', tracking.mapLoaded);
        tracking.map.setView([0, 0], tracking.options.minimumZoom);
        L.control.scale({ position: 'bottomright' }).addTo(tracking.map);
        tracking.data.previousZoom = tracking.options.minimumZoom;
        tracking.directionsService = L.Routing.control({
            router: new L.Routing.OSRMv1({
                serviceUrl: '/api/ui/getroute',
                language: getOSRMLanguage(tracking.language)
            }),
            formatter: new L.Routing.Formatter({
                language: getOSRMLanguage(tracking.language),
                units: (tracking.preferences.PREFERENCE_SPEED === 1 ? 'metric' : 'imperial')
            }),
            //autoRoute: false, // todo: revisit this
            useZoomParameter: true
        })
        .on('routingstart', function (e) {
            console.log('routing start');
            console.log(e);
        })
        .on('routesfound', function (e) {
            console.log('routes found');
            console.log(e);
        })
        .on('routingerror', function(e) {
            console.log('routing error!');
            console.log(e);
        })
        .on('routeselected', function (e) {
            console.log('route selected');
            console.log(e);
            if (e.route != null) {
                tracking.data.routeLine = L.polyline(e.route.coordinates);
                if (tracking.options.enabledFeatures.indexOf('UI_GEOFENCE_FROM_ROUTE') !== -1) {
                    $('#PlaceRouteGeofence,#RouteGeofence,#RouteGeofenceCreate').removeClass('disabled');
                }
            }
        });
        tracking.directionsService.control = tracking.directionsService.onAdd(tracking.map);
    };

    tracking.mapLoaded = function () {
        tracking.log('Map loaded.');
        tracking.data.mapLayers.positions = L.layerGroup();
        tracking.data.mapLayers.fences = L.layerGroup();
        tracking.data.mapLayers.places = L.layerGroup();
        tracking.data.mapLayers.other = L.layerGroup();
        tracking.data.mapLayers.waypoints = L.layerGroup();
        tracking.data.mapLayers.positions.setZIndex(9000);
        tracking.data.mapLayers.fences.setZIndex(6000);
        tracking.data.mapLayers.places.setZIndex(7000);
        tracking.data.mapLayers.other.setZIndex(8000);
        tracking.data.mapLayers.waypoints.setZIndex(8000);
        tracking.map.addLayer(tracking.data.mapLayers.positions);
        tracking.map.addLayer(tracking.data.mapLayers.fences);
        tracking.map.addLayer(tracking.data.mapLayers.places);
        tracking.map.addLayer(tracking.data.mapLayers.other);
        tracking.map.addLayer(tracking.data.mapLayers.waypoints);
        tracking.map.addLayer(tracking.data.mapLayers.normal);

        changeMapType(tracking.options.defaultMapType.toLowerCase());

        if ((Cookies.get('radar') !== undefined) || ($j.inArray('radar', tracking.options.enableLayers) !== -1)) {
            enableRadar();
        }

        if ((Cookies.get('radar-australia') !== undefined) || ($j.inArray('radar-australia', tracking.options.enableLayers) !== -1)) {
            //enableRadarAustralia(false, enableRadarAustraliaActive);
            enableRadarAustraliaActive();
        }

        if ((Cookies.get('worldIR') !== undefined) || ($j.inArray('worldIR', tracking.options.enableLayers) !== -1)) {
            enableWorldIR();
        }

        if ((Cookies.get('layer-oil') !== undefined) || ($j.inArray('oil', tracking.options.enableLayers) !== -1)) {
            enableLayerOil();
        }

        if ((Cookies.get('layer-maritime') !== undefined) || ($j.inArray('maritime', tracking.options.enableLayers) !== -1)) {
            enableLayerMaritime();
        }

        if ((Cookies.get('layer-iridium') !== undefined) || ($j.inArray('iridium', tracking.options.enableLayers) !== -1)) {
            enableLayerIridium();
        }

        if ((Cookies.get('layer-iridiumnext') !== undefined) || ($j.inArray('iridiumnext', tracking.options.enableLayers) !== -1)) {
            enableLayerIridiumNext();
        }

        if ((Cookies.get('layer-globalstar') !== undefined) || ($j.inArray('globalstar', tracking.options.enableLayers) !== -1)) {
            enableLayerGlobalstar();
        }

        if ((Cookies.get('layer-inmarsat') !== undefined) || ($j.inArray('inmarsat', tracking.options.enableLayers) !== -1)) {
            enableLayerInmarsat();
        }

        if ((Cookies.get('layer-orbcomm') !== undefined) || ($j.inArray('orbcomm', tracking.options.enableLayers) !== -1)) {
            enableLayerOrbcomm();
        }

        if ((Cookies.get('layer-geostationary') !== undefined) || ($j.inArray('geostationary', tracking.options.enableLayers) !== -1)) {
            enableLayerGeostationary();
        }

        if ((Cookies.get('layer-traffic') !== undefined) || ($j.inArray('traffic', tracking.options.enableLayers) !== -1)) {
            enableLayerTraffic();
        }

        if ((Cookies.get('layer-weather') !== undefined) || ($j.inArray('weather', tracking.options.enableLayers) !== -1)) {
            enableLayerWeather(false);
        }

        if ((Cookies.get('layer-clouds') !== undefined) || ($j.inArray('clouds', tracking.options.enableLayers) !== -1)) {
            enableLayerClouds();
        }

        if (Cookies.get('layer-custom') !== undefined) {
            enableCustomLayers(Cookies.get('layer-custom'));
        }

        // enable custom layers that are enabled by default
        for (var i = 0; i < tracking.options.customLayers.length; i++) {
            var layer = tracking.options.customLayers[i];
            if (layer.enabled === true) {
                enableCustomLayers(layer.id.toString());
            }
        }

        if (!tracking.options.disablePanels) {
            if (Cookies.get('eventsPanel') !== undefined) {
                $j('#event-panel-container').toggle();
                $j('#map_panels').hide();
            }
        }

        tracking.map.on('zoomend', checkWeatherForecast);
        tracking.map.on('viewreset', function (e) {  });
        tracking.map.on('zoomend', function (e) {
            var nowZoom = tracking.map.getZoom();
            if (nowZoom != tracking.data.previousZoom) {
                console.log('zoom changed to ' + tracking.map.getZoom());
            }
            tracking.data.previousZoom = nowZoom;
        });
        tracking.map.on('click', function (event) {
            console.log('map click');
            console.log(event);
            updateChosenLocation(event.latlng);
        });

        MapToolbar.buttons.$shape = $j('.tool-shape').get(0); //document.getElementById("shape_b");
        //MapToolbar.buttons.$line = $j('.tool-line').get(0); //document.getElementById("line_b");
        MapToolbar.buttons.$circle = $j('.tool-circle').get(0); //document.getElementById("circle_b");

        // map click event for polyline and polygon (adding/deleting points/segments)
        MapToolbar.polyClickEvent = tracking.map.on('click', function (event) {
            //console.log(MapToolbar.currentFeature);
            if (!MapToolbar.isSelected(MapToolbar.buttons.$shape)
                && !MapToolbar.isSelected(MapToolbar.buttons.$line)
                && !MapToolbar.isSelected(MapToolbar.buttons.$circle))
                return;
            if (MapToolbar.currentFeature) {
                MapToolbar.addPoint(event.latlng, MapToolbar.currentFeature);
            }
        });

        // TODO handle url actions better
        var preventPanelChange = false;
        var postLoadAction = null;
        var keepPrimaryPanelHidden = window.screen.width <= 768;
        if (window.location.hash !== '') {
            preventPanelChange = true;
            var hash = window.location.hash;
            if (hash.substring(0, 1) === '#') {
                hash = hash.substring(1);
            }
            var hashItems = hash.split('/');
            var activePanel = hash;
            if (hashItems.length > 1) {
                tracking.options.defaultMode = hashItems[0];
                activePanel = hashItems[1];
                if (hashItems.length > 2) {
                    postLoadAction = hashItems[2];
                }
            }
            switch (activePanel) {
                case 'journeys':
                    changeActivePanel(tracking.panels.JOURNEYS, keepPrimaryPanelHidden);
                    break;
                case 'places':
                    changeActivePanel(tracking.panels.PLACES, keepPrimaryPanelHidden);
                    break;
                case 'fences':
                    changeActivePanel(tracking.panels.GEOFENCES, keepPrimaryPanelHidden);
                    break;
                case 'shared-views':
                    changeActivePanel(tracking.panels.SHARED_VIEWS, keepPrimaryPanelHidden);
                    break;
                case 'live':
                case 'history':
                case 'assets':
                    changeActivePanel(tracking.panels.ASSETS, keepPrimaryPanelHidden);
                    break;
                //case '#panel-live':
                //    tracking.options.defaultMode = 'live';
                //    changeActivePanel(tracking.panels.LIVE);
                //    break;                
                //case '#panel-history':
                //    tracking.options.defaultMode = 'history';
                //    changeActivePanel(tracking.panels.HISTORY);
                //    break;
                case 'action-add-asset':
                case 'action-add-group':
                case 'action-add-fence':
                case 'action-add-place':
                case 'action-add-shared-view':
                    preventPanelChange = false;
                    postLoadAction = activePanel;
                    break;
                default:
                    preventPanelChange = false;
                    break;
            }
        }

        $.when(tracking.state.promises.getGroupsAndAssets, tracking.state.promises.getAssetUsers).done(function () {
            populateSidePanel();
            createGroupColorStyles();
            indexAssetsForSearch();
            indexAssetGroupsForSearch();
            indexPlacesForSearch();
            indexFencesForSearch();
            indexSharedViewsForSearch();
            sortModeUpdated();

            $('#loading-groups').removeClass('is-visible');

            // add places and geofences to the map
            // assets will be added to the map via their live/historical positions
            _.each(tracking.data.places, function (place, index, list) {
                var point = L.latLng(place.Location.Lat, place.Location.Lng);
                addPlaceMarker(point, place.Location, place);
            });

            addFencePaths();

            // add waypoints to map/UI
            _.each(tracking.data.waypoints, function (waypoint) {
                addOrUpdateWaypoint(waypoint);
                var point = L.latLng(waypoint.Location.Lat, waypoint.Location.Lng);
                addWaypointMarker(point, waypoint.Location, waypoint);
            });
            updateWaypointListing();

            // update asset/place/geofence custom attributes
            populateCustomAttributesEditable(document.getElementById('edit-asset-extra-accordion'), 0, 'EditAssetAttribute');
            populateCustomAttributesEditable(document.getElementById('edit-geofence-extra-accordion'), 1, 'EditGeofenceAttribute');
            populateCustomAttributesEditable(document.getElementById('edit-place-attributes-accordion'), 2, 'EditPlaceAttribute');

            // query positions
            // make live view the default unless otherwise specified

            var deferreds = [];

            if ((tracking.user.isAnonymous
                && tracking.options.showAssetId == ''
                && tracking.options.showAssetUniqueId == ''
                && tracking.options.defaultMode !== 'live')
                || tracking.options.defaultMode === 'history') {
                // anonymous users not highlighting an asset start with history view
                // as well as anyone with the history default mode preference

                deferreds.push(switchMapMode(false, null, true)); // promise

                // automatically requery the history for embedded views
                // todo: this will instead be follow asset mode with a specific asset and date range?
                if (tracking.user.isAnonymous) {
                    tracking.intervals.history = setInterval(requeryHistory, 1800000);
                }
            } else if (tracking.options.showSharedViewId !== null) {
                //changeActivePanel(tracking.panels.SHARED_VIEWS, keepPrimaryPanelHidden);
                //var sharedView = findSharedViewById(tracking.options.showSharedViewId);
                //selectSharedView(sharedView);
                //preventPanelChange = true;
                //postLoadAction = null;
                //// we always have to choose a map mode for normal view
                //var isSharedViewMapModeLive = sharedView.Preferences.DefaultMode === tracking.mapModes.LIVE;
                //switchMapMode(isSharedViewMapModeLive, null, !isSharedViewMapModeLive, preventPanelChange);
            } else if (tracking.options.showPositionId != '') {
                // loading a history view to highlight a specific position
                if (tracking.options.showAssetId != '') {
                    // make all other assets inactive
                    for (var i = 0; i < tracking.data.assets.length; i++) {
                        var makeActive = tracking.data.assets[i].Id == tracking.options.showAssetId;
                        toggleAssetActive(tracking.data.assets[i].Id, makeActive, false);
                    }

                    _.each(tracking.data.groups, function (group) {
                        updateGroupVisibilityStatus(group.Id);
                    });
                    updateGroupVisibilityStatus('all-assets');
                    updateActiveAssetInformation();
                }
                deferreds.push(switchMapMode(false, null, true)); // promise
            } else {
                deferreds.push(switchMapMode(true, null, false)); // promise
            }

            if (!preventPanelChange) {
                changeActivePanel(tracking.state.activePanel, keepPrimaryPanelHidden);
            }

            deferreds = _.union(deferreds, queryVisibleTrips());

            // TODO handle url actions better
            $.when.apply($, deferreds).done(function () {
                if (tracking.options.isCompact) {
                    // this need to be done after all data load or history view change needs to check for first load
                    tracking.data.domNodes.mapTools.container.classList.add('is-closed');
                    tracking.data.domNodes.mapMode.container.classList.add('is-minimized');
                    tracking.data.domNodes.mapMode.dateRange.classList.remove('is-visible');
                }

                if (postLoadAction !== null) {
                    switch (postLoadAction) {
                        case 'add':
                            switch (tracking.state.activePanel) {
                                case tracking.panels.ASSETS:
                                    openEditAssetDialog(null);
                                    setUrlHistoryForActivePanel('add');
                                    break;
                                case tracking.panels.FENCES:
                                    openGeofenceDialog(null);
                                    setUrlHistoryForActivePanel('add');
                                    break;
                                case tracking.panels.PLACES:
                                    openPlaceDialog(null);
                                    setUrlHistoryForActivePanel('add');
                                    break;
                                case tracking.panels.JOURNEYS:
                                    break;
                            }
                            break;
                        case 'add-group':
                            openAssetGroupDialog(null);
                            setUrlHistoryForActivePanel('add-group');
                            break;
                        case 'action-add-asset':
                            openEditAssetDialog(null);
                            setUrlHistoryForActivePanel('add');
                            break;
                        case 'action-add-group':
                            openAssetGroupDialog(null);
                            setUrlHistoryForActivePanel('add-group');
                            break;
                        case 'action-add-fence':
                            openGeofenceDialog(null);
                            setUrlHistoryForActivePanel('add');
                            break;
                        case 'action-add-place':
                            openPlaceDialog(null);
                            setUrlHistoryForActivePanel('add');
                            break;
                        case 'action-add-shared-view':
                            openSharedViewDialog(null);
                            setUrlHistoryForActivePanel('add');
                            break;
                    }
                }

                tracking.state.isFirstLoad = false;

                $.ajax({
                    type: 'POST',
                    url: wrapUrl('/api/ui/ml'),
                    contentType: 'application/json; charset=utf-8',
                    data: JSON.stringify({
                        Uid: tracking.user.id
                    }),
                    dataType: 'json'
                });
            });

            queryLatestNotifications();

            //queryUsersForAssets();

            tracking.data.alerts = [];
            queryAlertsRequiringAcknowledgement(null);
            initAlerts();

            setupSignalR();

            for (var i = 0; i < tracking.data.systemNotifications.length; i++) {
                showSystemNotification(tracking.data.systemNotifications[i]);
            }

            tracking.state.promises.getGroupsAndAssets = null;
            tracking.state.promises.getAssetUsers = null;

            if (tracking.user.showWalkthrough) {
                tracking.walkthrough.intro.start();
            }
        });
    };

    tracking.showData = function() {
        //console.log(data);
        console.log(this.data);
        console.log(tracking.data);
    };

    tracking.resizeApp = resizeApp;

    tracking.findAssetById = findAssetById;

    tracking.findGroupById = findGroupById;
    tracking.findTripById = findTripById;

    tracking.playbackClick = playbackClick;

    tracking.playbackPrevPosition = playbackPrevPosition;

    tracking.playbackFirstPosition = playbackFirstPosition;

    tracking.playbackLastPosition = playbackLastPosition;

    tracking.playbackNextPosition = playbackNextPosition;

    tracking.sortAssetGroups = sortAssetGroups;

    function initializeDialogs() {

    }

    function itemClickedInPrimaryPanel(item, itemType) {
        if (!tracking.state.openPanels.secondary) {
            return;
        }
        var isHandled = false;
        var panel = tracking.data.domNodes.panels.secondary;
        var panelGroup = panel.getAttribute('data-group-for');
        if (panelGroup === 'dialog') {
            var selector = document.getElementById('panel-secondary-header');
            var dialogGroup = selector.getAttribute('data-group-for');
            if (dialogGroup === itemType) {
                var dialogItems = selector.querySelector('#panel-secondary-item-options')
                var dialogItem = dialogItems.querySelector('option[value="' + item.Id + '"]');
                if (dialogItem !== null && dialogItem.disabled === false) {
                    var callback = tracking.data.domNodes.panels.secondary.nextItemCallback;
                    if (callback !== undefined && callback !== null) {
                        callback(item);
                        isHandled = true;
                    }
                }
            }
        }
        if (!isHandled) {
            switch (itemType) {
                case 'assets':
                    openAssetSettingsPanel(item);
                    break;
                case 'groups':
                    openAssetGroupSettingsPanel(item);
                    break;
                case 'fences':
                    openFenceSettingsPanel(item);
                    break;
                case 'places':
                    openPlaceSettingsPanel(item);
                    break;
                case 'trips':
                    break;
                case 'shared-views':
                    openSharedViewSettingsPanel(item);
                    break;
                default:
                    console.warn('No default item action for: ' + itemType);
                    break;
            }
        }
    }

    function handleNotificationItemAction(action, actionGroup, id) {
        switch (actionGroup) {
            case 'assets':
                var asset = findAssetById(id);
                switch (action) {
                    case 'asset-positions':
                    case 'positions':
                        openPositionsForAsset(asset);
                        break;
                    case 'asset-alerts':
                    case 'alerts':
                        openAlertsForAsset(asset);
                        break;
                    case 'asset-events':
                    case 'events':
                        openEventsForAsset(asset);
                        break;
                    case 'asset-status':
                    case 'status':
                        openStatusForAsset(asset);
                        break;
                    case 'asset-messages':
                    case 'messages':
                        openMessagesForAsset(asset);
                        break;
                    case 'asset-chat':
                    case 'chat':
                        openChatForAsset(asset);
                        break;
                    case 'options':
                        openAssetSettingsPanel(asset);
                        break;
                    case 'asset-activity':
                    case 'activity':
                        openActivityForAsset(asset);
                        break;
                    default:
                        console.warn('No notification handler for: ' + actionGroup + ', ' + action);
                        break;
                }
                break;
            case 'groups':
                var group = findGroupById(id);
                switch (action) {
                    case 'group-positions':
                    case 'positions':
                        openPositionsForGroup(group);
                        break;
                    case 'group-alerts':
                    case 'alerts':
                        openAlertsForGroup(group);
                        break;
                    case 'group-events':
                    case 'events':
                        openEventsForGroup(group);
                        break;
                    case 'group-status':
                    case 'status':
                        openStatusForGroup(group);
                        break;
                    case 'group-messages':
                    case 'messages':
                        openMessagesForGroup(group);
                        break;
                    case 'group-chat':
                    case 'chat':
                        openChatForGroup(group);
                        break;
                    case 'options':
                        openAssetGroupSettingsPanel(group);
                        break;
                    case 'group-activity':
                    case 'activity':
                        openActivityForGroup(group);
                        break;
                    default:
                        console.warn('No notification handler for: ' + actionGroup + ', ' + action);
                        break;
                }
                break;
            case 'trips':
                var trip = findTripById(id);
                switch (action) {
                    case 'asset-positions':
                    case 'positions':
                        openPositionsForTrip(trip);
                        break;
                    case 'asset-alerts':
                    case 'alerts':
                        openAlertsForTrip(trip);
                        break;
                    case 'asset-events':
                    case 'events':
                        openEventsForTrip(trip);
                        break;
                    case 'asset-status':
                    case 'status':
                        openStatusForTrip(trip);
                        break;
                    case 'asset-messages':
                    case 'messages':
                        openMessagesForTrip(trip);
                        break;
                    case 'asset-chat':
                    case 'chat':
                        openChatForTrip(trip);
                        break;
                    case 'options':
                        openTripSettingsPanel(trip);
                        break;
                    case 'activity':
                    case 'asset-activity':
                        openActivityForTrip(trip);
                        break;
                    default:
                        console.warn('No notification handler for: ' + actionGroup + ', ' + action);
                        break;
                }
                break;
            case 'journeys':
                var journey = findJourneyById(id);
                switch (action) {
                    case 'asset-positions':
                    case 'positions':
                        openPositionsForJourney(journey);
                        break;
                    case 'asset-alerts':
                    case 'alerts':
                        openAlertsForJourney(journey);
                        break;
                    case 'asset-events':
                    case 'events':
                        openEventsForJourney(journey);
                        break;
                    case 'asset-status':
                    case 'status':
                        openStatusForJourney(journey);
                        break;
                    case 'asset-messages':
                    case 'messages':
                        openMessagesForJourney(journey);
                        break;
                    case 'asset-chat':
                    case 'chat':
                        openChatForJourney(journey);
                        break;
                    case 'options':
                        openJourneySettingsPanel(journey);
                        break;
                    case 'asset-activity':
                    case 'activity':
                        openActivityForJourney(journey);
                        break;
                    default:
                        console.warn('No notification handler for: ' + actionGroup + ', ' + action);
                        break;
                }
                break;
            case 'shared-views':
                var sharedView = findSharedViewById(id);
                switch (action) {
                    case 'positions':
                        openPositionsForSharedView(sharedView);
                        break;
                    //case 'alerts':
                    //    openAlertsForSharedView(sharedView);
                    //    break;
                    case 'options':
                        openSharedViewSettingsPanel(sharedView);
                        break;
                    case 'status':
                        openStatusForSharedView(sharedView);
                        break;
                    case 'events':
                        openEventsForSharedView(sharedView);
                        break;
                    // alerts
                    // messages
                    case 'chat':
                        openChatForSharedView(sharedView);
                        break;
                    case 'activity':
                        openActivityForSharedView(sharedView);
                        break;
                    default:
                        console.warn('No notification handler for: ' + actionGroup + ', ' + action);
                        break;
                }
                break;
            default:
                console.warn('No notification handler for: ' + actionGroup + ', ' + action);
                break;
        }
    }

    function handleSecondaryPanelItemAction(action, actionGroup, id, e) {
        var preventDefault = true;
        if (actionGroup === 'dialog') {
            actionGroup = tracking.data.domNodes.panels.secondary.getAttribute('data-item-type');
        }
        switch (actionGroup) {
            case 'assets':
                var asset = findAssetById(id);
                switch (action) {
                    case 'alerts':
                        break;
                    case 'status':
                        break;
                    case 'positions':
                        break;
                    case 'events':
                        break;
                    case 'messages':
                        break;
                    case 'route-asset':
                        openPlaceRoutingForAsset(asset);
                        break;
                    case 'current-position':
                        closeSecondaryPanel();
                        openAssetLatestPosition(asset);
                        break;
                    case 'follow-asset':
                        closeSecondaryPanel();
                        toggleFollowAsset(asset);
                        break;
                    case 'history-replay':
                        closeSecondaryPanel();
                        loadHistory([asset.Id], true);
                        break;
                    case 'send-waypoint':
                        openSendWaypointDialog(asset);
                        break;
                    case 'send-message':
                        openSendMessageToAssetDialog(asset);
                        break;
                    case 'set-waypoint':
                        openSetWaypointDialog(asset);
                        break;
                    case 'add-position':
                        openAddPositionDialog(asset);
                        break;
                    case 'send-command':
                        openSendCommandDialog(asset);
                        break;
                    case 'set-output':
                        openSetOutputDialog(asset);
                        break;
                    case 'set-driver':
                        openCurrentDriversDialog(asset);
                        break;
                    case 'view-logs':
                        var logItems = document.getElementById('asset-function-log-container');
                        var logToggle = document.getElementById('asset-function-view-logs');
                        if (logItems.classList.contains('is-visible')) {
                            logToggle.classList.remove('expanded');
                            logItems.classList.remove('is-visible');
                        } else {
                            logItems.classList.add('is-visible');
                            logToggle.classList.add('expanded');
                        }
                        tracking.data.domNodes.simpleBars.secondary.recalculate();
                        break;
                    case 'view-logs-message':
                        loadMessageHistoryDialog(asset);
                        break;
                    case 'view-logs-io':
                        loadIOHistoryDialog(asset);
                        break;
                    case 'view-logs-driver':
                        loadDriverHistoryDialog(asset);
                        break;
                    case 'view-logs-waypoint':
                        loadWaypointHistoryDialog(asset);
                        break;
                    case 'view-logs-refuel':
                        loadFillupHistoryDialog(asset);
                        break;
                    case 'view-logs-service-meters':
                        loadServiceMeterHistoryDialog(asset);
                        break;
                    case 'view-logs-garmin-forms':
                        loadGarminFormsHistoryDialog(asset);
                        break;
                    case 'edit-asset':
                        openEditAssetDialog(asset);
                        break;
                    case 'delete-asset':
                        var deleteAssetModal = $('#delete-asset-modal');
                        $('.modal-body', deleteAssetModal).text(tracking.strings.MSG_DELETE_ASSET_CONFIRM.replace('{0}', asset.Name));
                        $j('#hfDeleteAssetId').val(asset.Id);
                        deleteAssetModal.modal('show');
                        break;
                    default:
                        console.warn('Unhandled asset action: ' + action);
                        break;
                }
                break;
            case 'fences':
                var fence = findFenceById(id);
                switch (action) {
                    case 'send-message':
                        openSendMessageDialog(null, null, fence);
                        break;
                    case 'history-replay':
                        var assetIds = findAssetIdsInGeofence(fence);
                        loadHistory(assetIds);
                        break;
                    case 'group-assets':
                        var assetIds = findAssetIdsInGeofence(fence);
                        openAssetGroupDialog(null);
                        var assets = new Array();
                        for (var i = 0; i < assetIds.length; i++) {
                            var asset = findAssetById(assetIds[i]);
                            if (asset == null) {
                                continue;
                            }
                            assets.push(asset);
                            $('#edit-asset-group-assets-list input[value=' + asset.Id + ']').prop('checked', true);
                        }
                        break;
                    case 'add-alert':
                        var assetIds = findAssetIdsInGeofence(fence);
                        window.location = '/Alerts/CreateAlert?assetIds=' + assetIds.join() + '&fenceId=' + fence.Id;
                        break;
                    case 'location-report':
                        window.location = '/Reports/Location?fenceId=' + fence.Id;
                        break;
                    case 'edit-fence':
                        openGeofenceDialog(fence);
                        break;
                    case 'delete-fence':
                        var deleteFenceModal = $('#delete-geofence-modal');
                        $('.modal-body', deleteFenceModal).text(tracking.strings.MSG_DELETE_FENCE_CONFIRM.replace('{0}', fence.Name));
                        $j('#hfDeleteFenceId').val(fence.Id);
                        deleteFenceModal.modal('show');
                        break;
                    default:
                        console.warn('Unhandled fence action: ' + action);
                        break;
                }
                break;
            case 'groups':
                var group = findGroupById(id);
                switch (action) {
                    case 'send-message':
                        openSendMessageDialog(null, group);
                        break;
                    case 'history-replay':
                        var assetIds = findAssetIdsUnderGroup(group);
                        loadHistory(assetIds);
                        break;
                    case 'follow-group':
                        closeSecondaryPanel();
                        toggleFollowGroup(group);
                        break;
                    case 'edit-group':
                        openAssetGroupDialog(group);
                        break;
                    case 'copy-group':
                        if (tracking.options.enabledFeatures.indexOf('UI_DUPLICATE_GROUP') === -1) {
                            return;
                        }
                        openAssetGroupDialog(null);
                        populateAssetGroupDialog(group);
                        _.each(tracking.data.assets, function (asset) {
                            var isChecked = _.indexOf(asset.GroupIds, group.Id) !== -1 || group.IsDefault;
                            $('#edit-asset-group-assets-list input[value=' + asset.Id + ']').prop('checked', isChecked);
                        });
                        var itemUsers = _.find(tracking.data.assetGroupUsers, { AssetGroupId: group.Id });
                        _.each(tracking.data.users, function (user) {
                            var isChecked = itemUsers !== undefined && _.indexOf(itemUsers.UserIds, user.Id) !== -1;
                            $('#edit-asset-group-users-list input[value=' + user.Id + ']').prop('checked', isChecked);
                        });
                        break;
                    case 'delete-group':
                        var deleteGroupModal = $('#delete-group-modal');
                        $('.modal-body', deleteGroupModal).text(tracking.strings.MSG_DELETE_GROUP_CONFIRM.replace('{0}', group.Name));
                        $j('#hfDeleteGroupId').val(group.Id);
                        deleteGroupModal.modal('show');
                        break;
                    default:
                        console.warn('Unhandled asset group action: ' + action);
                        break;
                }
                break;
            case 'places':
                var place = findPlaceById(id);
                switch (action) {
                    case 'route-asset':
                        openPlaceRoutingForPlace(place);
                        break;
                    case 'edit-place':
                        openPlaceDialog(place);
                        break;
                    case 'delete-place':
                        var deletePlaceModal = $('#delete-place-modal');
                        $('.modal-body', deletePlaceModal).text(tracking.strings.MSG_DELETE_PLACE_CONFIRM.replace('{0}', place.Name));
                        $j('#hfDeletePlaceId').val(place.Id);
                        deletePlaceModal.modal('show');
                        break;
                    default:
                        console.warn('Unhandled place action: ' + action);
                        break;
                }
                break;
            case 'trips':
                var trip = findTripById(id);
                switch (action) {
                    case 'edit-trip':
                        window.location = '/settings/editjourney/' + trip.JourneyId + '?tripId=' + trip.Id + '#modify-trip';
                        break;
                    case 'delete-trip':
                        var deleteTripModal = $('#delete-trip-modal');
                        $('.modal-body', deleteTripModal).text(tracking.strings.MSG_DELETE_TRIP_CONFIRM.replace('{0}', trip.Name));
                        $j('#hfDeleteTripId').val(trip.Id);
                        deleteTripModal.modal('show');
                        break;
                    default:
                        console.warn('Unhandled trip action: ' + action);
                        break;
                }
                break;
            case 'journeys':
                var journey = findJourneyById(id);
                switch (action) {
                    case 'edit-journey':
                        window.location = '/settings/editjourney/' + journey.Id;
                        break;
                    case 'delete-journey':
                        var deleteJourneyModal = $('#delete-journey-modal');
                        $('.modal-body', deleteJourneyModal).text(tracking.strings.MSG_DELETE_JOURNEY_CONFIRM.replace('{0}', journey.Name));
                        $j('#hfDeleteJourneyId').val(journey.Id);
                        deleteJourneyModal.modal('show');
                        break;
                    default:
                        console.warn('Unhandled journey action: ' + action);
                        break;
                }
                break;
            case 'shared-views':
                var sharedView = findSharedViewById(id);
                switch (action) {
                    case 'access-controls':
                    case 'data-visualization':
                    case 'details':
                    case 'map-settings':
                    case 'permissions':
                    case 'preferences':
                        openSharedViewDialog(sharedView, action);
                        break;
                    case 'share':
                    case 'share-email':
                        openSharedViewShareDialog(sharedView);
                        break;
                    case 'statistics':
                        openSharedViewStatisticsDialog(sharedView);
                        break;
                    case 'delete-shared-view':
                        var deleteSharedViewModal = $('#delete-shared-view-modal');
                        $('.modal-body', deleteSharedViewModal).text(tracking.strings.MSG_DELETE_SHARED_VIEW_CONFIRM.replace('{0}', sharedView.Name));
                        $j('#hfDeleteSharedViewId').val(sharedView.Id);
                        deleteSharedViewModal.modal('show');
                        break;
                    case 'disable-shared-view':
                        changeSharedViewStatus(sharedView);
                        break;
                    case 'share-facebook':
                    case 'share-twitter':
                    case 'share-linkedin':
                        // don't prevent default
                        preventDefault = false;
                        break;
                    default:
                        console.warn('Unhandled shared view action:', action);
                        break;
                }
                break;
            default:
                console.log('No action handler for: ' + actionGroup + ', ' + action);
                break;
        }
        if (preventDefault) {
            e.preventDefault();
        }
    }

    function userLocationErrorManual(error) {
        switch (error.code) {
            case error.PERMISSION_DENIED:
                $('#map-current-location').addClass('disabled');
                utility.handleWebServiceError(tracking.strings.CURRENT_LOCATION_NO_PERMISSION);
                break;
            case error.POSITION_UNAVAILABLE:
                utility.handleWebServiceError(tracking.strings.CURRENT_LOCATION_UNKNOWN);
                break;
            case error.TIMEOUT:
                utility.handleWebServiceError(tracking.strings.CURRENT_LOCATION_UNKNOWN);
                break;
        }
    }

    function startChoosingMapLocation(handler) {
        var handlerIndex = _.indexOf(tracking.state.mapClickQueue, handler);
        if (handlerIndex === -1) {
            tracking.state.mapClickQueue.push(handler);
            L.DomUtil.addClass(tracking.map._container, 'crosshair-cursor-enabled');
        }
    }

    function stopChoosingMapLocation(handler) {
        var handlerIndex = _.indexOf(tracking.state.mapClickQueue, handler);
        if (handlerIndex !== -1) {
            tracking.state.mapClickQueue.splice(handlerIndex);
        }
        if (tracking.state.mapClickQueue.length === 0) {
            if (tracking.state.chosenLocations[handler] !== null && tracking.state.chosenLocations[handler] !== undefined) {
                removeItemFromMap(tracking.state.chosenLocations[handler]);
            }
            L.DomUtil.removeClass(tracking.map._container, 'crosshair-cursor-enabled');
        }
    }

    function updateChosenLocation(latlng, handler) {
        if (tracking.state.mapClickQueue.length === 0) {
            return;
        }

        var clickHandler = tracking.state.mapClickQueue[tracking.state.mapClickQueue.length-1];
        if (handler === null || handler === undefined) {
            handler = clickHandler;
        }

        // place/move a marker icon at the spot for handlers adding/selecting some kind of marked location
        // we need to have a marker per handler so they don't interfere with each other
        // and the dragend callback needs to be aware of the handler associated with it
        if (handler === tracking.state.mapClickHandlers.POSITION
            || handler === tracking.state.mapClickHandlers.POSITION_ADD
            || handler === tracking.state.mapClickHandlers.PLACE) {
            if (tracking.state.chosenLocations[handler] === null || tracking.state.chosenLocations[handler] === undefined) {
                var locationIcon = L.icon({
                    iconUrl: createMarkerPath('Generic', 'red', null, null, null, false),
                    iconSize: [36, 36],
                    iconAnchor: [18, 18]
                });
                tracking.state.chosenLocations[handler] = L.marker(latlng, { icon: locationIcon, draggable: true });
                addItemToMap(tracking.state.chosenLocations[handler]);
                tracking.state.chosenLocations[handler].on('dragend', function (e) {
                    updateChosenLocation(e.target.getLatLng(), handler);
                });
            } else {
                addItemToMap(tracking.state.chosenLocations[handler]);
                tracking.state.chosenLocations[handler].setLatLng(latlng);
            }
        }

        switch (handler) {
            case tracking.state.mapClickHandlers.POSITION:
                $('#SendPositionLatLng').text(latlng.lat.toFixed(6) + ', ' + latlng.lng.toFixed(6));
                $('#hfPositionLat').val(latlng.lat);
                $('#hfPositionLng').val(latlng.lng);
                $('#send-position-search').removeClass('is-visible');
                $('#form-send-position-send').addClass('is-visible');
                tracking.reverseGeocode(latlng, function (success, address) {
                    if (success && address !== '') {
                        if ($('#SendPositionName').val() === '') {
                            $('#SendPositionName').val(address);
                        }
                    }
                });
                break;
            case tracking.state.mapClickHandlers.POSITION_ADD:
                $('#txtAddPositionLat').val(latlng.lat.toFixed(6));
                $('#txtAddPositionLng').val(latlng.lng.toFixed(6));
                break;
            case tracking.state.mapClickHandlers.PLACE:
                $('#add-place-location').text(latlng.lat.toFixed(6) + ', ' + latlng.lng.toFixed(6));
                $('#add-place-search').removeClass('is-visible');
                $('#form-add-place').addClass('is-visible');
                $('#hfPlaceLat').val(latlng.lat);
                $('#hfPlaceLng').val(latlng.lng);

                tracking.reverseGeocode(latlng, function (success, address) {
                    if (success && address !== '') {
                        if ($('#txtPlaceName').val() === '') {
                            $('#txtPlaceName').val(address);
                        }
                    }
                });
                break;
            case tracking.state.mapClickHandlers.RULER:
                addPointToRuler(latlng);
                break;
            case tracking.state.mapClickHandlers.GEOFENCE:
                break;
            case tracking.state.mapClickHandlers.ROUTING:
                addPointToRouting(latlng);
                break;
        }
    }

    function userLocationUpdated(position) {
        if (tracking.map == null) {
            return;
        }
        if (position == null) {
            return;
        }

        if (tracking.state.userLocation.watch === null) {
            return;
        }

        var pos = L.latLng(position.coords.latitude, position.coords.longitude).wrap();

        tracking.state.userLocation.position = position;
        if (tracking.state.userLocation.marker === null) {
            var icon = L.icon({
                iconUrl: '/content/images/marker-my-location.png',
                iconSize: [14, 14],
                iconAnchor: [7, 7]
            });
            tracking.state.userLocation.marker = L.marker(pos, {
                icon: icon,
                title: tracking.strings.CURRENT_LOCATION_YOU
            }).bindTooltip(tracking.strings.CURRENT_LOCATION_YOU);
            addItemToMap(tracking.state.userLocation.marker, tracking.data.mapLayers.other);
            tracking.state.userLocation.circle = L.circle(pos, {
                radius: position.coords.accuracy,
                color: '#3583ff',
                fillColor: '#3583ff',
                opacity: 1,
                fillOpacity: 0.15,
                weight: 0
            }).bindTooltip(tracking.strings.CURRENT_LOCATION_YOU);
            addItemToMap(tracking.state.userLocation.circle, tracking.data.mapLayers.other);
        } else {
            tracking.state.userLocation.marker.setLatLng(pos);
            if (tracking.state.userLocation.circle !== null) {
                tracking.state.userLocation.circle.setLatLng(pos);
                tracking.state.userLocation.circle.setRadius(position.coords.accuracy);
            }
        }

        if (tracking.state.userLocation.zoomToPosition) {
            tracking.map.setView(pos, tracking.options.defaultZoom);
            tracking.state.userLocation.zoomToPosition = false;
        }
        $j('#map-current-location').removeClass('disabled');
    }

    function zoomToUserLocation() {
        var position = tracking.state.userLocation.position;
        if (position === null) {
            return;
        }
        var pos = L.latLng(position.coords.latitude, position.coords.longitude).wrap();
        tracking.map.setView(pos, tracking.options.defaultZoom);
        tracking.state.userLocation.zoomToPosition = false;
    }

    function userLocationError(error) {
        switch (error.code) {
            case error.PERMISSION_DENIED:
                $j('#map-current-location').addClass('disabled');
                break;
            case error.POSITION_UNAVAILABLE:
                break;
            case error.TIMEOUT:
                break;
        }
    }

    tracking.checkVersion = checkVersion;

    function checkVersion() {
        $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetStatus'),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var resp = msg.d;
                if ((tracking.version != resp.Version) || (tracking.user.id != resp.UserId)) {
                    console.log('User or version mismatch. Version: ' + tracking.version + ' vs ' + resp.Version + '. User: ' + tracking.user.id + ' vs ' + resp.UserId + '. Reloading in 15s.');
                    // don't redirect immediately because this browser session could be the one initiating the logout and will naturally redirect
                    setTimeout(function () {
                        location.reload(true);
                    }, 15000);
                } else {
                    console.log('Portal status check valid.');
                }
            },
            error: function (xhr, status, error) {

            }
        });
        tracking.throttles.updatePositionStatus();
    }

    function checkEmergencyNotifications() {
        if (!tracking.preferences.PREFERENCE_EMERGENCY_AUDIO)
            return;

        if ((tracking.user.isAnonymous) || (tracking.options.hideEmergencyEvents))
            return;

        var activeNotifications = $j('.jGrowl-notification.emergency-on').length;
        if (activeNotifications > 0) {
            tracking.throttles.emergencyAlert();
        }
    }

    function checkTextNotifications() {
        if (!tracking.preferences.PREFERENCE_EMERGENCY_AUDIO)
            return;

        var activeNotifications = $j('.jGrowl-notification.text-message').length;
        if (activeNotifications > 0) {
            alertUserOfTextMessage();
        }
    }

    // start Edge Solar
    function requestEdgeSolarInformation(assetId) {
        var data = {
            assetId: assetId
        };
        var status = document.getElementById('edge-solar-query-status');
        toggleLoadingMessage(true, 'edge-solar-information');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/EdgeSolarGetConfiguration'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    if (result.Success == true) {
                        if (result.LastRetrievedOn == null) {
                            $j('#EdgeSolarConfigLastRetrieved').text(tracking.strings.NEVER);
                        } else {
                            $j('#EdgeSolarConfigLastRetrieved').text(result.LastRetrievedOn);
                        }
                        if (result.LastQueriedOn == null) {
                            $j('#EdgeSolarConfigLastQueried').text(tracking.strings.NEVER);
                        } else {
                            $j('#EdgeSolarConfigLastQueried').text(result.LastQueriedOn);
                        }

                        var config = result.Configuration;
                        populateEdgeConfig(config, assetId);
                    } else {
                        // message failure, keep text field to allow retry
                        formShowErrorMessage(status, tracking.strings.MSG_GET_DEVICE_INFORMATION_ERROR);
                        if (result.ErrorMessage != null && result.ErrorMessage != '') {
                            formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                        }
                    }
                }
                toggleLoadingMessage(false, 'edge-solar-information');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_GET_DEVICE_INFORMATION_ERROR);
                toggleLoadingMessage(false, 'edge-solar-information');
            }
        });
    }

    function populateEdgeConfig(config, assetId) {
        if (config == null) {
            // defaults
            for (var i = 1; i <= 8; i++) {
                document.getElementById('EdgeSolarReportingInterval' + i + 'Disabled').checked = true;
                document.getElementById('EdgeSolarReportingInterval' + i + 'IntervalMinutes').value = '0';
                document.getElementById('EdgeSolarReportingInterval' + i + 'TimeHour').value = '0';
                document.getElementById('EdgeSolarReportingInterval' + i + 'TimeMinute').value = '00';
            }
            document.getElementById('EdgeSolarReportingInterval1Interval').checked = true;
            document.getElementById('EdgeSolarReportingInterval1IntervalMinutes').value = '720';

            document.getElementById('EdgeSolarVbmrMode').value = '0';
            document.getElementById('EdgeSolarVbmrFields').classList.remove('is-visible');
            document.getElementById('EdgeSolarVbmrIntervalMinutes').value = '0';
            document.getElementById('EdgeSolarVbmrIsEnabledDuringPowerSaveNo').checked = true;
            
            document.getElementById('EdgeSolarGbmrIsEnabledNo').checked = true;
            document.getElementById('EdgeSolarGbmrFields').classList.remove('is-visible');
            document.getElementById('EdgeSolarGbmrCheckIntervalMinutes').value = '0';
            document.getElementById('EdgeSolarGbmrHomeRatio').value = '0';
            document.getElementById('EdgeSolarGbmrAwayRatio').value = '0';

            document.getElementById('EdgeSolarPowerSaveIsEnabledNo').checked = true;
            document.getElementById('EdgeSolarPowerSaveFields').classList.remove('is-visible');
            document.getElementById('EdgeSolarPowerSaveIsDays').value = '0',
            document.getElementById('EdgeSolarPowerSaveCount').value = '0';
            document.getElementById('EdgeSolarPowerSaveIntervalHours').value = '0';
            
            document.getElementById('EdgeSolarEngineeringMessageInterval').value = '336';
            document.getElementById('EdgeSolarMailboxCheckIntervalHours').value = '0';
            document.getElementById('EdgeSolarMailboxCheckIsEnabledFalse').checked = true;
            document.getElementById('EdgeSolarIridiumRetryCount').value = '3';            
            return;
        }

        // intervals
        for (var i = 1; i <= 8; i++) {
            var interval = 'Interval' + i;
            var conf = config[interval];
            if (!conf.IsEnabled) {
                document.getElementById('EdgeSolarReporting' + interval + 'Disabled').checked = true;
            } else {
                if (conf.IsTimed) {
                    document.getElementById('EdgeSolarReporting' + interval + 'TimeOfDay').checked = true;
                } else {
                    document.getElementById('EdgeSolarReporting' + interval + 'Interval').checked = true;
                }
            }

            // time of day, EdgeSolarReportingInterval2TimeHour, EdgeSolarReportingInterval2TimeMinute
            var hours = Math.floor(conf.TimeMinutesPastUtc / 60).toString();
            if (hours.length == 1) {
                hours = '0' + hours;
            }
            var minutes = (conf.TimeMinutesPastUtc % 60).toString();
            if (minutes.length == 1) {
                minutes = '0' + minutes;
            }
            // minutes to hours, minutes
            document.getElementById('EdgeSolarReporting' + interval + 'TimeHour').value = hours;
            document.getElementById('EdgeSolarReporting' + interval + 'TimeMinute').value = minutes;

            // interval in minutes
            document.getElementById('EdgeSolarReporting' + interval + 'IntervalMinutes').value = conf.IntervalMinutes;
        }

        // vbmr
        document.getElementById('EdgeSolarVbmrMode').value = config.VbmrMode;
        document.getElementById('EdgeSolarVbmrIntervalMinutes').value = config.VbmrIntervalMinutes;
        if (config.VbmrIsEnabledDuringPowerSave) {
            document.getElementById('EdgeSolarVbmrIsEnabledDuringPowerSaveYes').checked = true;
        } else {
            document.getElementById('EdgeSolarVbmrIsEnabledDuringPowerSaveNo').checked = true;
        }        

        // gbmr
        if (config.GbmrIsEnabled) {
            document.getElementById('EdgeSolarGbmrIsEnabledYes').checked = true;
        } else {
            document.getElementById('EdgeSolarGbmrIsEnabledNo').checked = true;
        }
        document.getElementById('EdgeSolarGbmrCheckIntervalMinutes').value = config.GbmrCheckIntervalMinutes;
        document.getElementById('EdgeSolarGbmrAwayRatio').value = config.GbmrAwayRatio;
        document.getElementById('EdgeSolarGbmrHomeRatio').value = config.GbmrHomeRatio;

        // power save
        if (config.PowerSaveIsEnabled) {
            document.getElementById('EdgeSolarPowerSaveIsEnabledYes').checked = true;
        } else {
            document.getElementById('EdgeSolarPowerSaveIsEnabledNo').checked = true;
        }
        if (config.PowerSaveIsDays) {
            document.getElementById('EdgeSolarPowerSaveIsDays').value = '1';
        } else {
            document.getElementById('EdgeSolarPowerSaveIsDays').checked = '0';
        }
        document.getElementById('EdgeSolarPowerSaveCount').value = config.PowerSaveCount;
        document.getElementById('EdgeSolarPowerSaveIntervalHours').value = config.PowerSaveIntervalHours;

        // settings
        document.getElementById('EdgeSolarEngineeringMessageInterval').value = config.EngineeringMessageIntervalHours;
        document.getElementById('EdgeSolarMailboxCheckIntervalHours').value = config.MailboxCheckIntervalHours;
        if (config.MailboxCheckIsEnabled) {
            document.getElementById('EdgeSolarMailboxCheckIsEnabledTrue').checked = true;
        } else {
            document.getElementById('EdgeSolarMailboxCheckIsEnabledFalse').checked = true;
        }
        document.getElementById('EdgeSolarMailboxCheckIntervalHours').value = config.MailboxCheckIntervalHours;
        document.getElementById('EdgeSolarIridiumRetryCount').value = config.IridiumRetryCount;

        // toggle enabled/disabled and is-visible based on item selection
        toggleEdgeSolarVisibleElements();
    }

    function toggleEdgeSolarVisibleElements() {
        if ($('input[name=EdgeSolarPowerSaveIsEnabled]:checked').val() === '1') {
            $('#EdgeSolarPowerSaveFields').addClass('is-visible');
        } else {
            $('#EdgeSolarPowerSaveFields').removeClass('is-visible');
        }
        if ($('input[name=EdgeSolarGbmrIsEnabled]:checked').val() === '1') {
            $('#EdgeSolarGbmrFields').addClass('is-visible');
        } else {
            $('#EdgeSolarGbmrFields').removeClass('is-visible');
        }
        if ($('#EdgeSolarVbmrMode').val() !== '0') {
            $('#EdgeSolarVbmrFields').addClass('is-visible');
        } else {
            $('#EdgeSolarVbmrFields').removeClass('is-visible');
        }

        // TODO disable things based on chosen radio elements
    }

    // end Edge Solar

    // start Queclink

    function requestQueclinkInformation(assetId) {
        var data = {
            assetId: assetId
        };
        var status = document.getElementById('queclink-query-status');
        toggleLoadingMessage(true, 'queclink-information');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/QueclinkGetConfiguration'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    if (result.Success == true) {
                        if (result.LastRetrievedOn == null) {
                            $j('#QueclinkConfigLastRetrieved').text(tracking.strings.NEVER);
                        } else {
                            $j('#QueclinkConfigLastRetrieved').text(result.LastRetrievedOn);
                        }
                        if (result.LastQueriedOn == null) {
                            $j('#QueclinkConfigLastQueried').text(tracking.strings.NEVER);
                        } else {
                            $j('#QueclinkConfigLastQueried').text(result.LastQueriedOn);
                        }

                        var config = result.Configuration;
                        populateQueclinkConfig(config, assetId);
                    } else {
                        // message failure, keep text field to allow retry
                        formShowErrorMessage(status, tracking.strings.MSG_GET_DEVICE_INFORMATION_ERROR);
                        if (result.ErrorMessage != null && result.ErrorMessage != '') {
                            formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                        }
                    }
                }
                toggleLoadingMessage(false, 'queclink-information');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_GET_DEVICE_INFORMATION_ERROR);
                toggleLoadingMessage(false, 'queclink-information');
            }
        });
    }

    function populateQueclinkConfig(config, assetId) {
        if (config == null)
            return;

        var asset = findAssetById(assetId);
        if ((config.Version == null) || (asset.Version == '')) {
            $j('#QueclinkProtocolVersion').text(tracking.strings.UNKNOWN);
        } else {
            $j('#QueclinkProtocolVersion').text(config.ProtocolVersion);
        }

        // GL200/300
        if (config.FRI != null) {
            $j('#ddlQueclinkReportMode').val(config.FRI.ReportMode);
            $j('#txtQueclinkGPSCheckInterval').val(config.FRI.CheckInterval);
            $j('#txtQueclinkSendInterval').val(config.FRI.SendInterval);
            $j('#txtQueclinkIgnitionOnGPSCheckInterval').val(config.FRI.IgnitionCheckInterval);
            $j('#txtQueclinkIgnitionOnSendInterval').val(config.FRI.IgnitionSendInterval);
            $j('#txtQueclinkStartTime').val(config.FRI.BeginTime);
            $j('#txtQueclinkEndTime').val(config.FRI.EndTime);
            $j('#txtQueclinkDistance').val(config.FRI.Distance);
            $j('#txtQueclinkMileage').val(config.FRI.Mileage);

            // nullables
            //if (config.FRI.MovementDetectMode != null) {
            $j('input:radio[name=rbQueclinkMovementDetectMode][value=' + config.FRI.MovementDetectMode + ']').prop('checked', true);
            //}
            $j('#txtQueclinkMovementSpeed').val(config.FRI.MovementSpeed);
            $j('#txtQueclinkMovementDistance').val(config.FRI.MovementDistance);
            $j('#txtQueclinkCorner').val(config.FRI.Corner);
            $j('input:radio[name=rbQueclinkDiscardNoFix][value=' + config.FRI.DiscardNoFix + ']').prop('checked', true);
            var reportInclude = config.FRI.ReportInclude;
            $j('#chkQueclinkReportIncludeSpeed').prop('checked', reportInclude.Speed);
            $j('#chkQueclinkReportIncludeAltitude').prop('checked', reportInclude.Altitude);
            $j('#chkQueclinkReportIncludeAzimuth').prop('checked', reportInclude.Azimuth);
            $j('#chkQueclinkReportIncludeGSMTowerData').prop('checked', reportInclude.GSMTowerData);
            $j('#chkQueclinkReportIncludeSendTime').prop('checked', reportInclude.SendTime);

        }
    }
    // end Queclink

    function resetIDPCounters(assetId) {
        var data = {
            assetId: assetId
        };
        var status = document.getElementById('idp-query-status');
        toggleLoadingMessage(true, 'idp-counters-reset');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/ResetAssetCounters'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    if (result.Success == true) {
                        requestAVLInformation(assetId);
                    } else {
                        // message failure, keep text field to allow retry
                        formShowErrorMessage(status, tracking.strings.MSG_SKYWAVE_COUNTERS_RESET_ERROR);
                        if (result.ErrorMessage != null && result.ErrorMessage != '') {
                            formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                        }
                    }
                }
                toggleLoadingMessage(false, 'idp-counters-reset');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_SKYWAVE_COUNTERS_RESET_ERROR);
                toggleLoadingMessage(false, 'idp-counters-reset');
            }
        });
    }

    function renderIDPCommandLog(commandLog) {
        var commandLogData = [];
        for (var i = 0; i < commandLog.length; i++) {
            var item = commandLog[i];
            commandLogData.push([
                item.CreatedOn,
                el('pre', item.Command),
                disabledCheckboxEl(item.IsSent),
                disabledCheckboxEl(item.IsResponse),
                disabledCheckboxEl(item.Success)
            ]);
        }

        $('#IDPCommandLog').dataTable({
            data: commandLogData,
            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
            'lengthChange': false, 'paging': false, 'pageLength': 5, 'deferRender': true,
            'order': [[0, 'asc']],
            'columnDefs': [{
                'targets': '_all',
                'render': $.fn.dataTable.render.text()
            }],
            'columns': [
                {},
                { render: renderDomElement },
                { render: renderDomElement },
                { render: renderDomElement },
                { render: renderDomElement }
            ],
            'language': tracking.strings.DATATABLE
        });
    }

    function requestIDPCommandLog(assetId, scrollTo) {
        var data = {
            assetId: assetId
        };

        var btn = document.getElementById('RefreshIDPCommandLog');
        handleAjaxFormSubmission('IDPGetExecuteCommandResponses', data, btn, null, null, null, function (result) {
            renderIDPCommandLog(result.Commands);
            if (scrollTo) {
                document.getElementById('IDPCommand').scrollIntoView(false);
            }
        });
    }

    function populateGarminConfig(config) {
        if(config == null)
            return;
        // populate configuration values
        if (config.autoArrivalInfo != null) {
            $j('input:radio[name=rbGarminAutoArrivalInfoEnabled][value=' + config.autoArrivalInfo.enabled + ']').prop('checked', true);
            $j('#txtGarminAutoArrivalInfoStopDistanceInMeters').val(config.autoArrivalInfo.stopDistanceInMeters);
            $j('#txtGarminAutoArrivalInfoStopTimeInSeconds').val(config.autoArrivalInfo.stopTimeInSeconds);
        }
        if ((config.accelerometerInfo != null) && (config.accelerometerInfo.accelerometers != null)) {
            var accel = config.accelerometerInfo.accelerometers[0];
            $j('input:radio[name=rbGarminAccelerometerForwardEnabled][value=' + accel.forward.enabled + ']').prop('checked', true);
            $j('#txtGarminAccelerometerForwardMagnitude').val(accel.forward.magnitude);
            $j('input:radio[name=rbGarminAccelerometerBackwardEnabled][value=' + accel.backward.enabled + ']').prop('checked', true);
            $j('#txtGarminAccelerometerBackwardMagnitude').val(accel.backward.magnitude);
            $j('input:radio[name=rbGarminAccelerometerLeftwardEnabled][value=' + accel.leftward.enabled + ']').prop('checked', true);
            $j('#txtGarminAccelerometerLeftwardMagnitude').val(accel.leftward.magnitude);
            $j('input:radio[name=rbGarminAccelerometerRightwardEnabled][value=' + accel.rightward.enabled + ']').prop('checked', true);
            $j('#txtGarminAccelerometerRightwardMagnitude').val(accel.rightward.magnitude);
            $j('input:radio[name=rbGarminAccelerometerUpwardEnabled][value=' + accel.upward.enabled + ']').prop('checked', true);
            $j('#txtGarminAccelerometerUpwardMagnitude').val(accel.upward.magnitude);
            $j('input:radio[name=rbGarminAccelerometerDownwardEnabled][value=' + accel.downward.enabled + ']').prop('checked', true);
            $j('#txtGarminAccelerometerDownwardMagnitude').val(accel.downward.magnitude);
        }
        if (config.deviceConfigSyncStatus != null) {

        }
        if (config.engineStateDetection != null) {
            $j('input:radio[name=rbGarminEngineStateDetectionEnabled][value=' + config.engineStateDetection.enabled + ']').prop('checked', true);
        }
        if (config.estimatedTimeOfArrival != null) {
            $j('input:radio[name=rbGarminEstimatedTimeOfArrivalEnabled][value=' + config.estimatedTimeOfArrival.enabled + ']').prop('checked', true);
        }
        if (config.motionDetection != null) {
            $j('input:radio[name=rbGarminMotionDetectionEnabled][value=' + config.motionDetection.enabled + ']').prop('checked', true);
            $j('#txtGarminMotionDetectionStopMotionMinimumDuration').val(config.motionDetection.stopMotionMinimumDurationInSeconds);
        }
        if (config.odometerCalibrationInfo != null) {
            $j('#txtGarminTripCOdometer').val(config.odometerCalibrationInfo.tripCOdometer);
        }
        if (config.powerOffMode != null) {
            $j('input:radio[name=rbGarminPowerOffModeEnabled][value=' + config.powerOffMode.enabled + ']').prop('checked', true);
            $j('#txtGarminPowerOffModePin').val(config.powerOffMode.pin);
        }
        if (config.privacyControlInfo != null) {
            $j('input:radio[name=rbGarminPrivacyControlInfoEnabled][value=' + config.privacyControlInfo.enabled + ']').prop('checked', true);
        }
        if (config.productDetails != null) {
            $j('#TelematicsMapVersion').text(config.productDetails.mapVersion);
            $j('#TelematicsSoftwareVersion').text(config.productDetails.softwareVersion);
        }
        if (config.safeMode != null) {
            $j('input:radio[name=rbGarminSafeModeEnabled][value=' + config.safeMode.enabled + ']').prop('checked', true);
            $j('#txtGarminSafeModeThresholdSpeed').val(config.safeMode.thresholdSpeedInMPS);
        }
        if (config.speedLimitEvents != null) {
            $j('input:radio[name=rbGarminSpeedLimitEventsEnabled][value=' + config.speedLimitEvents.enabled + ']').prop('checked', true);
            $j('input:radio[name=rbGarminSpeedLimitAlertUser][value=' + config.speedLimitEvents.alertUser + ']').prop('checked', true);
            $j('#txtGarminSpeedLimitEventsTimeOverValue').val(config.speedLimitEvents.timeOverValueInSeconds);
            $j('#txtGarminSpeedLimitEventsTimeUnderValue').val(config.speedLimitEvents.timeUnderValueInSeconds);
            $j('#txtGarminSpeedLimitEventsSpeed').val(config.speedLimitEvents.speed);
        }
        if (config.timeClockDriverSettings != null) {
            $j('input:radio[name=rbGarminTimeClockEnabled][value=' + config.timeClockDriverSettings.enabled + ']').prop('checked', true);
            $j('input:radio[name=rbGarminTimeClockDriverPasswordEnabled][value=' + config.timeClockDriverSettings.driverPasswordEnabled + ']').prop('checked', true);
        }
        if (config.uiTextInfo != null) {
            $j('#txtGarminDispatchText').val(config.uiTextInfo.dispatchText);
        }
    }

    function resetGarminDevice() {
        var resetDialog = $j('#garmin-reset-dialog');
        var dialog = $j('#garmin-dialog');
        var assetId = dialog.data('assetId');
        var data = {
            assetId: assetId
        };
        var status = document.getElementById('garmin-query-status');
        toggleLoadingMessage(true, 'garmin-reset');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/TelematicsResetDevice'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    if (result.Success == true) {
                        var config = result.Configuration;
                        populateGarminConfig(config);
                        dialog.dialog('close');
                        resetDialog.dialog('close');
                    } else {
                        // message failure, keep text field to allow retry
                        formShowErrorMessage(status, tracking.strings.MSG_RESET_DEVICE_ERROR);
                        if (result.ErrorMessage != null && result.ErrorMessage != '') {
                            formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                        }
                    }
                }
                toggleLoadingMessage(false, 'garmin-reset');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_RESET_DEVICE_ERROR);
                toggleLoadingMessage(false, 'garmin-reset');
            }
        });
    }

    function queclinkGetPassword(asset) {
        // asset.Password will be 'placeholder' for security reasons...
        if ((asset.Password == null) || (asset.Password == '')) {
            // use default
            switch (asset.DeviceId) {
                case tracking.devices.QUECLINK_GV200:
                    return 'gv200';
                case tracking.devices.QUECLINK_GV300:
                    return 'gv300';
                case tracking.devices.QUECLINK_GV600W:
                    return 'gv600w';
                case tracking.devices.QUECLINK_GL200:
                    return 'gl200';
                case tracking.devices.QUECLINK_GL300:
                	return 'gl300';
				case tracking.devices.QUECLINK_GL300W:
                    return 'gl300w';
                case tracking.devices.QUECLINK_GL500:
                	return 'gl500';
            	case tracking.devices.QUECLINK_GL520:
            		return 'gl520';
                case tracking.devices.QUECLINK_GV500:
                    return 'gv500';
                case tracking.devices.GOTEK_PRIMELITE:
                    return 'AIR11';
                case tracking.devices.QUECLINK_GT300:
                	return 'gt300';
            	case tracking.devices.QUECLINK_GT301:
            		return 'gt301';
            	case tracking.devices.QUECLINK_GB100:
            		return 'gb100';
                default:
                    return asset.Password;
            }
        }
        return asset.Password;
    }

    function queclinkCreateAPNCommand(assetId) {
        var asset = findAssetById(assetId);
        var apn = $j('#txtQueclinkAPN').val();
        var username = $j('#txtQueclinkAPNUsername').val();
        var password = $j('#txtQueclinkAPNPassword').val();

        var devicePassword = queclinkGetPassword(asset);
        var command = '';
        switch (asset.DeviceId) {
            case tracking.devices.QUECLINK_GL200:
        	case tracking.devices.QUECLINK_GL300:
        	case tracking.devices.QUECLINK_GL300W:
                command = 'AT+GTQSS={pwd},{apn},{username},{password},4,,2,137.83.51.39,4753,0.0.0.0,0,,0,0,,,0000$';
                break;
        	case tracking.devices.QUECLINK_GL500:
        	case tracking.devices.QUECLINK_GL520:
            case tracking.devices.GOTEK_PRIMELITE:
                command = 'AT+GTQSS={pwd},{apn},{username},{password},4,,0,137.83.51.39,4753,0.0.0.0,0,,0,0,,,0000$';
                break;
            case tracking.devices.QUECLINK_GV200:
            case tracking.devices.QUECLINK_GV300:
        	case tracking.devices.QUECLINK_GV500:
        	case tracking.devices.QUECLINK_GB100:
                command = 'AT+GTQSS={pwd},{apn},{username},{password},4,,2,137.83.51.39,4753,0.0.0.0,0,,0,0,0,,0000$';
                break;
            case tracking.devices.QUECLINK_GT300:
                command = 'AT+GTSRI={pwd},3,{apn},{username},{password},137.83.51.39,4753,,0000';
                break;
        	case tracking.devices.QUECLINK_GT301:
        		command = 'AT+GTSRI={pwd},3,{apn},{username},{password},137.83.51.39,4753,,0000$';
                break;
            case tracking.devices.QUECLINK_GV600W:
                command = 'AT+GTQSS={pwd},{apn},{username},{password},4,,2,137.83.51.39,4753,0.0.0.0,0,,0,0,0,0,0000$';
                break;
        }
        command = command.replace('{pwd}', devicePassword).replace('{username}', username).replace('{apn}', apn).replace('{password}', password);
        $j('#queclink-apn-form div.command-summary').text(command);
    }

    function resetGarminDeviceConfiguration() {
        var resetDialog = $j('#garmin-reset-config-dialog');
        var dialog = $j('#garmin-dialog');
        var assetId = dialog.data('assetId');
        var data = {
            assetId: assetId
        };
        var status = document.getElementById('garmin-query-status');
        toggleLoadingMessage(true, 'garmin-reset-config');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/TelematicsResetDeviceConfiguration'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    if (result.Success == true) {
                        var config = result.Configuration;
                        populateGarminConfig(config);
                        dialog.dialog('close');
                        resetDialog.dialog('close');
                    } else {
                        // message failure, keep text field to allow retry
                        formShowErrorMessage(status, tracking.strings.MSG_RESET_DEVICE_CONFIGURATION_ERROR);
                        if (result.ErrorMessage != null && result.ErrorMessage != '') {
                            formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                        }
                    }
                }
                toggleLoadingMessage(false, 'garmin-reset-config');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_RESET_DEVICE_CONFIGURATION_ERROR);
                toggleLoadingMessage(false, 'garmin-reset-config');
            }
        });
    }

    function requestInmarsatCInformation(asset) {
        var data = {
            assetId: asset.Id
        };
        var status = document.getElementById('inmarsatc-query-status');
        toggleLoadingMessage(true, 'inmarsatc-information');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/InmarsatCGetRegionStatus'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    $j('#InmarsatCOceanRegions label').removeClass('success error');
                    if (result.Success == true) {
                        $j('#InmarsatCAORW').text(result.AORW == null ? tracking.strings.UNKNOWN : (result.AORW == true ? tracking.strings.DOWNLOADED : tracking.strings.QUEUED))
                        .addClass(result.AORW == null ? '' : (result.AORW == true ? 'success' : 'error'));
                        $j('#InmarsatCAORE').text(result.AORE == null ? tracking.strings.UNKNOWN : (result.AORE == true ? tracking.strings.DOWNLOADED : tracking.strings.QUEUED))
                        .addClass(result.AORE == null ? '' : (result.AORE == true ? 'success' : 'error'));
                        $j('#InmarsatCPOR').text(result.POR == null ? tracking.strings.UNKNOWN : (result.POR == true ? tracking.strings.DOWNLOADED : tracking.strings.QUEUED))
                        .addClass(result.POR == null ? '' : (result.POR == true ? 'success' : 'error'));
                        $j('#InmarsatCIOR').text(result.IOR == null ? tracking.strings.UNKNOWN : (result.IOR == true ? tracking.strings.DOWNLOADED : tracking.strings.QUEUED))
                        .addClass(result.IOR == null ? '' : (result.IOR == true ? 'success' : 'error'));
                        $j('#InmarsatCMORP').text(result.MORP == null ? tracking.strings.UNKNOWN : (result.MORP == true ? tracking.strings.DOWNLOADED : tracking.strings.QUEUED))
                            .addClass(result.MORP == null ? '' : (result.MORP == true ? 'success' : 'error'));
                    } else {
                        // message failure, keep text field to allow retry
                        formShowErrorMessage(status, tracking.strings.MSG_GET_DEVICE_INFORMATION_ERROR);
                        if (result.ErrorMessage != null && result.ErrorMessage != '') {
                            formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                        }
                    }
                }
                toggleLoadingMessage(false, 'inmarsatc-information');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_GET_DEVICE_INFORMATION_ERROR);
                toggleLoadingMessage(false, 'inmarsatc-information');
            }
        });
    }

    function requestGarminInformation(assetId) {
        var data = {
            assetId: assetId
        };
        var status = document.getElementById('garmin-query-status');
        toggleLoadingMessage(true, 'garmin-information');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/TelematicsGetDeviceConfiguration'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    if (result.Success == true) {
                        var config = result.Configuration;
                        populateGarminConfig(config);
                    } else {
                        // message failure, keep text field to allow retry
                        formShowErrorMessage(status, tracking.strings.MSG_GET_DEVICE_INFORMATION_ERROR);
                        if (result.ErrorMessage != null && result.ErrorMessage != '') {
                            formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                        }
                    }
                }
                toggleLoadingMessage(false, 'garmin-information');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_GET_DEVICE_INFORMATION_ERROR);
                toggleLoadingMessage(false, 'garmin-information');
            }
        });
    }

    function formattedTextToDiv(formatted) {
        // split the text on newlines and add html breaks instead
        // returning an array of nodes
        if (formatted === undefined || formatted === null || formatted === '') {
            return null;
        }

        var lines = formatted.split(/\r?\n/g);
        var nodes = [];
        for (var i = 0; i < lines.length; i++) {
            var line = lines[i];
            nodes.push(text(line));
            if (i != lines.length - 1) {
                nodes.push(el('br'));
            }
        }
        return el('div', nodes);
    }

    function disabledCheckboxEl(checked) {
        var check = el('input.form-check-input', { type: 'checkbox', disabled: true });
        if (checked) {
            check.setAttribute('checked', 'checked');
        }
        var box = el('div.form-check', check);
        return box;
    }

    function populateIDPCryptoInformation(crypto) {
        //crypto.IsServiceEnabled
        //crypto.IsEnforced
        //crypto.CipherSuite
        //crypto.IsKeyDefault
        var serviceInfo = document.getElementById('idp-crypto-enabled');
        var enforced = document.getElementById('idp-crypto-required');
        var disableButton = document.getElementById('IDPCryptoDisableRequirement');
        var enableButton = document.getElementById('IDPCryptoEnableRequirement');
        var cipher = document.getElementById('idp-crypto-cipher');
        var key = document.getElementById('idp-crypto-key');
        var rekeyButton = document.getElementById('IDPCryptoRekey');

        serviceInfo.textContent = crypto.IsServiceEnabled === true ? tracking.strings.YES : crypto.IsServiceEnabled === false ? tracking.strings.NO : tracking.strings.UNKNOWN;
        enforced.textContent = crypto.IsEnforced === true ? tracking.strings.YES : tracking.strings.NO;

        if (crypto.IsEnforced === true) {
            disableButton.classList.remove('disabled');
            disableButton.classList.remove('toggle-content');
            disableButton.disabled = false;

            enableButton.classList.add('disabled');
            enableButton.classList.add('toggle-content');
            enableButton.disabled = true;
        } else {
            disableButton.classList.add('disabled');
            disableButton.classList.add('toggle-content');
            disableButton.disabled = true;

            enableButton.classList.remove('disabled');
            enableButton.classList.remove('toggle-content');
            enableButton.disabled = false;
        }

        cipher.textContent = crypto.CipherSuite !== null ? crypto.CipherSuite : tracking.strings.UNKNOWN;
        key.textContent = crypto.IsKeyDefault === true ? tracking.strings.DEFAULT : tracking.strings.CUSTOM;

        if (crypto.IsServiceEnabled === false) {
            rekeyButton.classList.add('disabled');
            rekeyButton.disabled = true;
        } else {
            rekeyButton.classList.remove('disabled');
            rekeyButton.disabled = false;
        }
    }

    function populateIDPUpdaterInformation(updater) {
        // terminal info
        var terminalInfo = updater.TerminalInfos;
        var terminalInfoData = [];
        for (var i = 0; i < terminalInfo.length; i++) {
            var item = terminalInfo[i];
            if (item.Info !== null) {
                terminalInfoData.push([
                    item.Info.FirmwarePackage,
                    item.Info.FirmwareVersion,
                    item.Info.FreeFilespaceBytes,
                    item.Info.FreeRamBytes,
                    item.Info.TerminalKind,
                    item.CreatedOn
                ]);
            }
        }
        $('#IDPUpdaterVersionHistory').dataTable({
            data: terminalInfoData,
            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
            'lengthChange': false, 'paging': false, 'pageLength': 3, 'deferRender': true,
            'order': [[5, 'desc']],
            'columnDefs': [{
                'targets': '_all',
                'render': $.fn.dataTable.render.text()
            }],
            'columns': [
                {},
                {},
                {},
                {},
                {},
                {}
            ],
            'language': tracking.strings.DATATABLE
        });

        // update states
        var stateInfo = updater.States;
        var terminalStateData = [];
        for (var i = 0; i < stateInfo.length; i++) {
            var item = stateInfo[i];
            terminalStateData.push([
                item.State,
                item.CreatedOn
            ]);
        }
        $('#IDPUpdaterStatus').dataTable({
            data: terminalStateData,
            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
            'lengthChange': false, 'paging': true, 'pageLength': 5, 'deferRender': true,
            'order': [[1, 'desc']],
            'columnDefs': [{
                'targets': '_all',
                'render': $.fn.dataTable.render.text()
            }],
            'columns': [
                {},
                {}
            ],
            'language': tracking.strings.DATATABLE
        });
    }

    function requestIDPUpdaterInformation(assetId) {
        toggleLoadingMessage(true, 'idp-updater');
        var data = {
            assetId: assetId
        };
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/IDPUpdaterGetInformation'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                if (msg !== null && msg.d !== null && msg.d.Success === true) {
                    populateIDPUpdaterInformation(msg.d);
                }
                toggleLoadingMessage(false, 'idp-updater');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_IDP_INFORMATION_ERROR);
                toggleLoadingMessage(false, 'idp-updater');
            }
        });
    }

    function requestIDPCryptoInformation(assetId) {
        toggleLoadingMessage(true, 'idp-crypto');
        var data = {
            assetId: assetId
        };
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/IDPCryptoGetInformation'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                if (msg !== null && msg.d !== null && msg.d.Success === true) {
                    populateIDPCryptoInformation(msg.d);
                }
                toggleLoadingMessage(false, 'idp-crypto');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_IDP_INFORMATION_ERROR);
                toggleLoadingMessage(false, 'idp-crypto');
            }
        });
    }

    function requestAVLInformation(assetId) {
        var data = {
            assetId: assetId
        };
        var status = document.getElementById('idp-query-status');
        toggleLoadingMessage(true, 'idp-information');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetAVLInformationForAsset'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    if (result.Success == true) {
                        var asset = findAssetById(assetId);
                        if (result.IDP != null) {
                            asset.IDP = result.IDP;
                        }

                        // service meter
                        var meter = result.ServiceMeter;
                        if (meter.LastRetrievedOn == null) {
                            $j('#IDPServiceMetersLastRetrieved').text(tracking.strings.NEVER);
                        } else {
                            $j('#IDPServiceMetersLastRetrieved').text(meter.LastRetrievedOn);
                        }
                        if (meter.LastQueriedOn == null) {
                            $j('#IDPServiceMetersLastQueried').text(tracking.strings.NEVER);
                        } else {
                            $j('#IDPServiceMetersLastQueried').text(meter.LastQueriedOn);
                        }
                        var props = meter.ServiceMeter;
                        if (props != null) {
                            $j('#txtIDPAVLSM0Time').val(props.SM0Time);
                            $j('#txtIDPAVLSM0Distance').val(props.SM0Distance);
                            $j('#txtIDPAVLSM1Time').val(props.SM1Time);
                            $j('#txtIDPAVLSM1Distance').val(props.SM1Distance);
                            $j('#txtIDPAVLSM2Time').val(props.SM2Time);
                            $j('#txtIDPAVLSM2Distance').val(props.SM2Distance);
                            $j('#txtIDPAVLSM3Time').val(props.SM3Time);
                            $j('#txtIDPAVLSM3Distance').val(props.SM3Distance);
                            $j('#txtIDPAVLSM4Time').val(props.SM4Time);
                            $j('#txtIDPAVLSM4Distance').val(props.SM4Distance);
                            $j('#txtIDPServiceMeterOdometer').val(props.Odometer);
                        }

                        // core parameters
                        var core = result.CoreProperties;
                        var props = core.CoreProperties;

                        if (core.LastRetrievedOn == null) {
                            $j('.CoreParametersLastRetrieved').text(tracking.strings.NEVER);
                        } else {
                            $j('.CoreParametersLastRetrieved').text(core.LastRetrievedOn);
                        }
                        if (core.LastQueriedOn == null) {
                            $j('.CoreParametersLastQueried').text(tracking.strings.NEVER);
                        } else {
                            $j('.CoreParametersLastQueried').text(core.LastQueriedOn);
                        }
                        if (props != null) {
                            // apply updated asset IDP information

                            if (props.System != null) {
                                $j('#txtIDPSystemExecutionWatchdogTimeout').val(props.System.ExecutionWatchdogTimeout);
                                $j('#txtIDPSystemAutoGCMemThreshold').val(props.System.AutoGCMemThreshold);
                                $j('#ddlIDPSystemLedControl').val(props.System.LedControl);
                            }
                            if (props.Power != null) {
                                $j('#ddlIDPPowerExtPowerPresentStateDetect').val(props.Power.ExtPowerPresentStateDetect);
                            }
                            if (props.Report != null) {
                                $j('input:radio[name=rbIDPReport1Enabled][value=' + props.Report.Report1Enabled + ']').prop('checked', true);
                                $j('#ddlIDPReport1ReportType').val(props.Report.Report1MIN);
                                $j('#txtIDPReport1TimeInterval').val(props.Report.Report1Interval);
                            }
                            if (props.Message != null) {
                                $j('input:radio[name=rbIDPMessageRetry][value=' + props.Message.Retry + ']').prop('checked', true);
                                $j('#txtIDPMessageTransport1').val(props.Message.Transport1);
                                $j('#txtIDPMessageTransport2').val(props.Message.Transport2);
                                $j('#txtIDPMessageTimeout1').val(props.Message.Timeout1);
                                $j('#txtIDPMessageTimeout2').val(props.Message.Timeout2);
                                $j('#txtIDPMessageRetrvInterval').val(props.Message.RetrvInterval);
                                $j('#txtIDPMessageRetrvMultiplier').val(props.Message.RetrvMultiplier);
                            }
                            if (props.Position != null) {
                                $j('#txtIDPPositionContinuous').val(props.Position.Continuous);
                            }
                            if (props.Log != null) {
                                $j('input:radio[name=rbIDPLogDataLogEnabled][value=' + props.Log.DataLogEnabled + ']').prop('checked', true);
                                $j('#txtIDPLogMaxDataLogSize').val(props.Log.MaxDataLogSize);
                                $j('#txtIDPLogMaxDataLogFiles').val(props.Log.MaxDataLogFiles);
                                $j('input:radio[name=rbIDPLogDebugLogEnabled][value=' + props.Log.DebugLogEnabled + ']').prop('checked', true);
                                $j('#txtIDPLogMaxDebugLogSize').val(props.Log.MaxDebugLogSize);
                            }
                            if (props.EIO != null) {
                                $j('#ddlIDPEIOPort1Config').val(props.EIO.Port1Config);
                                //$j('input[name=rbIDPEIOPort1AlarmMsg]:checked').val(props.EIO.Port1AlarmMsg);
                                //$j('input[name=rbIDPEIOPort1AlarmLog]:checked').val(props.EIO.Port1AlarmLog);
                                $j('input:radio[name=rbIDPEIOPort1AlarmMsg][value=' + props.EIO.Port1AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEIOPort1AlarmLog][value=' + props.EIO.Port1AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEIOPort1EdgeDetect').val(props.EIO.Port1EdgeDetect);
                                $j('#txtIDPEIOPort1EdgeSampleCount').val(props.EIO.Port1EdgeSampleCount);
                                $j('#txtIDPEIOPort1EdgeSampleError').val(props.EIO.Port1EdgeSampleError);
                                $j('#txtIDPEIOPort1AnalogSampleRate').val(props.EIO.Port1AnalogSampleRate);
                                $j('#txtIDPEIOPort1AnalogSampleFilter').val(props.EIO.Port1AnalogSampleFilter);
                                $j('#txtIDPEIOPort1AnalogLowThreshold').val(props.EIO.Port1AnalogLowThreshold);
                                $j('#txtIDPEIOPort1AnalogHighThreshold').val(props.EIO.Port1AnalogHighThreshold);
                                $j('#ddlIDPEIOPort2Config').val(props.EIO.Port2Config);
                                $j('input:radio[name=rbIDPEIOPort2AlarmMsg][value=' + props.EIO.Port2AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEIOPort2AlarmLog][value=' + props.EIO.Port2AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEIOPort2EdgeDetect').val(props.EIO.Port2EdgeDetect);
                                $j('#txtIDPEIOPort2EdgeSampleCount').val(props.EIO.Port2EdgeSampleCount);
                                $j('#txtIDPEIOPort2EdgeSampleError').val(props.EIO.Port2EdgeSampleError);
                                $j('#txtIDPEIOPort2AnalogSampleRate').val(props.EIO.Port2AnalogSampleRate);
                                $j('#txtIDPEIOPort2AnalogSampleFilter').val(props.EIO.Port2AnalogSampleFilter);
                                $j('#txtIDPEIOPort2AnalogLowThreshold').val(props.EIO.Port2AnalogLowThreshold);
                                $j('#txtIDPEIOPort2AnalogHighThreshold').val(props.EIO.Port2AnalogHighThreshold);
                                $j('#ddlIDPEIOPort3Config').val(props.EIO.Port3Config);
                                $j('input:radio[name=rbIDPEIOPort3AlarmMsg][value=' + props.EIO.Port3AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEIOPort3AlarmLog][value=' + props.EIO.Port3AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEIOPort3EdgeDetect').val(props.EIO.Port3EdgeDetect);
                                $j('#txtIDPEIOPort3EdgeSampleCount').val(props.EIO.Port3EdgeSampleCount);
                                $j('#txtIDPEIOPort3EdgeSampleError').val(props.EIO.Port3EdgeSampleError);
                                $j('#txtIDPEIOPort3AnalogSampleRate').val(props.EIO.Port3AnalogSampleRate);
                                $j('#txtIDPEIOPort3AnalogSampleFilter').val(props.EIO.Port3AnalogSampleFilter);
                                $j('#txtIDPEIOPort3AnalogLowThreshold').val(props.EIO.Port3AnalogLowThreshold);
                                $j('#txtIDPEIOPort3AnalogHighThreshold').val(props.EIO.Port3AnalogHighThreshold);
                                $j('#ddlIDPEIOPort4Config').val(props.EIO.Port4Config);
                                $j('input:radio[name=rbIDPEIOPort4AlarmMsg][value=' + props.EIO.Port4AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEIOPort4AlarmLog][value=' + props.EIO.Port4AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEIOPort4EdgeDetect').val(props.EIO.Port4EdgeDetect);
                                $j('#txtIDPEIOPort4EdgeSampleCount').val(props.EIO.Port4EdgeSampleCount);
                                $j('#txtIDPEIOPort4EdgeSampleError').val(props.EIO.Port4EdgeSampleError);
                                $j('#txtIDPEIOPort4AnalogSampleRate').val(props.EIO.Port4AnalogSampleRate);
                                $j('#txtIDPEIOPort4AnalogSampleFilter').val(props.EIO.Port4AnalogSampleFilter);
                                $j('#txtIDPEIOPort4AnalogLowThreshold').val(props.EIO.Port4AnalogLowThreshold);
                                $j('#txtIDPEIOPort4AnalogHighThreshold').val(props.EIO.Port4AnalogHighThreshold);
                                $j('input:radio[name=rbIDPEIOTemperatureAlarmMsg][value=' + props.EIO.TemperatureAlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEIOTemperatureAlarmLog][value=' + props.EIO.TemperatureAlarmLog + ']').prop('checked', true);
                                $j('#txtIDPEIOTemperatureSampleRate').val(props.EIO.TemperatureSampleRate);
                                $j('#txtIDPEIOTemperatureSampleFilter').val(props.EIO.TemperatureSampleFilter);
                                $j('#txtIDPEIOTemperatureLowThreshold').val(props.EIO.TemperatureLowThreshold);
                                $j('#txtIDPEIOTemperatureHighThreshold').val(props.EIO.TemperatureHighThreshold);
                                $j('input:radio[name=rbIDPEIOPowerAlarmMsg][value=' + props.EIO.PowerAlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEIOPowerAlarmLog][value=' + props.EIO.PowerAlarmLog + ']').prop('checked', true);
                                $j('#txtIDPEIOPowerSampleRate').val(props.EIO.PowerSampleRate);
                                $j('#txtIDPEIOPowerSampleFilter').val(props.EIO.PowerSampleFilter);
                                $j('#txtIDPEIOPowerLowThreshold').val(props.EIO.PowerLowThreshold);
                                $j('#txtIDPEIOPowerHighThreshold').val(props.EIO.PowerHighThreshold);
                                $j('#ddlIDPEIOOutputSink5Default').val(props.EIO.OutputSink5Default);
                                $j('#ddlIDPEIOOutputSink6Default').val(props.EIO.OutputSink6Default);
                            }
                            if (props.GPRS != null) {
                                $j('#txtIDPGPRSSIM1PIN').val(props.GPRS.SIM1PIN);
                                $j('#txtIDPGPRSSIM1APN').val(props.GPRS.SIM1APN);
                                $j('#txtIDPGPRSSIM1Username').val(props.GPRS.SIM1Username);
                                $j('#txtIDPGPRSSIM1Password').val(props.GPRS.SIM1Password);
                                $j('#txtIDPGPRSSIM1DNS1').val(props.GPRS.SIM1DNS1);
                                $j('#txtIDPGPRSSIM1DNS2').val(props.GPRS.SIM1DNS2);
                                $j('#txtIDPGPRSSIM2PIN').val(props.GPRS.SIM2PIN);
                                $j('#txtIDPGPRSSIM2APN').val(props.GPRS.SIM2APN);
                                $j('#txtIDPGPRSSIM2Username').val(props.GPRS.SIM2Username);
                                $j('#txtIDPGPRSSIM2Password').val(props.GPRS.SIM2Password);
                                $j('#txtIDPGPRSSIM2DNS1').val(props.GPRS.SIM2DNS1);
                                $j('#txtIDPGPRSSIM2DNS2').val(props.GPRS.SIM2DNS2);
                                $j('#txtIDPGPRSServer1').val(props.GPRS.Server1);
                                $j('#txtIDPGPRSPort1').val(props.GPRS.Port1);
                                $j('#txtIDPGPRSPollingInterval').val(props.GPRS.PollingInterval);
                                $j('#ddlIDPGPRSActiveSIM').val(props.GPRS.ActiveSIM);
                                $j('#ddlIDPGPRSTargetMode').val(props.GPRS.TargetMode);
                            }
                            if (props.EEIO != null) {
                                $j('#ddlIDPEEIOInput1Config').val(props.EEIO.Input1Config);
                                $j('#ddlIDPEEIOInput1EdgeDetect').val(props.EEIO.Input1EdgeDetect);
                                $j('#txtIDPEEIOInput1EdgeFilterCount').val(props.EEIO.Input1EdgeFilterCount);
                                $j('#txtIDPEEIOInput1AnalogSampleRate').val(props.EEIO.Input1AnalogSampleRate);
                                $j('#txtIDPEEIOInput1AnalogFilterCount').val(props.EEIO.Input1AnalogFilterCount);
                                $j('#txtIDPEEIOInput1AnalogLowThreshold').val(props.EEIO.Input1AnalogLowThreshold);
                                $j('#txtIDPEEIOInput1AnalogHighThreshold').val(props.EEIO.Input1AnalogHighThreshold);
                                $j('input:radio[name=rbIDPEEIOInput1AlarmMsg][value=' + props.EEIO.Input1AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOInput1AlarmLog][value=' + props.EEIO.Input1AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEEIOInput2Config').val(props.EEIO.Input2Config);
                                $j('#ddlIDPEEIOInput2EdgeDetect').val(props.EEIO.Input2EdgeDetect);
                                $j('#txtIDPEEIOInput2EdgeFilterCount').val(props.EEIO.Input2EdgeFilterCount);
                                $j('#txtIDPEEIOInput2AnalogSampleRate').val(props.EEIO.Input2AnalogSampleRate);
                                $j('#txtIDPEEIOInput2AnalogFilterCount').val(props.EEIO.Input2AnalogFilterCount);
                                $j('#txtIDPEEIOInput2AnalogLowThreshold').val(props.EEIO.Input2AnalogLowThreshold);
                                $j('#txtIDPEEIOInput2AnalogHighThreshold').val(props.EEIO.Input2AnalogHighThreshold);
                                $j('input:radio[name=rbIDPEEIOInput2AlarmMsg][value=' + props.EEIO.Input2AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOInput2AlarmLog][value=' + props.EEIO.Input2AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEEIOInput3Config').val(props.EEIO.Input3Config);
                                $j('#ddlIDPEEIOInput3EdgeDetect').val(props.EEIO.Input3EdgeDetect);
                                $j('#txtIDPEEIOInput3EdgeFilterCount').val(props.EEIO.Input3EdgeFilterCount);
                                $j('#txtIDPEEIOInput3AnalogSampleRate').val(props.EEIO.Input3AnalogSampleRate);
                                $j('#txtIDPEEIOInput3AnalogFilterCount').val(props.EEIO.Input3AnalogFilterCount);
                                $j('#txtIDPEEIOInput3AnalogLowThreshold').val(props.EEIO.Input3AnalogLowThreshold);
                                $j('#txtIDPEEIOInput3AnalogHighThreshold').val(props.EEIO.Input3AnalogHighThreshold);
                                $j('input:radio[name=rbIDPEEIOInput3AlarmMsg][value=' + props.EEIO.Input3AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOInput3AlarmLog][value=' + props.EEIO.Input3AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEEIOInput4Config').val(props.EEIO.Input4Config);
                                $j('#ddlIDPEEIOInput4EdgeDetect').val(props.EEIO.Input4EdgeDetect);
                                $j('#txtIDPEEIOInput4EdgeFilterCount').val(props.EEIO.Input4EdgeFilterCount);
                                $j('#txtIDPEEIOInput4AnalogSampleRate').val(props.EEIO.Input4AnalogSampleRate);
                                $j('#txtIDPEEIOInput4AnalogFilterCount').val(props.EEIO.Input4AnalogFilterCount);
                                $j('#txtIDPEEIOInput4AnalogLowThreshold').val(props.EEIO.Input4AnalogLowThreshold);
                                $j('#txtIDPEEIOInput4AnalogHighThreshold').val(props.EEIO.Input4AnalogHighThreshold);
                                $j('input:radio[name=rbIDPEEIOInput4AlarmMsg][value=' + props.EEIO.Input4AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOInput4AlarmLog][value=' + props.EEIO.Input4AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEEIOInputDigital5EdgeDetect').val(props.EEIO.InputDigital5EdgeDetect);
                                $j('#txtIDPEEIOInputDigital5EdgeFilterCount').val(props.EEIO.InputDigital5EdgeFilterCount);
                                $j('input:radio[name=rbIDPEEIOInputDigital5AlarmMsg][value=' + props.EEIO.InputDigital5AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOInputDigital5AlarmLog][value=' + props.EEIO.InputDigital5AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEEIOInputDigital6EdgeDetect').val(props.EEIO.InputDigital6EdgeDetect);
                                $j('#txtIDPEEIOInputDigital6EdgeFilterCount').val(props.EEIO.InputDigital6EdgeFilterCount);
                                $j('input:radio[name=rbIDPEEIOInputDigital6AlarmMsg][value=' + props.EEIO.InputDigital6AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOInputDigital6AlarmLog][value=' + props.EEIO.InputDigital6AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEEIOInputDigital7EdgeDetect').val(props.EEIO.InputDigital7EdgeDetect);
                                $j('#txtIDPEEIOInputDigital7EdgeFilterCount').val(props.EEIO.InputDigital7EdgeFilterCount);
                                $j('input:radio[name=rbIDPEEIOInputDigital7AlarmMsg][value=' + props.EEIO.InputDigital7AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOInputDigital7AlarmLog][value=' + props.EEIO.InputDigital7AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEEIOInputDigital8EdgeDetect').val(props.EEIO.InputDigital8EdgeDetect);
                                $j('#txtIDPEEIOInputDigital8EdgeFilterCount').val(props.EEIO.InputDigital8EdgeFilterCount);
                                $j('input:radio[name=rbIDPEEIOInputDigital8AlarmMsg][value=' + props.EEIO.InputDigital8AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOInputDigital8AlarmLog][value=' + props.EEIO.InputDigital8AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEEIOInputDigital9EdgeDetect').val(props.EEIO.InputDigital9EdgeDetect);
                                $j('#txtIDPEEIOInputDigital9EdgeFilterCount').val(props.EEIO.InputDigital9EdgeFilterCount);
                                $j('input:radio[name=rbIDPEEIOInputDigital9AlarmMsg][value=' + props.EEIO.InputDigital9AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOInputDigital9AlarmLog][value=' + props.EEIO.InputDigital9AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEEIOInputDigital10EdgeDetect').val(props.EEIO.InputDigital10EdgeDetect);
                                $j('#txtIDPEEIOInputDigital10EdgeFilterCount').val(props.EEIO.InputDigital10EdgeFilterCount);
                                $j('input:radio[name=rbIDPEEIOInputDigital10AlarmMsg][value=' + props.EEIO.InputDigital10AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOInputDigital10AlarmLog][value=' + props.EEIO.InputDigital10AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEEIOInputDigital11EdgeDetect').val(props.EEIO.InputDigital11EdgeDetect);
                                $j('#txtIDPEEIOInputDigital11EdgeFilterCount').val(props.EEIO.InputDigital11EdgeFilterCount);
                                $j('input:radio[name=rbIDPEEIOInputDigital11AlarmMsg][value=' + props.EEIO.InputDigital11AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOInputDigital11AlarmLog][value=' + props.EEIO.InputDigital11AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEEIOInputIgnition12EdgeDetect').val(props.EEIO.InputIgnition12EdgeDetect);
                                $j('#txtIDPEEIOInputIgnition12EdgeFilterCount').val(props.EEIO.InputIgnition12EdgeFilterCount);
                                $j('input:radio[name=rbIDPEEIOInputIgnition12AlarmMsg][value=' + props.EEIO.InputIgnition12AlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOInputIgnition12AlarmLog][value=' + props.EEIO.InputIgnition12AlarmLog + ']').prop('checked', true);
                                $j('#ddlIDPEEIOutputSink14Default').val(props.EEIO.OutputSink14Default);
                                $j('#ddlIDPEEIOutputSink15Default').val(props.EEIO.OutputSink15Default);
                                $j('#ddlIDPEEIOutputSink16Default').val(props.EEIO.OutputSink16Default);
                                $j('#ddlIDPEEIOutputSink17Default').val(props.EEIO.OutputSink17Default);
                                $j('#ddlIDPEEIOutputSink18Default').val(props.EEIO.OutputSink18Default);
                                $j('#txtIDPEEIOTemperatureSampleRate').val(props.EEIO.TemperatureSampleRate);
                                $j('#txtIDPEEIOTemperatureFilterCount').val(props.EEIO.TemperatureFilterCount);
                                $j('#txtIDPEEIOTemperatureLowThreshold').val(props.EEIO.TemperatureLowThreshold);
                                $j('#txtIDPEEIOTemperatureHighThreshold').val(props.EEIO.TemperatureHighThreshold);
                                $j('input:radio[name=rbIDPEEIOTemperatureAlarmMsg][value=' + props.EEIO.TemperatureAlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOTemperatureAlarmLog][value=' + props.EEIO.TemperatureAlarmLog + ']').prop('checked', true);
                                $j('#txtIDPEEIOExternalPowerSampleRate').val(props.EEIO.ExternalPowerSampleRate);
                                $j('#txtIDPEEIOExternalPowerFilterCount').val(props.EEIO.ExternalPowerFilterCount);
                                $j('#txtIDPEEIOExternalPowerLowThreshold').val(props.EEIO.ExternalPowerLowThreshold);
                                $j('#txtIDPEEIOExternalPowerHighThreshold').val(props.EEIO.ExternalPowerHighThreshold);
                                $j('input:radio[name=rbIDPEEIOExternalPowerAlarmMsg][value=' + props.EEIO.ExternalPowerAlarmMsg + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPEEIOExternalPowerAlarmLog][value=' + props.EEIO.ExternalPowerAlarmLog + ']').prop('checked', true);
                            }
                            if (props.Accel != null) {
                                $j('#txtIDPAccelSleepInterval').val(props.Accel.SleepInterval);
                                $j('#txtIDPAccelGRange').val(props.Accel.GRange);
                                $j('#txtIDPAccelMotionEventThreshold').val(props.Accel.MotionEventThreshold);
                                $j('input:radio[name=rbIDPAccelMotionEventXAxisEn][value=' + props.Accel.MotionEventXAxisEn + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAccelMotionEventYAxisEn][value=' + props.Accel.MotionEventYAxisEn + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAccelMotionEventZAxisEn][value=' + props.Accel.MotionEventZAxisEn + ']').prop('checked', true);
                            }
                        }

                        // geofence parameters
                        var geofence = result.GeofenceParameters;
                        var props = geofence.GeofenceParameters;

                        if (geofence.LastRetrievedOn == null) {
                            $j('#AVLGeofenceParametersLastRetrieved').text(tracking.strings.NEVER);
                        } else {
                            $j('#AVLGeofenceParametersLastRetrieved').text(geofence.LastRetrievedOn);
                        }
                        if (geofence.LastQueriedOn == null) {
                            $j('#AVLGeofenceParametersLastQueried').text(tracking.strings.NEVER);
                        } else {
                            $j('#AVLGeofenceParametersLastQueried').text(geofence.LastQueriedOn);
                        }
                        if (props != null) {
                            $j('input:radio[name=rbIDPAVLGeofenceEnabled][value=' + props.Enabled + ']').prop('checked', true);
                            $j('#txtIDPAVLGeofenceInterval').val(props.Interval);
                            $j('#txtIDPAVLGeofenceHysteresis').val(props.Hysteresis);
                            $j('input:radio[name=rbIDPAVLGeofenceSendAlarm][value=' + props.SendAlarm + ']').prop('checked', true);
                        }

                        // avl parameters
                        var avl = result.AVLParameters;
                        var props = avl.ServiceProperties;
                        if (avl.LastRetrievedOn == null) {
                            $j('.AVLParametersLastRetrieved').text(tracking.strings.NEVER);
                        } else {
                            $j('.AVLParametersLastRetrieved').text(avl.LastRetrievedOn);
                        }
                        if (avl.LastQueriedOn == null) {
                            $j('.AVLParametersLastQueried').text(tracking.strings.NEVER);
                        } else {
                            $j('.AVLParametersLastQueried').text(avl.LastQueriedOn);
                        }
                        if (props != null) {
                            // populate
                            $j('#txtIDPAVLStationarySpeedThld').val(props.StationarySpeedThreshold);
                            $j('#txtIDPAVLStationaryDebounceTime').val(props.StationaryDebounceTime);
                            $j('#txtIDPAVLMovingDebounceTime').val(props.MovingDebounceTime);
                            $j('#txtIDPAVLMovingDistanceThld').val(props.MovingDistanceThreshold);
                            $j('#txtIDPAVLDefaultSpeedLimit').val(props.DefaultSpeedLimit);
                            $j('#txtIDPAVLSpeedingTimeOver').val(props.SpeedingTimeOver);
                            $j('#txtIDPAVLSpeedingTimeUnder').val(props.SpeedingTimeUnder);
                            $j('#txtIDPAVLLoggingPositionsInterval').val(props.LoggingPositionsInterval);
                            $j('#txtIDPAVLStationaryIntervalCell').val(props.StationaryIntervalCell);
                            $j('#txtIDPAVLMovingIntervalCell').val(props.MovingIntervalCell);
                            $j('#txtIDPAVLStationaryIntervalSat').val(props.StationaryIntervalSat);
                            $j('#txtIDPAVLMovingIntervalSat').val(props.MovingIntervalSat);
                            $j('#txtIDPAVLShmReportingHour').val(props.ShmReportingHour);
                            $j('#txtIDPAVLOdometerDistanceIncrement').val(props.OdometerDistanceIncrement);
                            $j('#txtIDPAVLOdometer').val(props.Odometer);
                            $j('#txtIDPAVLTurnThreshold').val(props.TurnThreshold);
                            $j('#txtIDPAVLTurnDebounceTime').val(props.TurnDebounceTime);
                            $j('#txtIDPAVLDistanceCellThld').val(props.DistanceCellThreshold);
                            $j('#txtIDPAVLDistanceSatThld').val(props.DistanceSatThreshold);
                            $j('#txtIDPAVLMaxDrivingTime').val(props.MaxDrivingTime);
                            $j('#txtIDPAVLMinRestTime').val(props.MinRestTime);
                            $j('#txtIDPAVLAirBlockageTime').val(props.AirBlockageTime);
                            $j('#txtIDPAVLMaxIdlingTime').val(props.MaxIdlingTime);
                            $j('input:radio[name=rbIDPAVLIdleTimerAutoRestart][value=' + props.IdleTimerAutoRestart + ']').prop('checked', true);
                            $j('#txtIDPAVLDefaultGeoDwellTime').val(props.DefaultGeoDwellTime);
                            $j('input:radio[name=rbIDPAVLGeoDwellTimerAutoRestart][value=' + props.GeoDwellTimerAutoRestart + ']').prop('checked', true);
                            $j('#txtIDPAVLImmobilizeOnBlockage').val(props.ImmobilizeOnBlockage);
                            $j('#txtIDPAVLGpsJamDebounceTime').val(props.GpsJamDebounceTime);
                            $j('#txtIDPAVLCellJamDebounceTime').val(props.CellJamDebounceTime);
                            // todo: Version
                            //$j('#txtIDPAVLLpmTrigger').val(props.LpmTrigger);
                            if (props.LpmTriggerDef != null) {
                                $j('input:radio[name=rbIDPAVLLpmTriggerIgnitionOff][value=' + props.LpmTriggerDef.IgnitionOff + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLLpmTriggerBuiltInBattery][value=' + props.LpmTriggerDef.BuiltInBattery + ']').prop('checked', true);
                            }
                            $j('#txtIDPAVLLpmEntryDelay').val(props.LpmEntryDelay);
                            $j('#txtIDPAVLLpmGeoInterval').val(props.LpmGeoInterval);
                            $j('#ddlIDPAVLLpmModemWakeupInterval').val(props.LpmModemWakeupInterval);
                            $j('#txtIDPAVLExitLpmOnTowingMaxTime').val(props.ExitLpmOnTowingMaxTime);
                            $j('#txtIDPAVLTowStartCheckInterval').val(props.TowStartCheckInterval);
                            $j('#txtIDPAVLTowStartDebCount').val(props.TowStartDebCount);
                            $j('#txtIDPAVLTowStopCheckInterval').val(props.TowStopCheckInterval);
                            $j('#txtIDPAVLTowStopDebCount').val(props.TowStopDebCount);
                            $j('#txtIDPAVLTowInterval').val(props.TowInterval);

                            $j('#txtIDPAVLHarshBrakingThld').val(props.HarshBrakingThld);
                            $j('#txtIDPAVLMinHarshBrakingTime').val(props.MinHarshBrakingTime);
                            $j('#txtIDPAVLReArmHarshBrakingTime').val(props.ReArmHarshBrakingTime);
                            $j('#txtIDPAVLHarshAccelThld').val(props.HarshAccelThld);
                            $j('#txtIDPAVLMinHarshAccelTime').val(props.MinHarshAccelTime);
                            $j('#txtIDPAVLReArmHarshAccelTime').val(props.ReArmHarshAccelTime);
                            $j('#txtIDPAVLAccidentThld').val(props.AccidentThld);
                            $j('#txtIDPAVLMinAccidentTime').val(props.MinAccidentTime);
                            $j('#txtIDPAVLSeatbeltDebounceTime').val(props.SeatbeltDebounceTime);
                            $j('#ddlIDPAVLDriverIdPort').val(props.DriverIdPort);
                            $j('#txtIDPAVLDriverIdPollingInterval').val(props.DriverIdPollingInterval);
                            $j('#txtIDPAVLDriverIdAutoLogoutDelay').val(props.DriverIdAutoLogoutDelay);
                            $j('input:radio[name=rbIDPAVLAccidentAccelDataCapture][value=' + props.AccidentAccelDataCapture + ']').prop('checked', true);
                            $j('input:radio[name=rbIDPAVLAccidentGpsDataCapture][value=' + props.AccidentGpsDataCapture + ']').prop('checked', true);
                            $j('#txtIDPAVLHarshTurnThld').val(props.HarshTurnThld);
                            $j('#txtIDPAVLMinHarshTurnTime').val(props.MinHarshTurnTime);
                            $j('#txtIDPAVLReArmHarshTurnTime').val(props.ReArmHarshTurnTime);

                            if (props.OptionalFieldsDef != null) {
                                $j('input:radio[name=rbIDPAVLOptionalFieldsInMsgsOdometer][value=' + props.OptionalFieldsDef.Odometer + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLOptionalFieldsInMsgsSensor1][value=' + props.OptionalFieldsDef.Sensor1 + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLOptionalFieldsInMsgsSensor2][value=' + props.OptionalFieldsDef.Sensor2 + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLOptionalFieldsInMsgsSensor3][value=' + props.OptionalFieldsDef.Sensor3 + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLOptionalFieldsInMsgsSensor4][value=' + props.OptionalFieldsDef.Sensor4 + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLOptionalFieldsInMsgsDriverId][value=' + props.OptionalFieldsDef.DriverId + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLOptionalFieldsInMsgsAvlStates][value=' + props.OptionalFieldsDef.AvlStates + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLOptionalFieldsInMsgsDigitalPorts][value=' + props.OptionalFieldsDef.DigitalPorts + ']').prop('checked', true);
                            }

                            $j('#txtIDPAVLPositionMsgInterval').val(props.PositionMsgInterval);

                            // i/o properties
                            $j('#ddlIDPAVLFuncDigOut1').val(props.FuncDigOut1);
                            $j('#ddlIDPAVLFuncDigOut2').val(props.FuncDigOut2);
                            $j('#ddlIDPAVLFuncDigOut3').val(props.FuncDigOut3);
                            $j('#ddlIDPAVLFuncDigOut4').val(props.FuncDigOut4);
                            $j('#ddlIDPAVLFuncDigOut5').val(props.FuncDigOut5);
                            $j('#ddlIDPAVLFuncDigOut6').val(props.FuncDigOut6);

                            $j('#ddlIDPAVLFuncDigInp1').val(props.FuncDigInp1);
                            $j('#ddlIDPAVLFuncDigInp2').val(props.FuncDigInp2);
                            $j('#ddlIDPAVLFuncDigInp3').val(props.FuncDigInp3);
                            $j('#ddlIDPAVLFuncDigInp4').val(props.FuncDigInp4);
                            $j('#ddlIDPAVLFuncDigInp5').val(props.FuncDigInp5);
                            $j('#ddlIDPAVLFuncDigInp6').val(props.FuncDigInp6);
                            $j('#ddlIDPAVLFuncDigInp7').val(props.FuncDigInp7);
                            $j('#ddlIDPAVLFuncDigInp8').val(props.FuncDigInp8);
                            $j('#ddlIDPAVLFuncDigInp9').val(props.FuncDigInp9);
                            $j('#ddlIDPAVLFuncDigInp10').val(props.FuncDigInp10);
                            $j('#ddlIDPAVLFuncDigInp11').val(props.FuncDigInp11);
                            $j('#ddlIDPAVLFuncDigInp12').val(props.FuncDigInp12);
                            $j('#ddlIDPAVLFuncDigInp13').val(props.FuncDigInp13);
                            $j('#txtIDPAVLSensorReportingInterval').val(props.SensorReportingInterval);
                            if (props.DigStatesDef != null) {
                                $j('input:radio[name=rbIDPAVLDigStatesDefIgnitionOn][value=' + props.DigStatesDef.IgnitionOn + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLDigStatesDefSeatbeltOff][value=' + props.DigStatesDef.SeatbeltBuckled + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLDigStatesDefSM1Active][value=' + props.DigStatesDef.SM1Active + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLDigStatesDefSM2Active][value=' + props.DigStatesDef.SM2Active + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLDigStatesDefSM3Active][value=' + props.DigStatesDef.SM3Active + ']').prop('checked', true);
                                $j('input:radio[name=rbIDPAVLDigStatesDefSM4Active][value=' + props.DigStatesDef.SM4Active + ']').prop('checked', true);
                            }
                            if (props.Sensor1Properties != null) {
                                $j('#txtIDPAVLSensor1SourceSIN').val(props.Sensor1Properties.SourceSIN);
                                $j('#txtIDPAVLSensor1SourcePIN').val(props.Sensor1Properties.SourcePIN);
                                $j('#txtIDPAVLSensor1NormalSampleInterval').val(props.Sensor1Properties.NormalSampleInterval);
                                $j('#txtIDPAVLSensor1LpmSampleInterval').val(props.Sensor1Properties.LpmSampleInterval);
                                $j('#txtIDPAVLSensor1MaxReportInterval').val(props.Sensor1Properties.MaxReportInterval);
                                $j('#txtIDPAVLSensor1ChangeThld').val(props.Sensor1Properties.ChangeThld);
                                $j('#txtIDPAVLSensor1MinThld').val(props.Sensor1Properties.MinThld);
                                $j('#txtIDPAVLSensor1MaxThld').val(props.Sensor1Properties.MaxThld);
                            }
                            if (props.Sensor2Properties != null) {
                                $j('#txtIDPAVLSensor2SourceSIN').val(props.Sensor2Properties.SourceSIN);
                                $j('#txtIDPAVLSensor2SourcePIN').val(props.Sensor2Properties.SourcePIN);
                                $j('#txtIDPAVLSensor2NormalSampleInterval').val(props.Sensor2Properties.NormalSampleInterval);
                                $j('#txtIDPAVLSensor2LpmSampleInterval').val(props.Sensor2Properties.LpmSampleInterval);
                                $j('#txtIDPAVLSensor2MaxReportInterval').val(props.Sensor2Properties.MaxReportInterval);
                                $j('#txtIDPAVLSensor2ChangeThld').val(props.Sensor2Properties.ChangeThld);
                                $j('#txtIDPAVLSensor2MinThld').val(props.Sensor2Properties.MinThld);
                                $j('#txtIDPAVLSensor2MaxThld').val(props.Sensor2Properties.MaxThld);
                            }
                            if (props.Sensor3Properties != null) {
                                $j('#txtIDPAVLSensor3SourceSIN').val(props.Sensor3Properties.SourceSIN);
                                $j('#txtIDPAVLSensor3SourcePIN').val(props.Sensor3Properties.SourcePIN);
                                $j('#txtIDPAVLSensor3NormalSampleInterval').val(props.Sensor3Properties.NormalSampleInterval);
                                $j('#txtIDPAVLSensor3LpmSampleInterval').val(props.Sensor3Properties.LpmSampleInterval);
                                $j('#txtIDPAVLSensor3MaxReportInterval').val(props.Sensor3Properties.MaxReportInterval);
                                $j('#txtIDPAVLSensor3ChangeThld').val(props.Sensor3Properties.ChangeThld);
                                $j('#txtIDPAVLSensor3MinThld').val(props.Sensor3Properties.MinThld);
                                $j('#txtIDPAVLSensor3MaxThld').val(props.Sensor3Properties.MaxThld);
                            }
                            if (props.Sensor4Properties != null) {
                                $j('#txtIDPAVLSensor4SourceSIN').val(props.Sensor4Properties.SourceSIN);
                                $j('#txtIDPAVLSensor4SourcePIN').val(props.Sensor4Properties.SourcePIN);
                                $j('#txtIDPAVLSensor4NormalSampleInterval').val(props.Sensor4Properties.NormalSampleInterval);
                                $j('#txtIDPAVLSensor4LpmSampleInterval').val(props.Sensor4Properties.LpmSampleInterval);
                                $j('#txtIDPAVLSensor4MaxReportInterval').val(props.Sensor4Properties.MaxReportInterval);
                                $j('#txtIDPAVLSensor4ChangeThld').val(props.Sensor4Properties.ChangeThld);
                                $j('#txtIDPAVLSensor4MinThld').val(props.Sensor4Properties.MinThld);
                                $j('#txtIDPAVLSensor4MaxThld').val(props.Sensor4Properties.MaxThld);
                            }

                        }

                        var utility = result.UtilityConfigurations;
                        var props = utility.UtilityConfigurations;
                        if (utility.LastRetrievedOn == null) {
                            $j('#IDPUtilityConfigurationsLastRetrieved').text(tracking.strings.NEVER);
                        } else {
                            $j('#IDPUtilityConfigurationsLastRetrieved').text(utility.LastRetrievedOn);
                        }
                        if (utility.LastQueriedOn == null) {
                            $j('#IDPUtilityConfigurationsLastQueried').text(tracking.strings.NEVER);
                        } else {
                            $j('#IDPUtilityConfigurationsLastQueried').text(utility.LastQueriedOn);
                        }
                        if (props != null) {
                            // todo: utility configuration properties
                        }

                        var arc = result.ARCProperties;
                        var props = arc.ARCProperties;
                        if (arc.LastRetrievedOn == null) {
                            $j('#IDPARCPropertiesLastRetrieved').text(tracking.strings.NEVER);
                        } else {
                            $j('#IDPARCPropertiesLastRetrieved').text(arc.LastRetrievedOn);
                        }
                        if (arc.LastQueriedOn == null) {
                            $j('#IDPARCPropertiesLastQueried').text(tracking.strings.NEVER);
                        } else {
                            $j('#IDPARCPropertiesLastQueried').text(arc.LastQueriedOn);
                        }
                        if (props != null) {
                            if (props.EIO1 != null) {
                                $j('#ddlIDPARCEIO1EventType').val(props.EIO1.EventType);
                                $j('#txtIDPARCEIO1TriggerDebounce').val(props.EIO1.TriggerDebounce);
                                $j('#txtIDPARCEIO1AnalogThresholds').val(props.EIO1.AnalogThresholds);
                                $j('#txtIDPARCEIO1Value').val(props.EIO1.Value);
                            }
                            if (props.EIO2 != null) {
                                $j('#ddlIDPARCEIO2EventType').val(props.EIO2.EventType);
                                $j('#txtIDPARCEIO2TriggerDebounce').val(props.EIO2.TriggerDebounce);
                                $j('#txtIDPARCEIO2AnalogThresholds').val(props.EIO2.AnalogThresholds);
                                $j('#txtIDPARCEIO2Value').val(props.EIO2.Value);
                            }
                            if (props.EIO3 != null) {
                                $j('#ddlIDPARCEIO3EventType').val(props.EIO3.EventType);
                                $j('#txtIDPARCEIO3TriggerDebounce').val(props.EIO3.TriggerDebounce);
                                $j('#txtIDPARCEIO3AnalogThresholds').val(props.EIO3.AnalogThresholds);
                                $j('#txtIDPARCEIO3Value').val(props.EIO3.Value);
                            }
                            if (props.EIO4 != null) {
                                $j('#ddlIDPARCEIO4EventType').val(props.EIO4.EventType);
                                $j('#txtIDPARCEIO4TriggerDebounce').val(props.EIO4.TriggerDebounce);
                                $j('#txtIDPARCEIO4AnalogThresholds').val(props.EIO4.AnalogThresholds);
                                $j('#txtIDPARCEIO4Value').val(props.EIO4.Value);
                            }
                            $j('#txtIDPARCHeartbeatInterval').val(props.HeartbeatInterval);
                            $j('input:radio[name=rbIDPARCIsSatQualityReported][value=' + props.IsSatQualityReported + ']').prop('checked', true);
                            $j('#ddlIDPARCModemPowerMode').val(props.ModemPowerMode);
                            $j('#ddlIDPARCModemWakeUpInterval').val(props.ModemWakeUpInterval);
                            $j('input:radio[name=rbIDPARCFlashLedOnTransmit][value=' + props.FlashLedOnTransmit + ']').prop('checked', true);
                            $j('input:radio[name=rbIDPARCEnablePositionReporting][value=' + props.EnablePositionReporting + ']').prop('checked', true);
                            $j('#txtIDPARCMotionTestInterval').val(props.MotionTestInterval);
                            $j('#txtIDPARCDistanceThreshold').val(props.DistanceThreshold);
                            $j('#txtIDPARCLedInstallFlashTime').val(props.LedInstallFlashTime);
                            $j('#txtIDPARCContractHour').val(props.ContractHour);
                            $j('#txtIDPARCMaxContractDispersion').val(props.MaxContractDispersion);
                            $j('#txtIDPARCDispersionOffsetSeconds').val(props.DispersionOffsetSeconds);
                            $j('input:radio[name=rbIDPARCSendContractDayValues][value=' + props.SendContractDayValues + ']').prop('checked', true);
                            $j('#txtIDPARCSupplyVoltageThreshold').val(props.SupplyVoltageThreshold);
                            $j('#txtIDPARCSupplyVoltageTriggerDebounce').val(props.SupplyVoltageTriggerDebounce);
                            $j('#txtIDPARCSupplyVoltage').val(props.SupplyVoltage);
                            $j('input:radio[name=rbIDPARCIsDebugEnabled][value=' + props.IsDebugEnabled + ']').prop('checked', true);
                            $j('#txtIDPARCVersion').val(props.Version);
                        }

                        // garmin parameters
                        var garmin = result.GarminProperties;
                        var props = garmin.GarminProperties;

                        if (garmin.LastRetrievedOn == null) {
                            $j('#IDPGarminParametersLastRetrieved').text(tracking.strings.NEVER);
                        } else {
                            $j('#IDPGarminParametersLastRetrieved').text(garmin.LastRetrievedOn);
                        }
                        if (garmin.LastQueriedOn == null) {
                            $j('#IDPGarminParametersLastQueried').text(tracking.strings.NEVER);
                        } else {
                            $j('#IDPGarminParametersLastQueried').text(garmin.LastQueriedOn);
                        }
                        if (props != null) {
                            // populate
                            $j('input:radio[name=rbIDPGarminFmiEnabled][value=' + props.FmiEnabled + ']').prop('checked', true);
                            $j('input:radio[name=rbIDPGarminSafeModeEnabled][value=' + props.SafeModeEnabled + ']').prop('checked', true);
                            $j('#txtIDPGarminDispatchIconText').val(props.DispatchIconText);
                            $j('#ddlIDPGarminDeleteGarminData').val(props.DeleteGarminData);
                            $j('#txtIDPGarminWelcomeMessage').val(props.WelcomeMessage);
                            $j('input:radio[name=rbIDPGarminOpenTextMsgAllowed][value=' + props.OpenTextMsgAllowed + ']').prop('checked', true);
                            $j('input:radio[name=rbIDPGarminTextingRequiresDriverId][value=' + props.TextingRequiresDriverId + ']').prop('checked', true);
                            $j('input:radio[name=rbIDPGarminMultiDriverEnabled][value=' + props.MultiDriverEnabled + ']').prop('checked', true);
                            $j('input:radio[name=rbIDPGarminDriverPwdEnabled][value=' + props.DriverPwdEnabled + ']').prop('checked', true);
                            $j('input:radio[name=rbIDPGarminDriverIdCaseSensitive][value=' + props.DriverIdCaseSensitive + ']').prop('checked', true);
                            $j('input:radio[name=rbIDPGarminPasswordCaseSensitive][value=' + props.PasswordCaseSensitive + ']').prop('checked', true);
                            $j('input:radio[name=rbIDPGarminReportFailedLogins][value=' + props.ReportFailedLogins + ']').prop('checked', true);
                            $j('#txtIDPGarminAutoArrivalTime').val(props.AutoArrivalTime);
                            $j('#txtIDPGarminAutoArrivalDistance').val(props.AutoArrivalDistance);
                            $j('#ddlIDPGarminSpeedLimitMode').val(props.SpeedLimitMode);
                            $j('#txtIDPGarminSpeedLimitThreshold').val(props.SpeedLimitThreshold);
                            $j('#txtIDPGarminSpeedLimitTimeOver').val(props.SpeedLimitTimeOver);
                            $j('#txtIDPGarminSpeedLimitTimeUnder').val(props.SpeedLimitTimeUnder);
                            $j('input:radio[name=rbIDPGarminSpeedLimitAlertUser][value=' + props.SpeedLimitAlertUser + ']').prop('checked', true);
                            $j('input:radio[name=rbIDPGarminHosEnabled][value=' + props.HosEnabled + ']').prop('checked', true);
                            $j('input:radio[name=rbIDPGarminSubmitFormAsFile][value=' + props.SubmitFormAsFile + ']').prop('checked', true);
                        }

                        // immobilizer/driver admin
                        var immobilizer = result.ImmobilizerProperties;
                        var props = immobilizer.Properties;
                        if (immobilizer.LastRetrievedOn == null) {
                            $('#IDPImmobilizerParametersLastRetrieved').text(tracking.strings.NEVER);
                        } else {
                            $('#IDPImmobilizerParametersLastRetrieved').text(immobilizer.LastRetrievedOn);
                        }
                        if (immobilizer.LastQueriedOn == null) {
                            $('#IDPImmobilizerParametersLastQueried').text(tracking.strings.NEVER);
                        } else {
                            $('#IDPImmobilizerParametersLastQueried').text(immobilizer.LastQueriedOn);
                        }
                        if (props != null) {
                            $('#txtIDPImmobilizerDriverLoginCheckInterval').val(props.DriverLoginCheckInterval);
                            $('input:radio[name=rbIDPImmobilizerNotifyOnlyWhenMoving][value=' + props.NotifyOnlyWhenMoving + ']').prop('checked', true);
                        }

                        // updater 
                        populateIDPUpdaterInformation(result.UpdaterInfo);

                        // crypto
                        populateIDPCryptoInformation(result.CryptoInfo);

                        // gateway counters
                        var counters = result.GatewayCounters;
                        $j('#CounterSatellite').text(counters.CounterSatellite);
                        $j('#CounterSatelliteBytes').text(counters.CounterSatelliteBytes);
                        $j('#CounterGPRS').text(counters.CounterGPRS);
                        $j('#CounterGPRSBytes').text(counters.CounterGPRSBytes);
                        if (counters.CountersResetOn == null) {
                            $j('#AVLCountersLastReset').text(tracking.strings.NEVER);
                        } else {
                            $j('#AVLCountersLastReset').text(counters.CountersResetOn);
                        }

                        // garmin info
                        var garminInfos = result.GarminInfo;
                        var infoData = [];
                        for (var i = 0; i < garminInfos.Responses.length; i++) {
                            var item = garminInfos.Responses[i];
                            if (item.Info != null) {
                                var protocols = item.Info.SupportedProtocols;
                                if (protocols != null) {
                                    protocols = protocols.split(",").join(", ");
                                }
                                infoData.push([
                                    item.Info.Latitude,
                                    item.Info.Longitude,
                                    protocols,
                                    item.Info.UnitId,
                                    item.Info.ProductId,
                                    item.Info.SwVer,
                                    item.CreatedOn
                                ]);
                            }
                        }
                        $('#IDPGarminInfos').dataTable({
                            data: infoData,
                            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
                            'lengthChange': false, 'paging': true, 'pageLength': 3, 'deferRender': true,
                            'order': [[6, 'desc']],
                            'columnDefs': [{
                                'targets': '_all',
                                'render': $.fn.dataTable.render.text()
                            }],
                            'columns': [
	                            {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {}
                            ],
                            'language': tracking.strings.DATATABLE
                        });

                        // diagnostics
                        var diagnostics = result.AVLDiagnostics;
                        var diagData = [];

                        for (var i = 0; i < diagnostics.Responses.length; i++) {
                            var item = diagnostics.Responses[i];
                            if (item.Diagnostics != null) {
                                diagData.push([
                                    (item.Diagnostics.SatCnr / 100),
                                    item.Diagnostics.CellRssi,
                                    item.Diagnostics.PowerUpTime,
                                    item.Diagnostics.BattChargerState,
                                    (item.Diagnostics.BattVoltage != null) ? item.Diagnostics.BattVoltage + 'mV' : '',
                                    (item.Diagnostics.ExtVoltage != null) ? item.Diagnostics.ExtVoltage + 'mV' : '',
                                    (item.Diagnostics.Temperature != null) ? item.Diagnostics.Temperature + '°C' : '',
                                    (item.Diagnostics.DigitalPorts.Line1 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line2 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line3 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line4 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line5 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line6 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line7 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line8 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line9 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line10 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line11 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line12 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line13 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line14 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line15 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line16 ? tracking.strings.ON : tracking.strings.OFF),
                                    (item.Diagnostics.DigitalPorts.Line17 ? tracking.strings.ON : tracking.strings.OFF),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.InLPM),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.OnMainPower),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.Speeding),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.Moving),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.Towing),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.GPSJammed),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.CellJammed),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.Tamper),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.Blocked),
                                    //disabledCheckbox(item.Diagnostics.AvlStates.AccelerometerEnabled),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.SeatbeltViolation),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.IgnitionOn),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.EngineIdling),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.SM1Active),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.SM2Active),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.SM3Active),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.SM4Active),
                                    disabledCheckboxEl(item.Diagnostics.AvlStates.GeoDwelling),
                                    disabledCheckboxEl(item.Diagnostics.DigStatesDefMap.IgnitionOn),
                                    disabledCheckboxEl(item.Diagnostics.DigStatesDefMap.SeatbeltBuckled),
                                    disabledCheckboxEl(item.Diagnostics.DigStatesDefMap.SM1Active),
                                    disabledCheckboxEl(item.Diagnostics.DigStatesDefMap.SM2Active),
                                    disabledCheckboxEl(item.Diagnostics.DigStatesDefMap.SM3Active),
                                    disabledCheckboxEl(item.Diagnostics.DigStatesDefMap.SM4Active),
                                    item.CreatedOn
                                ]);
                            }
                        }

                        $('#AVLDiagnostics').dataTable({
                            data: diagData,
                            "scrollX": "100%",
                            //"sScrollXInner": "100%",
                            "scrollCollapse": false,
                            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': true,
                            'lengthChange': false, 'paging': true, 'pageLength': 3, 'deferRender': true,
                            'order': [[47, 'desc']],
                            'columnDefs': [{
                                'targets': '_all',
                                'render': $.fn.dataTable.render.text()
                            }],
                            'columns': [
	                            { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px' },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px', render: renderDomElement },
                                { 'sWidth': '50px' }
                            ],
                            'language': tracking.strings.DATATABLE
                        });

                        var commandLog = result.CommandLog;
                        renderIDPCommandLog(commandLog.Commands);

                        console.log(result);
                        var serviceLists = result.ServiceLists;
                        var serviceVersions = serviceLists.Versions;
                        var serviceListData = [];
                        var activeServices = null;

                        for (var i = 0; i < serviceLists.Responses.length; i++) {
                            var item = serviceLists.Responses[i];
                            if (item.ServiceList != null) {
                                if (activeServices == null) {
                                    activeServices = {};
                                    if (item.ServiceList.Report == true) {
                                        activeServices.Report = serviceVersions.Report;
                                    }
                                    if (item.ServiceList.Position == true) {
                                        activeServices.Position = serviceVersions.Position;
                                    }
                                    if (item.ServiceList.AVL == true) {
                                        activeServices.AVL = serviceVersions.AVL;
                                    }
                                    if (item.ServiceList.GarminFMI == true) {
                                        activeServices.GarminFMI = serviceVersions.GarminFMI;
                                    }
                                    if (item.ServiceList.GarminDispatch == true) {
                                        activeServices.GarminDispatch = serviceVersions.GarminDispatch;
                                    }
                                    if (item.ServiceList.GarminExt == true) {
                                        activeServices.GarminExt = serviceVersions.GarminExt;
                                    }
                                    if (item.ServiceList.J1939 == true) {
                                        activeServices.J1939 = serviceVersions.J1939;
                                    }
                                    if (item.ServiceList.Analytics == true) {
                                        activeServices.Analytics = serviceVersions.Analytics;
                                    }
                                    if (item.ServiceList.ARC == true) {
                                        activeServices.ARC = serviceVersions.ARC;
                                    }
                                    if (item.ServiceList.Utility == true) {
                                        activeServices.Utility = serviceVersions.Utility;
                                    }
                                    if (item.ServiceList.PackageVersion != null) {
                                        activeServices.PackageVersion = item.ServiceList.PackageVersion;
                                    }
                                    if (item.ServiceList.FLS == true) {
                                        activeServices.FLS = serviceVersions.FLS;
                                    }
                                    if (item.ServiceList.VMS == true) {
                                        activeServices.VMS = serviceVersions.VMS;
                                    }
                                    if (item.ServiceList.Unibox == true) {
                                        activeServices.Unibox = serviceVersions.Unibox;
                                    }
                                    if (item.ServiceList.Updater == true) {
                                        activeServices.Updater = serviceVersions.Updater;
                                    }
                                    if (item.ServiceList.Crypto == true) {
                                        activeServices.Crypto = serviceVersions.Crypto;
                                    }
                                }
                                serviceListData.push([
                                    disabledCheckboxEl(item.ServiceList.Report),
                                    disabledCheckboxEl(item.ServiceList.Position),
                                    disabledCheckboxEl(item.ServiceList.AVL),
                                    disabledCheckboxEl(item.ServiceList.GarminFMI),
                                    disabledCheckboxEl(item.ServiceList.GarminDispatch),
                                    disabledCheckboxEl(item.ServiceList.GarminExt),
                                    disabledCheckboxEl(item.ServiceList.J1939),
                                    disabledCheckboxEl(item.ServiceList.Analytics),
                                    disabledCheckboxEl(item.ServiceList.Utility),
                                    disabledCheckboxEl(item.ServiceList.ARC),
                                    disabledCheckboxEl(item.ServiceList.FLS),
                                    disabledCheckboxEl(item.ServiceList.VMS),
                                    disabledCheckboxEl(item.ServiceList.Unibox),
                                    disabledCheckboxEl(item.ServiceList.EyeAlert),
                                    disabledCheckboxEl(item.ServiceList.Immobilizer),
                                    disabledCheckboxEl(item.ServiceList.Updater),
                                    disabledCheckboxEl(item.ServiceList.Crypto),
                                    item.CreatedOn
                                ]);
                            }
                        }
                        // get latest Service List (if any)
                        if (serviceLists.Responses.length > 0) {
                            var latestServiceList = serviceLists.Responses[0].ServiceList;
                            var body = document.getElementById('IDPServiceListing').querySelector('tbody');
                            var sins = [];
                            var serviceListNames = [];
                            serviceListNames[19] = 'Report';
                            serviceListNames[20] = 'Position';
                            serviceListNames[65] = 'ARC';
                            serviceListNames[115] = 'VMS';
                            serviceListNames[121] = 'J1939';
                            serviceListNames[122] = 'Analytics';
                            serviceListNames[124] = 'GarminDispatch';
                            serviceListNames[125] = 'GarminFMI';
                            serviceListNames[126] = 'AVL';
                            serviceListNames[127] = 'Utility';
                            serviceListNames[128] = 'GarminExt';
                            serviceListNames[130] = 'FLS';
                            serviceListNames[136] = 'GSE_CustomFields';
                            serviceListNames[148] = 'GSE_EyeAlert';
                            serviceListNames[165] = 'Unibox';
                            serviceListNames[160] = 'GSE_Immobilizer';
                            serviceListNames[199] = 'CellMonitor';
                            serviceListNames[224] = 'GSE_Garmin';
                            serviceListNames[226] = 'GSE_AAT';
                            serviceListNames[228] = 'GSE_Updater';
                            serviceListNames[255] = 'GSE_Crypto';

                            for (var sin in serviceListNames) {
                                if (latestServiceList.EnabledSINs.indexOf(parseInt(sin)) != -1) {
                                    if (sins.indexOf(parseInt(sin)) == -1) {
                                        sins.push(parseInt(sin));
                                    }
                                } else if (latestServiceList.DisabledSINs.indexOf(parseInt(sin)) != -1) {
                                    if (sins.indexOf(sin) == -1) {
                                        sins.push(parseInt(sin));
                                    }
                                }
                            }
                            // add user custom services (SIN > 130)
                            for (var i = 0 ; i < latestServiceList.EnabledSINs.length; i++) {
                                if (latestServiceList.EnabledSINs[i] > 130) {
                                    if (sins.indexOf(parseInt(latestServiceList.EnabledSINs[i])) == -1) {
                                        sins.push(parseInt(latestServiceList.EnabledSINs[i]));
                                    }
                                }
                            }
                            for (var i = 0; i < latestServiceList.DisabledSINs.length; i++) {
                                if (latestServiceList.DisabledSINs[i] > 130) {
                                    if (sins.indexOf(parseInt(latestServiceList.DisabledSINs[i])) == -1) {
                                        sins.push(parseInt(latestServiceList.DisabledSINs[i]));
                                    }
                                }
                            }

                            var rows = [];
                            for (var i = 0; i < sins.length; i++) {
                                var sin = sins[i];
                                var disabled = (latestServiceList.DisabledSINs.indexOf(parseInt(sin)) !== -1);
                                var name = serviceListNames[sin];
                                rows.push(el('tr', [
                                    el('td'),
                                    el('td', sin),
                                    el('td', name),
                                    el('td', disabledCheckboxEl(!disabled)),
                                    el('td', [
                                        el('div.form-check', [
                                            el('input#SIN' + sin + 'Enable.form-check-input.enable', { type: 'radio', name: 'SIN' + sin, value: '1', dataset: { sin: sin }}),
                                            el('label.form-check-label', { for: 'SIN' + sin + 'Enable' }, tracking.strings.ENABLE)
                                        ]),
                                        el('div.form-check', [
                                            el('input#SIN' + sin + 'Disable.form-check-input.disable', { type: 'radio', name: 'SIN' + sin, value: '0', dataset: { sin: sin } }),
                                            el('label.form-check-label', { for: 'SIN' + sin + 'Disable' }, tracking.strings.DISABLE)
                                        ]),
                                    ])
                                ]));
                            }
                            setChildren(body, rows);
                        }

                        $j('#IDPAgentVersions li').hide();
                        $j('#IDPAgentPackageVersion').hide();
                        if (activeServices != null) {
                            console.log(activeServices);
                            if (typeof activeServices.Report != 'undefined') {
                                $j('#IDPAgentVersionReport').show().find('span.version').text((activeServices.Report == null) ? tracking.strings.UNKNOWN : activeServices.Report);
                            }
                            if (typeof activeServices.Position != 'undefined') {
                                $j('#IDPAgentVersionPosition').show().find('span.version').text((activeServices.Position == null) ? tracking.strings.UNKNOWN : activeServices.Position);
                            }
                            if (typeof activeServices.FLS != 'undefined') {
                                $j('#IDPAgentVersionFLS').show().find('span.version').text((activeServices.FLS == null) ? tracking.strings.UNKNOWN : activeServices.FLS);
                            }
                            if (typeof activeServices.AVL != 'undefined') {
                                $j('#IDPAgentVersionAVL').show().find('span.version').text((activeServices.AVL == null) ? tracking.strings.UNKNOWN : activeServices.AVL);
                            }
                            if (typeof activeServices.GarminDispatch != 'undefined') {
                                $j('#IDPAgentVersionGarminDispatch').show().find('span.version').text((activeServices.GarminDispatch == null) ? tracking.strings.UNKNOWN : activeServices.GarminDispatch);
                            }
                            if (typeof activeServices.ARC != 'undefined') {
                                $j('#IDPAgentVersionARC').show().find('span.version').text((activeServices.ARC == null) ? tracking.strings.UNKNOWN : activeServices.ARC);
                            }
                            if (typeof activeServices.GarminFMI != 'undefined') {
                                $j('#IDPAgentVersionGarminFMI').show().find('span.version').text((activeServices.GarminFMI == null) ? tracking.strings.UNKNOWN : activeServices.GarminFMI);
                            }
                            if (typeof activeServices.GarminExt != 'undefined') {
                                $j('#IDPAgentVersionGarminExt').show().find('span.version').text((activeServices.GarminExt == null) ? tracking.strings.UNKNOWN : activeServices.GarminExt);
                            }
                            if (typeof activeServices.J1939 != 'undefined') {
                                $j('#IDPAgentVersionJ1939').show().find('span.version').text((activeServices.J1939 == null) ? tracking.strings.UNKNOWN : activeServices.J1939);
                            }
                            if (typeof activeServices.Analytics != 'undefined') {
                                $j('#IDPAgentVersionAnalytics').show().find('span.version').text((activeServices.Analytics == null) ? tracking.strings.UNKNOWN : activeServices.Analytics);
                            }
                            if (typeof activeServices.Utility != 'undefined') {
                                $j('#IDPAgentVersionUtility').show().find('span.version').text((activeServices.Utility == null) ? tracking.strings.UNKNOWN : activeServices.Utility);
                            }
                            if (typeof activeServices.VMS != 'undefined') {
                                $j('#IDPAgentVersionVMS').show().find('span.version').text((activeServices.VMS == null) ? tracking.strings.UNKNOWN : activeServices.VMS);
                            }
                            if (typeof activeServices.Unibox != 'undefined') {
                                $j('#IDPAgentVersionUnibox').show().find('span.version').text((activeServices.Unibox == null) ? tracking.strings.UNKNOWN : activeServices.Unibox);
                            }
                            if (typeof activeServices.Updater != 'undefined') {
                                $j('#IDPAgentVersionUpdater').show().find('span.version').text((activeServices.Updater == null) ? tracking.strings.UNKNOWN : activeServices.Updater);
                            }
                            if (typeof activeServices.Crypto != 'undefined') {
                                $j('#IDPAgentVersionCrypto').show().find('span.version').text((activeServices.Crypto == null) ? tracking.strings.UNKNOWN : activeServices.Crypto);
                            }
                            if (typeof activeServices.PackageVersion != 'undefined') {
                                var version = (activeServices.PackageVersion == null) ? tracking.strings.UNKNOWN : activeServices.PackageVersion;
                                $j('#IDPAgentPackageVersion').show().find('span').text(version);
                            }
                        }

                        $('#IDPServiceLists').dataTable({
                            data: serviceListData,
                            'scrollX': '100%', 'scrollCollapse': false,
                            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
                            'lengthChange': false, 'paging': true, 'pageLength': 3, 'deferRender': true,
                            'order': [[17, 'desc']],
                            'columnDefs': [{
                                'targets': '_all',
                                'render': renderDomElement
                            }],
                            'columns': [
                                {},
                                {},
                                {},
	                            {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                { render: $.fn.dataTable.render.text() }
                            ],
                            'language': tracking.strings.DATATABLE
                        });

                        var geofenceStatus = result.GeofenceStatus;
                        if (geofenceStatus.Status != null) {
                            $j('#IDPGeofenceStatusDeviceLastRetrieved').text(geofenceStatus.Status.CreatedOn);
                        }
                        if (geofenceStatus.Combined != null) {
                            var geoStatusData = [];
                            for (var i = 0; i < geofenceStatus.Combined.length; i++) {
                                var item = geofenceStatus.Combined[i];
                                var enabled = null;
                                if (item.Enabled) {
                                    enabled = el('button.DisableGeofence', { dataset: { fenceId: item.DeviceId } }, tracking.strings.DISABLE);
                                }
                                geoStatusData.push([
                                    item.DeviceId,
                                    disabledCheckboxEl(item.Enabled),
                                    disabledCheckboxEl(item.Inside),
                                    disabledCheckboxEl(item.Outside),
                                    item.PortalId,
                                    item.Fence,
                                    item.Alert,
                                    item.Filters,
                                    enabled
                                ]);
                            }
                            $('#IDPGeofenceStatus').dataTable({
                                data: geoStatusData,
                                'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
                                'lengthChange': false, 'paging': false, 'pageLength': 10, 'deferRender': true,
                                'columnDefs': [{
                                    'targets': '_all',
                                    'render': $.fn.dataTable.render.text()
                                }],
                                'columns': [
                                    {},
                                    { render: renderDomElement },
                                    { render: renderDomElement },
                                    { render: renderDomElement },
                                    {},
                                    {},
                                    {},
                                    {},
                                    { render: renderDomElement }
                                ],
                                'language': tracking.strings.DATATABLE,
                                'drawCallback': function (oSettings) {
                                }
                            });
                        }

                        // sleep schedules
                        var modemRegistrations = result.ModemRegistrations;
                        var modemData = [];

                        for (var i = 0; i < modemRegistrations.Responses.length; i++) {
                            var item = modemRegistrations.Responses[i];
                            if (item.ModemRegistration != null) {
                                modemData.push([
                                    item.ModemRegistration.HardwareMajorVersion,
                                    item.ModemRegistration.HardwareMinorVersion,
                                    item.ModemRegistration.SoftwareMajorVersion,
                                    item.ModemRegistration.SoftwareMinorVersion,
                                    item.ModemRegistration.Product,
                                    item.ModemRegistration.WakeupPeriod,
                                    item.ModemRegistration.LastResetReason,
                                    item.ModemRegistration.VirtualCarrier,
                                    item.ModemRegistration.Beam,
                                    item.ModemRegistration.Vain,
                                    item.ModemRegistration.OperatorTxState,
                                    item.ModemRegistration.UserTxState,
                                    item.ModemRegistration.BroadcastIDCount,
                                    item.CreatedOn
                                ]);
                            }
                        }

                        $('#IDPModemRegistrations').dataTable({
                            data: modemData,
                            'scrollX': '100%', 'scrollCollapse': false,
                            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
                            'lengthChange': false, 'paging': true, 'pageLength': 3, 'deferRender': true,
                            'order': [[13, 'desc']],
                            'columnDefs': [{
                                'targets': '_all',
                                'render': $.fn.dataTable.render.text()
                            }],
                            'columns': [
	                            {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {},
                                {}
                            ],
                            'language': tracking.strings.DATATABLE
                        });

                        // modem registrations
                        var sleepSchedules = result.SleepSchedules;
                        var sleepData = [];

                        for (var i = 0; i < sleepSchedules.Responses.length; i++) {
                            var item = sleepSchedules.Responses[i];
                            if (item.SleepSchedule != null) {
                                sleepData.push([
                                    item.SleepSchedule.WakeupPeriod,
                                    disabledCheckboxEl(item.SleepSchedule.MobileInitiated),
                                    item.SleepSchedule.MessageReference,
                                    item.CreatedOn
                                ]);
                            }
                        }

                        $('#IDPSleepSchedules').dataTable({
                            data: sleepData,
                            'scrollX': '100%', 'scrollCollapse': false,
                            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
                            'lengthChange': false, 'paging': true, 'pageLength': 3, 'deferRender': true,
                            'order': [[3, 'desc']],
                            'columnDefs': [{
                                'targets': '_all',
                                'render': $.fn.dataTable.render.text()
                            }],
                            'columns': [
	                            {},
                                { render: renderDomElement },
                                {},
                                {}
                            ],
                            'language': tracking.strings.DATATABLE
                        });

                        // gsm health checks
                        var healthChecks = result.GSMHealthChecks;
                        var itemData = [];

                        for (var i = 0; i < healthChecks.Responses.length; i++) {
                            var item = healthChecks.Responses[i];
                            if (item.HealthCheck != null) {
                                itemData.push([
                                    item.HealthCheck.ActiveSIM,
                                    disabledCheckboxEl(item.HealthCheck.NetworkAllowed),
                                    item.HealthCheck.LinkStability,
                                    item.HealthCheck.RSSI,
                                    disabledCheckboxEl(item.HealthCheck.PowerOnStatus),
                                    disabledCheckboxEl(item.HealthCheck.CoverOpen),
                                    item.HealthCheck.SIMStatus,
                                    item.HealthCheck.NetworkStatus,
                                    disabledCheckboxEl(item.HealthCheck.APNConnected),
                                    disabledCheckboxEl(item.HealthCheck.ServerConnected),
                                    item.HealthCheck.CurrentMode,
                                    disabledCheckboxEl(item.HealthCheck.SIM1Present),
                                    disabledCheckboxEl(item.HealthCheck.SIM2Present),
                                    item.CreatedOn//,
                                    //item.Source
                                ]);
                            }
                        }

                        $('#GSMHealthChecks').dataTable({
                            data: itemData,
                            'scrollX': '100%', 'scrollCollapse': false,
                            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
                            'lengthChange': false, 'paging': true, 'pageLength': 3, 'deferRender': true,
                            'order': [[12, 'desc']],
                            'columnDefs': [{
                                'targets': '_all',
                                'render': $.fn.dataTable.render.text()
                            }],
                            'columns': [
	                            {},
                                { render: renderDomElement },
                                {},
                                {},
                                { render: renderDomElement },
                                { render: renderDomElement },
                                {},
                                {},
                                { render: renderDomElement },
                                { render: renderDomElement },
                                {},
                                { visible: ((asset.DeviceId == tracking.devices.SKYWAVE_782) || (asset.DeviceId == tracking.devices.SKYWAVE_782_CELL) || (asset.DeviceId == tracking.devices.SKYWAVE_ST9100)), render: renderDomElement }, // 782-only
                                { visible: ((asset.DeviceId == tracking.devices.SKYWAVE_782) || (asset.DeviceId == tracking.devices.SKYWAVE_782_CELL) || (asset.DeviceId == tracking.devices.SKYWAVE_ST9100)), render: renderDomElement }, // 782-only
                                {}//,
                                //{}
                            ],
                            'language': tracking.strings.DATATABLE
                        });

                    } else {
                        // message failure, keep text field to allow retry
                        formShowErrorMessage(status, tracking.strings.MSG_IDP_INFORMATION_ERROR);
                        if (result.ErrorMessage != null && result.ErrorMessage != '') {
                            formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                        }
                    }
                }
                toggleLoadingMessage(false, 'idp-information');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_IDP_INFORMATION_ERROR);
                toggleLoadingMessage(false, 'idp-information');
            }
        });
    }

    function toggleEventContainer(isForAlerts) {
        $j('#event-panel-container').toggle();
        if ($j('#event-panel-container').is(':visible')) {
            if (isForAlerts) {
                $('#event-panel-tab-alerts').tab('show');
            } else {
                $('#event-panel-tab-events').tab('show');
            }
            Cookies.set('eventsPanel', true, { expires: 365, path: '/', secure: true });
            $j('#map_panels').hide();
        } else {
            Cookies.remove('eventsPanel');
            $j('#map_panels').show();
        }
        resizeApp(true);
    }

    // private, helper functions

    function htmlEscape(str) {
        return String(str)
                .replace(/&/g, '&amp;')
                .replace(/"/g, '&quot;')
                .replace(/'/g, '&#39;')
                .replace(/</g, '&lt;')
                .replace(/>/g, '&gt;');
    }

    function htmlUnescape(value) {
        return String(value)
            .replace(/&quot;/g, '"')
            .replace(/&#39;/g, "'")
            .replace(/&lt;/g, '<')
            .replace(/&gt;/g, '>')
            .replace(/&amp;/g, '&');
    }

    function populateGroupList() {
        // populate group parent list
        var cont = document.getElementById('ddlGroupParent');
        var defaultOption = el('option', { selected: true, value: '' }, tracking.strings.GROUP_NONE);
        defaultOption.setAttribute('selected', 'selected');
        var options = [
            defaultOption
        ];
        
        var sortedGroups = sortAssetGroups();
        for (var i = 0; i < sortedGroups.length; i++) {
            var group = sortedGroups[i];
            addGroupToGroupListInAddGroupDialog(group, 0, options);
        }
        setChildren(cont, options);
    }

    function populateAssetGroupDialog(assetGroup) {
        // populate fields with assetGroup information
        $('#txtGroupName').val(assetGroup.Name);
        $('#txtGroupDestinationId').val(assetGroup.DestinationId);
        $('#txtColor').val(assetGroup.Color);
        $('#txtColor').css('background-color', assetGroup.Color);
        $('#txtColor').next('span').css('background-color', assetGroup.Color);
        $('#EditAssetGroupAllowChat').prop('checked', assetGroup.IsChatEnabled);
        $('#EditAssetGroupAllowLocationSharing').prop('checked', assetGroup.IsLocationSharingEnabled);
    }

    function openAssetGroupDialog(assetGroup) {
        if (tracking.options.enabledFeatures.indexOf('UI_GROUPS') === -1) {
            return;
        }

        var dialog = tracking.data.domNodes.dialogs.editGroup;
        tracking.state.isEditingAssetGroup = assetGroup !== null;

        var buttonText = tracking.state.isEditingAssetGroup ? tracking.strings.SAVE_CHANGES : tracking.strings.CREATE_GROUP;
        changePrimaryButtonLabel(tracking.data.domNodes.dialogs.editGroup, buttonText);

        if (!tracking.state.isEditingAssetGroup) {
            $('#accordion-edit-asset-group-main-content').collapse('show');
        }

        tracking.data.validation.addGroup.resetForm();
        tracking.data.validation.addGroup.currentForm.reset();

        populateGroupList();
        var dialog = $j(tracking.data.domNodes.dialogs.editGroup);
        $('#edit-asset-group-accordion .primary-card button').removeClass('disabled');
        var dialogTitle = tracking.strings.ADD_ASSET_GROUP;
        if (tracking.state.isEditingAssetGroup) {
            $j('#asset-group-parent').hide();
            dialogTitle = tracking.strings.EDIT_ASSET_GROUP;

            $j(dialog).data('groupId', assetGroup.Id);
        } else {
            $j('#asset-group-parent').show();
        }

        if (!tracking.user.isAdmin) {
            $('#accordion-edit-asset-group-users-head button').addClass('disabled');
        }

        // checkbox lists...
        var userList = tracking.user.isAdmin ? tracking.data.users : [];
        populateCheckboxList('edit-asset-group-users-list', userList, 'EditAssetGroupUserIds',
            function (item) {
                if (assetGroup === null) {
                    return false;
                }
                var itemUsers = _.find(tracking.data.assetGroupUsers, { AssetGroupId: assetGroup.Id });
                if (itemUsers === undefined) {
                    return false;
                }
                return _.indexOf(itemUsers.UserIds, item.Id) !== -1;
            }, function (item) {
                return item.Name;
            }, 'users', function(item) { return item.Username; }
        );
        populateCheckboxList('edit-asset-group-assets-list', tracking.data.assets, 'EditAssetGroupAssetIds',
            function (item) {
                if (assetGroup === null) {
                    return false;
                }
                return _.indexOf(item.GroupIds, assetGroup.Id) !== -1;
            }, function (item) {
                return item.Name;
            }, 'assets', function(item) { return item.UniqueId; }
        );

        if (assetGroup !== null) {
            populateAssetGroupDialog(assetGroup);
        }

        openDialogPanel(tracking.data.domNodes.dialogs.editGroup, dialogTitle, assetGroup, false, null, 'group', (assetGroup !== null ? 'edit-group' : null), (assetGroup !== null ? openAssetGroupDialog : null));
        document.getElementById('txtGroupName').focus();
    }

    function openActionDialog(buttonLabel, dialogTitle, callback, idOverride, showGroupSelection, satelliteOnly, forDeviceIds) {
        var modal = document.getElementById('message-action-modal');
        var btn = document.getElementById('message-action-button');
        btn.textContent = buttonLabel;
        var title = document.getElementById('message-action-modal-title');
        title.textContent = dialogTitle;
        $(modal).data('message-action-callback', callback);

        $('input[name=IDPGateway]', modal).prop('disabled', false);
        if (satelliteOnly === true) {
            $('input[name=IDPGateway]', modal).prop('checked', false).prop('disabled', true);
            $('#IDPGatewaySatellite').prop('checked', true).prop('disabled', false);
        }

        // populate groups and assets
        var cont = $('.GroupsContainer', modal).empty();
        if (cont.length > 0) {
            var sortedGroups = sortAssetGroups();
            for (var i = 0; i < sortedGroups.length; i++) {
                var group = sortedGroups[i];
                addGroupToGroupListWithName(group, 0, cont[0], 'IDPGatewayParametersGroups', null, false);
            }
        }

        // this seems obscure/fragile
        var assetId = $(tracking.data.domNodes.dialogs.idpSendCommand).data('assetId');
        if (idOverride != null) {
            assetId = idOverride;
        }

        var showGroups = true;
        if (showGroupSelection != null) {
            showGroups = showGroupSelection;
        }

        if (showGroups) {
            $('#message-action-additional').show();
        } else {
            $('#message-action-additional').hide();
        }

        if (forDeviceIds === undefined) {
            forDeviceIds = null;
        }

        // assets - filter on Configuration version or AVL config (ARC?)
        var primaryAsset = findAssetById(assetId);
        if (!satelliteOnly) {
            var defaultGateway = 3;
            if (_.indexOf(tracking.devices.SKYWAVE_IDP_CELL_ONLY, primaryAsset.DeviceId) !== -1) {
                defaultGateway = 2;
            } else if (primaryAsset.DefaultIDPGateway != null) {
                var defaultGateway = primaryAsset.DefaultIDPGateway;
            }
            $('input[name=IDPGateway][value=' + defaultGateway + ']').prop('checked', true);
        }
        $('select.AssetsIncluded', modal).empty();
        var notIncludedContainer = modal.querySelector('select.AssetsNotIncluded');
        var optionCount = 0;
        var options = [];
        for (var i = 0; i < tracking.data.assets.length; i++) {
            var asset = tracking.data.assets[i];
            if (asset.Id == assetId) {
                continue;
            }
            if (asset.IsOutOfService) {
                continue;
            }
            if (forDeviceIds != null) {
                if (_.indexOf(forDeviceIds, asset.DeviceId) === -1) {
                    continue;
                }
                options.push(el('option', { value: asset.Id }, asset.Name + ' (' + asset.UniqueId + ')'));
                optionCount++;
            } else {
                if (primaryAsset != null) {
                    if (asset.Configuration == primaryAsset.Configuration) {
                        options.push(el('option', { value: asset.Id }, asset.Name + ' (' + asset.UniqueId + ')'));
                        optionCount++;
                    }
                } else {
                    if (_.indexOf(tracking.data.avlConfigurations, asset.Configuration) !== -1) {
                        options.push(el('option', { value: asset.Id }, asset.Name + ' (' + asset.UniqueId + ')'));
                        optionCount++;
                    }
                }
            }
        }
        setChildren(notIncludedContainer, options);
        $('#ActionAssetsNotIncludedCount').text(optionCount);
        $('#ActionAssetsIncludedCount').text('0');

        $(modal).data('assetId', assetId);
        $(modal).modal('show');
    }

    function openAlertInDialog(offset) {
        var eventId = $('#hfAcknowledgeEventId').val();
        var assetId = $('#hfAcknowledgeAssetId').val();
        var asset = findAssetById(assetId);
        var indexes = getAssetAlertIndex(asset, eventId);
        var newIndex = indexes.index + offset - 1;
        console.log(newIndex);
        console.log(indexes);
        var assetEvents = _.filter(tracking.data.alerts, function (item) { return item.AssetId === asset.Id; });
        var event = assetEvents[newIndex];
        if (event !== undefined && event !== null) {
            openAcknowledgeAlertDialog(asset, event);
        }
    }

    function updateEventForPosition(position, event) {
        var index = _.findIndex(position.Events, { Id: event.Id });
        if (index !== -1) {
            position.Events[index] = event;
        }
    }

    function updateAcknowledgedEventData(event) {
        // the event could be in both live and history caches, so search both and replace if found
        var liveEvent = _.find(tracking.data.live.normalizedEventsByAssetId[event.AssetId], function(item) { return item.Event.Id === event.Id; });
        if (liveEvent !== undefined && event.Alert !== undefined) {
            liveEvent.Event.Alert = event.Alert;
        }
        var historyEvent = _.find(tracking.data.history.normalizedEventsByAssetId[event.AssetId], function (item) { return item.Event.Id === event.Id; });
        if (historyEvent !== undefined && event.Alert !== undefined) {
            historyEvent.Event.Alert = event.Alert;
        }

        if (tracking.data.eventsById[event.Id] !== undefined) {
            tracking.data.eventsById[event.Id].Alert = event.Alert;
        }

        // there may not be a position associated with the event - in the case of no GPS or something like a heartbeat alert
        if (event.PositionId !== undefined && event.PositionId !== null) {
            // find the positions and update the event for them
            var updatedPosition = null;
            var livePosition = _.find(tracking.data.live.positionsByAssetId[event.AssetId], function (item) {
                return item.Position !== null && item.Position.Id === event.PositionId
            });
            if (livePosition !== undefined) {
                updatedPosition = livePosition.Position;
                updateEventForPosition(livePosition.Position, event);
            }
            if (tracking.data.live.latestPositionsByAssetId[event.AssetId] !== undefined && tracking.data.live.latestPositionsByAssetId[event.AssetId].Position.Id === event.PositionId) {
                updatedPosition = tracking.data.live.latestPositionsByAssetId[event.AssetId];
                updateEventForPosition(tracking.data.live.latestPositionsByAssetId[event.AssetId], event);
            }

            var historyPositions = tracking.data.history.positionsByAssetId[event.AssetId];
            if (historyPositions !== undefined) {
                var historyPosition = _.find(historyPositions.Positions, { Id: event.PositionId });
                if (historyPosition !== undefined) {
                    updatedPosition = historyPosition;
                    updateEventForPosition(historyPosition, event);
                }
            }

            if (tracking.data.positionsById[event.PositionId] !== undefined) {
                updateEventForPosition(tracking.data.positionsById[event.PositionId].Position, event);
            }

            // also update any markers for the position
            if (updatedPosition !== null) {
                //var allMarkers = _.union(tracking.data.live.markers, tracking.data.history.markers);
                var allMarkers = tracking.data.live.markers.concat(tracking.data.history.markers);
                var markers = _.filter(allMarkers, function (marker) {
                    return marker.data.location.Id === updatedPosition.Id;
                });
                _.each(markers, function (marker) {
                    marker.data.location = updatedPosition;
                });
            }
        }

        // it may already be confirmed
        confirmAlert(event);
    }

    function getAssetAlertIndex(asset, eventId) {
        var assetAlerts = _.filter(tracking.data.alerts, function (evt) {
            return evt.AssetId === asset.Id;
        });
        var totalAlerts = assetAlerts.length;
        var alertIndex = _.findIndex(assetAlerts, { Id: parseInt(eventId) });
        //for (var i = 0; i < totalAlerts; i++) {
        //    if (eventId === assetAlerts[i].Id) {
        //        alertIndex = i + 1;
        //        break;
        //    }
        //}
        return { index: alertIndex + 1, total: totalAlerts };
    }

    function populateAssetAlertIndex(asset, event) {
        var indexes = getAssetAlertIndex(asset, event.Id);
        $j('#alert-prev,#alert-next').removeClass('disabled');
        if (indexes.index === 1) {
            $j('#alert-prev').addClass('disabled');
        }
        if (indexes.index === indexes.total) {
            $j('#alert-next').addClass('disabled');
        }
        $j('#alert-index').text(tracking.strings.POSITION_INDEX.replace('{0}', indexes.index).replace('{1}', indexes.total));
        return indexes.index;
    }

    function initializeFormValidation() {
        var defaultBootstrapOptions = {
            errorClass: 'is-invalid',
            validClass: 'is-valid',
            focusInvalid: true,
            ignoreTitle: true,
            wrapper: 'div',
            errorPlacement: function (error, element) {
                error.addClass('invalid-feedback');
                error.insertAfter(element);
            }
        };
        jQuery.validator.setDefaults(defaultBootstrapOptions);
        tracking.data.validation.placeFind = $('#form-add-place-find').validate();
        tracking.data.validation.placeSearch = $('#form-add-place-search').validate();
        tracking.data.validation.placeAdd = $('#form-add-place').validate();
        tracking.data.validation.routing = $('#form-routing').validate();

        tracking.data.validation.sendPositionFind = $('#form-send-position-find').validate();
        tracking.data.validation.sendPositionSearch = $('#form-send-position-search').validate();
        tracking.data.validation.sendPositionShow = $('#form-send-position-show').validate();

        tracking.data.validation.idpAvlGeofence = $('#idp-avl-geofence-form').validate();
        tracking.data.validation.idpAvlParameters = $('#idp-avl-parameters-form').validate();
        tracking.data.validation.idpOutput = $('#idp-output-form').validate();
        tracking.data.validation.idpCoreParameters = $('#idp-core-parameters-form').validate();
        tracking.data.validation.idpIoParameters = $('#idp-avl-io-parameters-form').validate();
        tracking.data.validation.idpMetersSet = $('#idp-meters-set-form').validate();
        tracking.data.validation.idpReset = $('#idp-reset-form').validate();
        tracking.data.validation.idpSleepSchedule = $('#idp-sleep-schedule-form').validate();
        tracking.data.validation.idpModemRegistration = $('#idp-modem-registrations-form').validate();
        tracking.data.validation.idpEyeAlertImageRequest = $('#idp-eyealert-form').validate();
        tracking.data.validation.idpParameters = $('#idp-parameters-form').validate();
        tracking.data.validation.idpCommandLog = $('#idp-command-log-form').validate();
        tracking.data.validation.idpArc = $('#idp-arc-form').validate();
        tracking.data.validation.idpGarmin = $('#idp-garmin-form').validate();
        tracking.data.validation.idpImmobilizer = $('#idp-immobilizer-parameters-form').validate();
        tracking.data.validation.acknowledgeAlert = $('#acknowledge-form').validate();
        tracking.data.validation.inmarsatC = $('#inmarsatc-location-form').validate();
        tracking.data.validation.inmarsatCSchedule = $('#inmarsatc-schedule-form').validate();
        tracking.data.validation.inmarsatCDelete = $('#inmarsatc-dnid-delete-form').validate();
        tracking.data.validation.iridiumEdge = $('#edge-location-form').validate();
        tracking.data.validation.tm3000 = $('#tm3000-form').validate();
        tracking.data.validation.calampApn = $('#calamp-apn-form').validate();
        tracking.data.validation.calampOutput = $('#calamp-output-form').validate();
        tracking.data.validation.extremeInterval = $('#extreme-form').validate();
        tracking.data.validation.extremeEmergencyDestination = $('#extreme-emergency-destination-form').validate();
        tracking.data.validation.extremeEmergencyRecipient = $('#extreme-emergency-recipient-form').validate();
        tracking.data.validation.skywaveM2MParameters = $('#skywave-form').validate();
        tracking.data.validation.skywaveM2MLocation = $('#skywave-location-form').validate();
        tracking.data.validation.inreachInterval = $('#inreach-interval-form').validate();
        tracking.data.validation.hughesDownloadFile = $('#hughes-download-file-form').validate();
        tracking.data.validation.hughesCommands = $('#hughes-commands-form').validate();
        tracking.data.validation.queclinkApn = $('#queclink-apn-form').validate();
        tracking.data.validation.queclinkSettings = $('#queclink-settings-form').validate();
        tracking.data.validation.queclinkCommands = $('#queclink-commands-form').validate();
        tracking.data.validation.lt100Periodic = $('#lt100-periodic-form').validate();
        tracking.data.validation.lt100Motion = $('#lt100-motion-form').validate();
        tracking.data.validation.lt100Vibrate = $('#lt100-vibrate-form').validate();
        tracking.data.validation.lt501Periodic = $('#lt501-periodic-form').validate();
        tracking.data.validation.lt501Motion = $('#lt501-motion-form').validate();
        tracking.data.validation.lt501Location = $('#lt501-location-form').validate();
        tracking.data.validation.quake = $('#quake-settings-form').validate();
        tracking.data.validation.fbbInterval = $('#fbb-interval-form').validate();
        tracking.data.validation.acknowledgeAlert = $('#acknowledge-form').validate();
        tracking.data.validation.nal = $('#nal-properties-form').validate();
        tracking.data.validation.geoproInterval = $('#geopro-interval-form').validate();
        tracking.data.validation.gttsSettings = $('#gtts-settings-form').validate();
        tracking.data.validation.dantracker = $('#dantracker-settings-form').validate();
        tracking.data.validation.flightcellPayload = $('#flightcell-payload-form').validate();
        tracking.data.validation.edgeSolarSettings = $('#edge-solar-settings-form').validate();

        // discontinued
        //tracking.data.validation.garminGarmin = $('#garmin-garmin-form').validate();
        //tracking.data.validation.garminOdometer = $('#garmin-odometer-form').validate();
        //tracking.data.validation.garminDeleteData = $('#garmin-delete-data-for').validate();
        //tracking.data.validation.garminReset = $('#garmin-reset-form').validate();
        //tracking.data.validation.garminResetConfig = $('#garmin-reset-config-form').validate();

        tracking.data.validation.currentDriver = $('#current-driver-login-form').validate();

        tracking.data.validation.addPosition = $('#form-add-position').validate();
        //tracking.data.validation.sendPositionSearch = $('#form-send-position-search').validate();
        tracking.data.validation.sendPositionSend = $('#form-send-position-send').validate();
        tracking.data.validation.sendMessage = $('#form-send-message').validate();

        tracking.data.validation.refuelAdd = $('#form-add-refuel').validate();

        // edit-asset validation
        tracking.data.validation.addGroup = $('#form-add-group').validate();
        tracking.data.validation.editAsset = $('#form-edit-asset').validate();
        $.validator.addMethod(
            'regex',
            function (value, element, regexp) {
                var re = new RegExp(regexp);
                return this.optional(element) || re.test(value);
            },
            'Invalid ID.'
        );
        $.validator.addMethod('imei-code', function (value) {
            return /^\d{5}$/.test(value);
        }, 'Invalid Portal Registration Code #1.');
        $.validator.addMethod('shared-view-shareone', function (value, element) {
            var totalItemsShared = 0;
            if (document.getElementById('SharedViewPermissionsGroups').checked) {
                totalItemsShared += document.querySelectorAll('input[name="SharedViewAssetGroupIds"]:checked').length;
            }
            if (document.getElementById('SharedViewPermissionsAssets').checked) {
                totalItemsShared += document.querySelectorAll('input[name="SharedViewAssetIds"]:checked').length;
            }
            if (document.getElementById('SharedViewPermissionsGeofences').checked) {
                totalItemsShared += document.querySelectorAll('input[name="SharedViewFenceIds"]:checked').length;
            }
            if (document.getElementById('SharedViewPermissionsPlaces').checked) {
                totalItemsShared += document.querySelectorAll('input[name="SharedViewPlaceIds"]:checked').length;
            }

            return totalItemsShared > 0;
        }, tracking.strings.MSG_ERROR_SHARE_SOMETHING);	
        tracking.data.validation.editGroup = $('#form-edit-group').validate();
        tracking.data.validation.geofence = $('#form-add-geofence').validate();
        tracking.data.validation.dPlusQuery = $('#dplus-query-form').validate();
        tracking.data.validation.dPlusInterval = $('#dplus-interval-form').validate();
        tracking.data.validation.runReport = $('#run-report-form').validate();

        tracking.data.validation.sharedViewShare = $('#form-shared-view-share').validate();

        $.validator.addMethod(
            'ranges',
            function (value, element, ranges) {
                var noUpperBound = false;
                var valid = false;
                for (var i = 0; i < ranges.length; i++) {
                    if (ranges[i].length === 1) {
                        noUpperBound = true;
                    }
                    if (value >= ranges[i][0] && (value <= ranges[i][1] || noUpperBound)) {
                        valid = true;
                        break;
                    }
                }

                return this.optional(element) || valid;
            },
            "Invalid value."
        );
        $('#txtIDPAVLMovingIntervalSat').rules('add', { ranges: [[0, 0], [60]], messages: { ranges: 'Min 60s' } }); // either 0 to disable or 60+
        $('#txtIDPAVLStationaryIntervalSat').rules('add', { ranges: [[0, 0], [60]], messages: { ranges: 'Min 60s' } }); // either 0 to disable or 60+
    }

    function acknowledgeAssetAlert(btn, fromDialog) {
        var isAlternative = btn.classList.contains('alert-acknowledge-alt');
        var form = $(btn).closest('form');
        var buttons = _.toArray(form[0].querySelectorAll('button'));
        var isFormValid = form.valid();
        if (!isFormValid) {
            return;
        }

        var assetId = parseInt(form[0].getAttribute('data-asset-id'));
        var eventId = parseInt(form[0].getAttribute('data-event-id'));
        var resolution = $('.alert-resolution textarea', form).val();
        var data = {
            assetId: assetId,
            eventId: eventId,
            resolution: resolution,
            alternative: isAlternative
        };
        var status = $('.dialog-status', form)[0];
        handleAjaxFormSubmission('AcknowledgeAlert', data, buttons, status, tracking.strings.MSG_ALERT_ACKNOWLEDGE_SUCCESS, tracking.strings.MSG_ALERT_ACKNOWLEDGE_ERROR, function (result) {
            if (fromDialog) {
                // move to next alert in acknowledge dialog if it is open for the asset
                var asset = findAssetById(assetId);
                var indexes = getAssetAlertIndex(asset, eventId);
                updateAcknowledgedEventData(result.AssetEvent);

                if (indexes.total > 1) {
                    // move to next alert in dialog (same index)
                    if (indexes.index >= indexes.total) {
                        indexes.index = indexes.total;
                    }
                    var assetAlerts = _.filter(tracking.data.alerts, function (evt) {
                        return evt.AssetId === asset.Id;
                    });
                    var event = assetAlerts[indexes.index - 1];
                    if (event !== undefined && event !== null) {
                        openAcknowledgeAlertDialog(asset, event);
                    } else {
                        closeSecondaryPanel(); // open asset options instead?
                    }
                } else {
                    // no more events requiring acknowledgement for the asset
                    var none = document.getElementById('asset-acknowledge-alerts-none');
                    none.classList.add('is-visible');
                    form[0].classList.remove('is-visible');
                }
            } else {
                updateAcknowledgedEventData(result.AssetEvent);
            }
        });
    }

    function fuelEfficiencyType(type) {
        switch (type) {
            case 0: // us mpg
                return tracking.strings.FUEL_USMPG;
            case 1: // imperial mpg
                return tracking.strings.FUEL_IMPERIALMPG;
            case 2: // kpl
                return tracking.strings.FUEL_KPL;
            case 3: // l/100 km
            default:
                return tracking.strings.FUEL_L100KM;
        }
    }

    function convertFuelEfficiencyToStandard(fuelEfficiency, fuelEfficiencyType) {
        var efficiency = NaN;
        fuelEfficiency = $j.parseFloat(fuelEfficiency);
        fuelEfficiencyType = parseInt(fuelEfficiencyType);
        switch (fuelEfficiencyType) {
            case 0: // us mpg
                efficiency = (235.2146 / fuelEfficiency).toFixed(4);
                break;
            case 1: // imperial mpg
                efficiency = (282.4809363 / fuelEfficiency).toFixed(4);
                break;
            case 2: // kpl
                efficiency = (100 / fuelEfficiency).toFixed(4);
                break;
            case 3: // l/100 km
                efficiency = fuelEfficiency;
                break;
            default:
                break;
        }
        if (isNaN(efficiency))
            return '';
        efficiency = parseFloat(efficiency);
        efficiency = $j.formatGlobalization(efficiency, 'n4');
        return efficiency;
    }

    function changeMapType(type) {
    	if (tracking.map == null)
    		return;
    	if ((type == 'road') && (tracking.options.enableHemaMap)) {
    		type = 'hema';
    	} else if (tracking.preferences.PREFERENCE_MOROCCO_OVERLAY) {
    	    if ((type != 'satellite') && (type != 'satellitealt')) {
    	        type = 'morocco';
    	    }
    	}
        tracking.state.currentMapType = type;
        $('#map-type-list a').removeClass('active');

        // bounds, minZoom, maxZoom
    	switch (type) {
    	    case 'satellitealt':
                var thisBaseLayer = sentinel2SatelliteOverlay;

                tracking.map.setMaxBounds(tracking.options.maxBounds);
                tracking.map.setMaxZoom(22);
                tracking.map.setMinZoom(tracking.options.minimumZoom);
                if (!tracking.map.hasLayer(thisBaseLayer)) {
                    tracking.map.addLayer(thisBaseLayer);
                }
                for (var i = 0; i < baseTileLayers.length; i++) {
                    var baseLayer = baseTileLayers[i];
                    if (tracking.map.hasLayer(baseLayer) && (baseLayer != thisBaseLayer)) {
                        tracking.map.removeLayer(baseLayer);
                    }
                }

                var satelliteAlt = $('#map-type-list a.satellitealt').addClass('active');
                $('#map-type-current').text(satelliteAlt.text());
				break;

    		case 'satellite':
                tracking.map.setMaxBounds(tracking.options.maxBounds);
                tracking.map.setMaxZoom(20);
                tracking.map.setMinZoom(tracking.options.minimumZoom);

                var thisBaseLayer = tracking.options.baseLayers.satellitenolabels;
                if (tracking.data.isSatelliteLabelOverlayEnabled && !tracking.options.isSatelliteLabelsOverlay) {
                    thisBaseLayer = tracking.options.baseLayers.satellitelabels;
                }

                if (!tracking.map.hasLayer(thisBaseLayer.layer)) {
                    tracking.map.addLayer(thisBaseLayer.layer);
                }
                if (tracking.options.isSatelliteLabelsOverlay && tracking.data.isSatelliteLabelOverlayEnabled) {
                    // add any label overlay layers
                    if (!tracking.map.hasLayer(tracking.options.baseLayers.satellitelabels.layer)) {
                        tracking.map.addLayer(tracking.options.baseLayers.satellitelabels.layer);
                    }
                    if (tracking.options.baseLayers.satellitelabelsadditional !== undefined) {
                        if (!tracking.map.hasLayer(tracking.options.baseLayers.satellitelabelsadditional.layer)) {
                            tracking.map.addLayer(tracking.options.baseLayers.satellitelabelsadditional.layer);
                        }
                    }
                }
                for (var i = 0; i < baseTileLayers.length; i++) {
                    var baseLayer = baseTileLayers[i];
                    if (tracking.options.isSatelliteLabelsOverlay && tracking.data.isSatelliteLabelOverlayEnabled) {
                        if (tracking.map.hasLayer(baseLayer)
                            && (baseLayer != tracking.options.baseLayers.satellitelabels.layer)
                            && (tracking.options.baseLayers.satellitelabelsadditional !== undefined && baseLayer != tracking.options.baseLayers.satellitelabelsadditional.layer)
                            && (baseLayer != tracking.options.baseLayers.satellitenolabels.layer)) {
                            tracking.map.removeLayer(baseLayer);
                        }
                    } else {
                        if (tracking.map.hasLayer(baseLayer) && (baseLayer != thisBaseLayer.layer)) {
                            tracking.map.removeLayer(baseLayer);
                        }
                    }
                }

    			if (tracking.options.enableBethelMap) {
    				if (!tracking.map.hasLayer(bethelOverlay)) {
    					tracking.map.addLayer(bethelOverlay);
    				}
    			}

                var satellite = $('#map-type-list a.satellite').addClass('active');
                $('#map-type-current').text(satellite.text());
    			if (!tracking.preferences.PREFERENCE_REMOVE_ROADS && !tracking.preferences.PREFERENCE_MOROCCO_OVERLAY) {
    				$('#map-type-labels').show();
    			}
                document.getElementById('map-type-labels-show').checked = tracking.data.isSatelliteLabelOverlayEnabled;
    			break;
    	    case 'morocco':
    	    	var thisBaseLayer = moroccoOverlay;

                tracking.map.setMaxBounds(tracking.options.maxBounds);
    	    	tracking.map.setMaxZoom(18);
    	    	tracking.map.setMinZoom(tracking.options.minimumZoom);
    	    	if (!tracking.map.hasLayer(thisBaseLayer)) {
    	    		tracking.map.addLayer(thisBaseLayer);
    	    	}

                var road = $('#map-type-list a.road').addClass('active');
                $('#map-type-current').text(road.text());
    	        $('#map-type-labels').hide();
                break;
    		case 'hema':
    			var thisBaseLayer = hemaOverlay;
    			tracking.map.setMaxBounds(L.latLngBounds([10, 95], [-50, 180]));
    			tracking.map.setMaxZoom(14);
                tracking.map.setMinZoom(tracking.options.minimumZoom > 5 ? tracking.options.minimumZoom : 5);
    			if (!tracking.map.hasLayer(thisBaseLayer)) {
    				tracking.map.addLayer(thisBaseLayer);
    			}
				// keep osm overlay?
                var hema = $('#map-type-list a.road').addClass('active');
                $('#map-type-current').text(hema.text());
    			$('#map-type-labels').hide();
    			break;
    		case 'road':
            default:
                var thisBaseLayer = tracking.options.baseLayers[type];
                tracking.map.setMaxBounds(tracking.options.maxBounds);
    			tracking.map.setMaxZoom(thisBaseLayer.maxZoom);
    			tracking.map.setMinZoom(tracking.options.minimumZoom > thisBaseLayer.minZoom ? tracking.options.minimumZoom : thisBaseLayer.minZoom);
    			if (!tracking.map.hasLayer(thisBaseLayer.layer)) {
                    tracking.map.addLayer(thisBaseLayer.layer);
    			}
    			for (var i = 0; i < baseTileLayers.length; i++) {
    				var baseLayer = baseTileLayers[i];
                    if (tracking.map.hasLayer(baseLayer) && (baseLayer != thisBaseLayer.layer)) {
    					tracking.map.removeLayer(baseLayer);
    				}
                }
                var custom = $('#map-type-list a.' + type).addClass('active');
                $('#map-type-current').text(custom.text());
    			$('#map-type-labels').hide();
    			break;
    	}
        // workaround for bug with GL layers that initialize with offset base map
        tracking.map.flyToBounds(tracking.map.getBounds());
    }

    function createMarkerPath(icon, color, heading, alpha, assetId, forceAlpha, type, isFirst, isLast, number) {
        if (color === undefined
            || color === null) {
            color = '';
        }
        if (heading === undefined
            || heading === null) {
            heading = '';
        }
        if (alpha === undefined
            || alpha === null)
        	alpha = '';

		number = typeof number !== 'undefined' ? number : '';
        type = typeof type !== 'undefined' ? type : '';

        var headingVal = '';
        if (heading != '') {
            heading = Math.floor(heading);
            heading = Math.floor(heading / 5) * 5; // round to nearest 5 degrees
            headingVal = '&heading=' + heading;
        }

        var alphaVal = '';
        if (alpha !== '') {
            if (tracking.preferences.PREFERENCE_ALPHA_POSITIONS || forceAlpha) {
                if (Math.ceil(alpha) !== 255) {
                    alphaVal = '&alpha=' + Math.ceil(alpha);
                }
            }
        }

        var colorVal = '&color=' + color;
        // if custom icon and no heading, no color needed
        if ((icon.toLowerCase() === 'upload') && (heading === '')) {
            colorVal = '';
        }

        var aidVal = '';
        var lmVal = '';
        if (icon.toLowerCase() === 'upload') {
            aidVal = '&aid=' + assetId;
            if (assetId != null) {
                var asset = findAssetById(assetId);
                if(asset !== null) {
                    lmVal = '&lm=' + asset.IconModified;
                }
            }
        }

        var typeVal = '';
        if ((type !== null) && (type !== '')) {
            typeVal = '&type=' + type;
        }

        var numberVal = '';
        if ((number !== null) && (number !== '')) {
			numberVal = '&num=' + number;
        }

        var flagVal = '';
        var hideFlags = false;
        if (((isFirst === true) || (isLast === true)) && (assetId != null)) {
            if (assetId != null) {
                var asset = findAssetById(assetId);
                hideFlags = asset.HideFlags;
            }
        }
        if (!hideFlags) {
            if (isFirst === true) {
                flagVal = '&flag=begin';
            } else if (isLast === true) {
                flagVal = '&flag=end';
            }
        }

        var imagePath = '/markers/' + icon + '?' + colorVal + headingVal + alphaVal + aidVal + typeVal + flagVal + numberVal + lmVal;
        if (tracking.user.isImpersonated && aidVal !== '') {
            imagePath += '&ishr=' + encodeURIComponent(tracking.user.id);
        }
        if (tracking.options.useStaticSubdomains) {
            // TODO: handle this with a httpmodule instead?
            // serve static content from other subdomains to improve request handling
            var domain = 'static';
            switch (icon.toLowerCase()) {
                case 'person':
                case 'boat':
                case 'airplane':
                case 'yacht':
                    domain += '1';
                    break;
                case 'truck':
                case 'car':
                case 'upload':
                case 'helicopter':
                    domain += '2';
                    break;
                default:
                    break;
            }
            // return 'https://domain.actualdomain.com{imagePath}'
        }
        return imagePath;
    }

    function convertAltitudeToPreference(altitude) {
        if(altitude == null)
            return '';
        if(altitude == '')
            return '';

        switch(tracking.preferences.PREFERENCE_SPEED) {
            case 0:
            case 3: // knots/feet
                return (altitude * 3.2808399).toFixed(1) + ' ' + altitudeText();
            case 1:
            case 2:
            default:
                return altitude.toFixed(1) + ' ' + altitudeText();
                break;
        }
    }

    function convertSpeedToPreference(speed) {
        switch (tracking.preferences.PREFERENCE_SPEED) {
            case 0:
                return (speed * 2.23693629).toFixed(1) + ' ' + speedText();
            case 1:
                return (speed * 3.6).toFixed(1) + ' ' + speedText();
        	case 2:
			case 3:
                return (speed * 1.94384449).toFixed(1) + ' ' + speedText();
            default:
                return (speed * 2.23693629).toFixed(1) + ' ' + speedText();
        }
    }

    function convertToLatLngPreference(lat, lng, grid) {
        var pref = tracking.preferences.PREFERENCE_LAT_LNG;
        if (((pref == 3) && (grid == null))
        	|| ((pref == 4) && (grid == null))
            || ((pref == 5) && (grid == null))){
            pref = 0;
        }
        switch (pref) {
            case 1:
                return convertDecimalDegrees(lat, lng);
            case 2:
                return convertDegreesDecimalMinutes(lat, lng);
        	case 3:
			case 4:
                return grid;
            case 5:
                if (grid !== null && grid.length === 8) {
                    return grid;
                }
                return tracking.strings.NOT_IN_UK;
            case 0:
            default:
                return lat.toFixed(6) + ', ' + lng.toFixed(6);
            //case 3:
                // pull Grid from position
            //    return '';
                //return convertMGRS(lat, lng);
        }
    }

    function convertFromMetresToUserDistancePreference(distance) {
        switch (tracking.preferences.PREFERENCE_SPEED) {
            case 3: // knots/nautical miles - avionics
            case 2: // knots/nautical miles
                return ((distance /1000)* 0.539956803).toFixed(2) + ' nmi';
            case 0: // miles
                var feet = 3.2808399 * distance;
                if (feet > 5280)
                    return (feet / 5280).toFixed(2) + ' mi';
                return Math.floor(feet) + ' ft';
            case 1: // metres/kilometres
            default:
                if (distance < 1000)
                    return Math.floor(distance) + ' m';
                return (distance / 1000).toFixed(2) + ' km';
        }
    }

    function distanceText() {
        switch (tracking.preferences.PREFERENCE_SPEED) {
            case 3: // knots/nautical miles - avionics
            case 2: // knots/nautical miles
                return 'nmi';
            case 0: // miles
                return 'mi';
            case 1: // metres/kilometres
            default:
                return 'km';
        }
    }

    function fuelText() {
        switch (tracking.preferences.PREFERENCE_FUEL_UNIT) {
            case 1:
                // imp mpg
                return tracking.strings.FUEL_IMPERIAL;
            case 2:
            case 3:
                // litres
                return tracking.strings.FUEL_LITRES;
            case 0:
            default:
                // us mpg
                return tracking.strings.FUEL_US;
        }
    }

    function altitudeText() {
        switch (tracking.preferences.PREFERENCE_SPEED) {
            case 0:
            case 3:
                return 'ft';
            case 1:
            case 2:
            default:
                return 'm';
        }
    }

    function speedText() {
        switch (tracking.preferences.PREFERENCE_SPEED) {
            case 1:
                return 'kph';
            case 2:
            case 3:
                return 'knots';
            case 0:
            default:
                return 'mph';
        }
    }

    function decode_polyline(str, precision) {
    	var index = 0,
			lat = 0,
			lng = 0,
			coordinates = [],
			shift = 0,
			result = 0,
			byte = null,
			latitude_change,
			longitude_change,
			factor = Math.pow(10, precision || 5);

    	// Coordinates have variable length when encoded, so just keep
    	// track of whether we've hit the end of the string. In each
    	// loop iteration, a single coordinate is decoded.
    	while (index < str.length) {

    		// Reset shift, result, and byte
    		byte = null;
    		shift = 0;
    		result = 0;

    		do {
    			byte = str.charCodeAt(index++) - 63;
    			result |= (byte & 0x1f) << shift;
    			shift += 5;
    		} while (byte >= 0x20);

    		latitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1));

    		shift = result = 0;

    		do {
    			byte = str.charCodeAt(index++) - 63;
    			result |= (byte & 0x1f) << shift;
    			shift += 5;
    		} while (byte >= 0x20);

    		longitude_change = ((result & 1) ? ~(result >> 1) : (result >> 1));

    		lat += latitude_change;
    		lng += longitude_change;

    		coordinates.push([lat / factor, lng / factor]);
    	}

    	return coordinates;
    };

    //var playbackInterval = null;
    //var playbackAssetId = null;
    //var isInPlaybackMode = false;
    //var playbackIndex = 0;
    //var playbackPositions = [];

    function playbackInit() {
        tracking.intervals.playbackInterval = null;
        tracking.data.playback.assetId = null;
        tracking.state.isInPlaybackMode = false;
        tracking.data.playback.index = 0;
        tracking.data.playback.positions = [];
    }

    function updateMessageStatusForAsset(asset) {
        var epochSeconds = ((new Date).getTime() - tracking.user.utcOffset) / 1000;

        var assetNodes = tracking.data.domNodes.assets[asset.Id];
        if (assetNodes === undefined) {
            return;
        }

        var hasNewMessage = false;
        var mailboxClasses = [];

        // if an asset has unconfirmed messages or an incoming message within the past 10 minutes
        // then highlight its mailbox
        if (asset.UnconfirmedMessages != null && asset.UnconfirmedMessages.length > 0) {
            hasNewMessage = true;
        } else if (asset.LastIncomingMessage != null) {
            var diff = epochSeconds - asset.LastIncomingMessage;
            if (diff < 600) { // todo: user configurable value
                hasNewMessage = true;
            }
        }

        if (hasNewMessage) {
            mailboxClasses.push('new');
        }

        if (asset.MessageStatuses != null) {
            for (var j = asset.MessageStatuses.length - 1; j >= 0; j -= 1) {
                var status = asset.MessageStatuses[j];
                var diff = epochSeconds - status.Time;
                if (diff < 1800) { // todo: user configurable value
                    switch (status.Status) {
                        case 'Error': // todo: test in other languages
                            mailboxClasses.push('error');
                            break;
                        case 'Submitted':
                            mailboxClasses.push('pending');
                            break;
                        case 'Received':
                            break;
                        default:
                            break;
                    }
                } else {
                    // message status has expired
                    asset.MessageStatuses.splice(j, 1);
                }
            }
        }
        var hasStatus = mailboxClasses.length > 0;
        mailboxClasses.push("t-icon");
        mailboxClasses.push("t-icon-mail");
        mailboxClasses.push("mailbox");
        mailboxClasses.push("has-status");
        _.each(assetNodes, function (assetNode) {
            var mailbox = assetNode.querySelector('.t-icon-mail');
            if (mailbox !== null) {
                if (mailbox.classList.contains("has-status") || hasStatus) {
                    mailbox.className = mailboxClasses.join(' ');
                }
            }
        });
    }

    function updatePositionStatus() {
    	tracking.log('Update position indicators.');

        //// update message status
        //_.each(tracking.data.domNodes.assets, function (assetNodes, key) {
        //    var asset = findAssetById(key);
        //    updateMessageStatusForAsset(asset);
        //});

        updateTimeBasedNotificationIndicators();
        updateLatestPositionTimeForSharedView();
    }

    function getNotificationOpacityForTime(time) {
        if (time === null) {
            return null;
        }
        //var serverTime = new Date().getTime() - tracking.user.tickOffset;
        var currentTime = new Date().getTime();
        var difference = Math.abs(currentTime - time);
        if (difference > 3600000) {
            return null;
        }

        var percent = 100 - Math.floor((difference / 3600000) * 100);
        var opacity = Math.round(percent / 10) * 10; // round to nearest 10
        return opacity;
    }

    function updateTimeBasedNotificationIndicatorsForNode(item, opacity, type) {
        var recentType = 'recent-' + type;

        //var classesToRemove = _.union([recentType], tracking.NOTIFICATION_PREFIXES[type].classes);
        var classesToRemove = [recentType].concat(tracking.NOTIFICATION_PREFIXES[type].classes);
        var classNames = [ recentType ];
        if (opacity !== null) {
            classNames.push(tracking.NOTIFICATION_PREFIXES[type].prefix + '-' + opacity);
        }

        if (opacity === null) {
            // remove all indicators for type, if any
            if (!item.classList.contains(recentType + '-no')) {
                _.each(classesToRemove, function (cls) {
                    if (item.classList.contains(cls)) {
                        item.classList.remove(cls);
                    }
                });
                item.classList.add(recentType + '-no');
            }

            // bugfix here, remove this by ensuring recent-type-no is not added when recent-type is active
            if (item.classList.contains(recentType)) {
                item.classList.remove(recentType);
            }
        } else {
            // ensure classNames are added and others removed
            var isRemoved = false;
            classesToRemove = _.difference(classesToRemove, classNames);
            _.each(classNames, function (className) {
                if (!item.classList.contains(className)) {
                    if (!isRemoved) {
                        if (!item.classList.contains(recentType + '-no')) {
                            _.each(classesToRemove, function (cls) {
                                if (item.classList.contains(cls)) {
                                    item.classList.remove(cls);
                                }
                            });
                        } else {
                            item.classList.remove(recentType + '-no');
                        }
                        isRemoved = true;
                    }
                    item.classList.add(className);
                }
            });
        }
    }

    function updateTimeBasedNotificationIndicatorsForGroup(groupId) {
        //console.log('update time notification indicator for ' + groupId);
        // get max times for all assets within the group
        var node = tracking.data.domNodes.groups[groupId];
        if (node === undefined) {
            return;
        }

        var item = node.querySelector('.notifications');

        var assetIds = [];
        var assets = [];
        var group = null;
        if (groupId === 'all-assets') {
            assetIds = _.map(tracking.data.assets, 'Id');
        } else {
            group = findGroupById(groupId);
            assetIds = group.AssetIds;
        }

        _.each(assetIds, function(assetId) {
            assets.push(findAssetById(assetId));
        });

        var notificationTypes = tracking.NOTIFICATION_TYPES;

        _.each(notificationTypes, function (itemType) {
            var recentType = 'recent-' + itemType;

            if (tracking.state.activeMapMode !== tracking.mapModes.LIVE) {
                // history mode does not support fading icons
                // remove any opacity classes from history mode
                if (!item.classList.contains(recentType + '-no')) {
                    _.each(tracking.NOTIFICATION_PREFIXES[itemType].classes, function (cls) {
                        if (item.classList.contains(cls)) {
                            item.classList.remove(cls);
                        }
                    });
                    item.classList.add(recentType + '-no');
                }
                return;
            }

            // add/upate indicators for LIVE mode
            var opacity = null;

            // check times for all assets within this group
            _.each(assets, function (asset) {
                if (tracking.data.live.notificationTimesByAssetId[asset.Id] !== undefined) {
                    var assetOpacity = getNotificationOpacityForTime(tracking.data.live.notificationTimesByAssetId[asset.Id][itemType]);
                    if (assetOpacity !== null && (opacity === null || assetOpacity > opacity)) {
                        opacity = assetOpacity;
                    }
                }
            });

            updateTimeBasedNotificationIndicatorsForNode(item, opacity, itemType);
        });
    }

    function updateTimeBasedNotificationIndicatorsForAsset(asset) {
        var notificationTypes = tracking.NOTIFICATION_TYPES;
        var indicators = [];
        _.each(tracking.data.domNodes.assets[asset.Id], function (assetNode) {
            indicators.push(assetNode.querySelector('.notifications'));
        });

        if (indicators.length === 0) {
            return;
        }

        _.each(notificationTypes, function (itemType) {
            var recentType = 'recent-' + itemType;

            if (tracking.state.activeMapMode !== tracking.mapModes.LIVE) {
                // history mode does not support fading icons
                _.each(indicators, function (item) {
                    // remove any opacity classes from history mode
                    if (!item.classList.contains(recentType + '-no')) {
                        _.each(tracking.NOTIFICATION_PREFIXES[itemType].classes, function (cls) {
                            if (item.classList.contains(cls)) {
                                item.classList.remove(cls);
                            }
                        });
                        item.classList.add(recentType + '-no');
                    }
                });
                return;
            }

            // add/upate indicators for LIVE mode
            var opacity = null;
            if (tracking.data.live.notificationTimesByAssetId[asset.Id] !== undefined) {
                opacity = getNotificationOpacityForTime(tracking.data.live.notificationTimesByAssetId[asset.Id][itemType]);
            }
            //var classNames = [recentType];

            //if (opacity !== null) {
            //    classNames.push(tracking.NOTIFICATION_PREFIXES[type].prefix + '-' + opacity);
            //}

            _.each(indicators, function (item) {
                updateTimeBasedNotificationIndicatorsForNode(item, opacity, itemType);
            });
        });
    }

    function updateTimeBasedNotificationIndicators() {
        if (tracking.state.activeMapMode !== tracking.mapModes.LIVE) {
            return;
        }

        // todo: profile having individual setIntervals on active
        // assets instead of this global query/update
        // each interval would be responsible for caching its dom element
        // and switching views should clear all intervals
        // the intervals should be attached to assets
        // the interval would be cleared after the indicator is no longer relevant

        _.each(tracking.data.assets, function (asset) {
            //updatePositionTimeIndicatorForAsset(asset);
            updateTimeBasedNotificationIndicatorsForAsset(asset);
        });
        var groupIds = _.map(tracking.data.groups, 'Id');
        groupIds.push('all-assets');
        _.each(groupIds, function (groupId) {
            updateTimeBasedNotificationIndicatorsForGroup(groupId);
        });
    }

    function playbackClick() {
        // start playback from this position if not already running
        if (tracking.state.isInPlaybackMode) {
            playbackEnd();
        } else {
            var link = document.getElementById('playback');
            if (link === null) {
                return false;
            }
            var assetId = parseInt(link.getAttribute('data-asset-id'));
            var positionId = link.getAttribute('data-position-id');
            var tripId = link.getAttribute('data-trip-id');
            if (tripId !== null) {
                tripId = parseInt(tripId);
            }
            if (tracking.state.activeMapMode === tracking.mapModes.LIVE && tracking.state.activeViewMode === tracking.viewModes.NORMAL && tripId === null) {
                return false;
            }
            playbackStart(assetId, positionId, tripId);
        }
        return false;
    }

    function playbackEnd() {
        clearInterval(tracking.intervals.playbackInterval);
        tracking.data.playback.assetId = null;
        tracking.data.playback.tripId = null;
        tracking.state.isInPlaybackMode = false;
        tracking.data.playback.positions = [];
        tracking.data.playback.index = 0;
        var playback = document.getElementById('playback');
        if (playback !== null) {
            playback.title = tracking.strings.PLAYBACK_PLAY;
            playback.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#play-solid');
        }
    }

    function openMarkerForPosition(positionId, tripId) {
        var marker = null;
        if (tripId !== null) {
            marker = _.find(tracking.data.trips.markersByTripId[tripId], function(item) { return item.data.location.Id === positionId;});
        } else if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
            marker = _.find(tracking.data.history.markers, function(item) { return item.data.location.Id === positionId; });
        } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
            marker = _.find(tracking.data.sharedView.markers, function (item) { return item.data.location.Id === positionId; });
        }
        if (marker !== undefined) {
            // if the asset for this location is not active, make it active
            if (tripId === null) {
                toggleAssetActive(marker.data.assetId, true, true);
            }
            marker.fire('click');
        }
    }

    function getPositionInHistoryRelative(assetId, positionId, tripId, shift) {
        if (tripId !== null) {
            assetPositions = tracking.data.trips.positionsByTripId[tripId].Positions;
        } else if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
            assetPositions = tracking.data.history.positionsByAssetId[assetId].Positions;
        } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
            assetPositions = tracking.data.sharedView.positionsByAssetId[assetId].Positions;
        }

        for (var j = 0; j < assetPositions.length; j++) {
            if (assetPositions[j].Id == positionId) {
                var positionIndex = j;
                // current position is assetPositions[j], get previous one and show it
                var indexShift = j + shift;
                if ((indexShift < 0) || (indexShift > assetPositions.length)) {
                    return null;
                }

                var shiftedPosition = assetPositions[indexShift];
                return shiftedPosition;
            }
        }

        return null;
    }

    function playbackPrevPosition()
    {
        var link = document.getElementById('playback');
        var assetId = parseInt(link.getAttribute('data-asset-id'));
        var positionId = link.getAttribute('data-position-id');
        var tripId = link.getAttribute('data-trip-id');
        if (tripId !== null) {
            tripId = parseInt(tripId);
        }

        if (tracking.state.activeMapMode === tracking.mapModes.LIVE && tracking.state.activeViewMode === tracking.viewModes.NORMAL && tripId === null) {
            return;
        }

        // get previous position id
        var relativePosition = getPositionInHistoryRelative(assetId, positionId, tripId, 1);
        if (relativePosition == null) {
            return;
        }

        // reset the playback interval if in playback mode
        if (tracking.state.isInPlaybackMode) {
            if (tracking.intervals.playbackInterval != null) {
                clearInterval(tracking.intervals.playbackInterval);
            }
            tracking.intervals.playbackInterval = setInterval(playbackNext, 5000);
            tracking.data.playback.index++;
        }

        // open its marker
        openMarkerForPosition(relativePosition.Id, tripId);
    }

    function playbackLastPosition() {
        var link = document.getElementById('playback');
        var assetId = parseInt(link.getAttribute('data-asset-id'));
        var tripId = link.getAttribute('data-trip-id');
        if (tripId !== null) {
            tripId = parseInt(tripId);
        }

        // doesn't have to be in live mode if playing back a trip
        if (tracking.state.activeMapMode === tracking.mapModes.LIVE && tracking.state.activeViewMode === tracking.viewModes.NORMAL && tripId === null) {
            return;
        }

        var relativePosition = null;
        var assetPositions = [];
        if (tripId !== null) {
            assetPositions = tracking.data.trips.positionsByTripId[tripId].Positions;
        } else if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
            assetPositions = tracking.data.history.positionsByAssetId[assetId].Positions;
        } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
            assetPositions = tracking.data.sharedView.positionsByAssetId[assetId].Positions;
        }
        if (assetPositions.length === 0) {
            return;
        }

        relativePosition = assetPositions[0];

        // reset the playback interval if in playback mode
        if (tracking.state.isInPlaybackMode) {
            if (tracking.intervals.playbackInterval != null) {
                clearInterval(tracking.intervals.playbackInterval);
            }
            tracking.intervals.playbackInterval = setInterval(playbackNext, 5000);
            tracking.data.playback.index = 0;
        }

        // open its marker
        openMarkerForPosition(relativePosition.Id, tripId);
    }

    function playbackFirstPosition() {
        var link = document.getElementById('playback');
        var assetId = parseInt(link.getAttribute('data-asset-id'));
        var tripId = link.getAttribute('data-trip-id');
        if (tripId !== null) {
            tripId = parseInt(tripId);
        }

        if (tracking.state.activeMapMode === tracking.mapModes.LIVE && tracking.state.activeViewMode === tracking.viewModes.NORMAL && tripId === null) {
            return;
        }

        var relativePosition = null;
        var assetPositions = [];
        if (tripId !== null) {
            assetPositions = tracking.data.trips.positionsByTripId[tripId].Positions;
        } else if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
            assetPositions = tracking.data.history.positionsByAssetId[assetId].Positions;
        } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
            assetPositions = tracking.data.sharedView.positionsByAssetId[assetId].Positions;
        }
        if (assetPositions.length === 0) {
            return;
        }
        relativePosition = assetPositions[assetPositions.length - 1];

        if (relativePosition == null) {
            return;
        }

        // reset the playback interval if in playback mode
        if (tracking.state.isInPlaybackMode) {
            if (tracking.intervals.playbackInterval != null) {
                clearInterval(tracking.intervals.playbackInterval);
            }
            tracking.intervals.playbackInterval = setInterval(playbackNext, 5000);
            tracking.data.playback.index = assetPositions.length - 1;
        }

        // open its marker
        openMarkerForPosition(relativePosition.Id, tripId);
    }

    function playbackNextPosition() {
        var link = document.getElementById('playback');
        var assetId = parseInt(link.getAttribute('data-asset-id'));
        var positionId = link.getAttribute('data-position-id');
        var tripId = link.getAttribute('data-trip-id');
        if (tripId !== null) {
            tripId = parseInt(tripId);
        }

        if (tracking.state.activeMapMode === tracking.mapModes.LIVE && tracking.state.activeViewMode === tracking.viewModes.NORMAL && tripId === null) {
            return;
        }

        var relativePosition = getPositionInHistoryRelative(assetId, positionId, tripId, -1);
        if (relativePosition == null) {
            return;
        }

        // reset the playback interval if in playback mode
        if (tracking.state.isInPlaybackMode) {
            if (tracking.intervals.playbackInterval != null) {
                clearInterval(tracking.intervals.playbackInterval);
            }
            tracking.intervals.playbackInterval = setInterval(playbackNext, 5000);
            tracking.data.playback.index--;
        }

        // open its marker
        openMarkerForPosition(relativePosition.Id, tripId);
    }

    function playbackNext() {
        if (tracking.state.activeMapMode === tracking.mapModes.LIVE && tracking.state.activeViewMode === tracking.viewModes.NORMAL && tracking.data.playback.tripId === null) {
            playbackEnd();
        }
        if (tracking.data.playback.positions == null) {
            playbackEnd();
        }

        // move to the next position in playback index
        tracking.data.playback.index--;
        if ((tracking.data.playback.positions.length > tracking.data.playback.index) && (tracking.data.playback.index >= 0)) {
            var position = tracking.data.playback.positions[tracking.data.playback.index];
            openMarkerForPosition(position.Id, tracking.data.playback.tripId);
            if (tracking.data.playback.index == 0) {
                playbackEnd();
            }
        } else {
            // reached the last position
            playbackEnd();
        }
    }

    function playbackStart(assetId, positionId, tripId) {
        console.log('playback: ' + assetId + ',' + positionId + ',' + tripId);
        var asset = findAssetById(assetId);
        if (asset == null) {
            return;
        }

        var playbackPositions = [];
        tracking.data.playback.assetId = assetId;
        tracking.data.playback.tripId = tripId;
        if (tripId === null && tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
            playbackPositions = tracking.data.history.positionsByAssetId[assetId];
        } else if (tripId !== undefined && tripId !== null) {
            playbackPositions = tracking.data.trips.positionsByTripId[tripId];
        } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
            playbackPositions = tracking.data.sharedView.positionsByTripId[tripId];
        }
        if (playbackPositions !== undefined && playbackPositions !== null) {
            tracking.data.playback.positions = playbackPositions.Positions;
            // find current position
            for (var j = 0; j < tracking.data.playback.positions.length; j++) {
                if (tracking.data.playback.positions[j].Id == positionId) {
                    tracking.data.playback.index = j;
                    if (tracking.intervals.playbackInterval != null) {
                        clearInterval(tracking.intervals.playbackInterval);
                    }
                    tracking.intervals.playbackInterval = setInterval(playbackNext, 5000);
                    var total = tracking.data.playback.positions.length;
                    var current = tracking.data.playback.positions.length - tracking.data.playback.index;
                    var positionStatus = tracking.strings.POSITION_INDEX.replace('{0}', current).replace('{1}', total) + ' ';
                    $('#playback-status').text(positionStatus);
                    var playback = document.getElementById('playback');
                    playback.title = tracking.strings.PLAYBACK_STOP;
                    playback.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#pause-solid');
                    tracking.state.isInPlaybackMode = true;
                    break;
                }
            }
        }
    }

    function openRouting() {
        if (tracking.data.routing.isOpen) {
            return;
        }
        tracking.data.routing.isOpen = true;
        tracking.data.routing.start = null;
        tracking.data.routing.end = null;
        tracking.data.routing.isUsingMap = true;
        tracking.data.routing.points = [];
        if (tracking.options.enabledFeatures.indexOf('GET_ROUTE') !== -1) {
            tracking.data.routing.mapClickDestination = $(document.getElementById('RoutingDestination1'));
            startChoosingMapLocation(tracking.state.mapClickHandlers.ROUTING);
        }

        var results = document.getElementById('routing-results');
        results.classList.remove('is-visible');
        openDialogPanel(tracking.data.domNodes.dialogs.routing, tracking.strings.GET_ROUTE, null, false, closeRouting, 'misc');
        var routeGeofence = document.getElementById('routing-geofence');
        if (tracking.user.canEditGeofences) {
            routeGeofence.classList.add('is-visible');
        }
        $('#RoutingWaypoints').find('li').remove();
    }

    function populateDiagnostics() {
        $j('#DiagnosticsBrowser').text(navigator.userAgent);
        $j('#DiagnosticsUIVersion').text(tracking.version);
        if (tracking.notificationConnection.transport != null) {
            $j('#DiagnosticsTransport').text(tracking.notificationConnection.transport.name);
        } else {
            $j('#DiagnosticsTransport').text('NONE');
        }
    }

    function runReportOptionChanged() {
        var option = $('#RunReportReport option:selected')[0];
        var needsDateRange = option.getAttribute('data-date-range') === 'true';
        var dateRange = document.getElementById('RunReportDateRange');
        if (needsDateRange) {
            dateRange.classList.add('is-visible');
        } else {
            dateRange.classList.remove('is-visible');
        }
        $('#run-report-form')[0].setAttribute('action', '/reports/' + option.value);
    }

    function openRunReportModal() {
        tracking.data.validation.runReport.resetForm();
        tracking.data.validation.runReport.currentForm.reset();

        var reportAssets = tracking.data.assets;
        populateCheckboxList('run-report-assets-list', reportAssets, 'AssetIds',
            function (item) {
                return _.indexOf(tracking.data.visible.assets, item.Id) !== -1;
            }, function (item) {
                return item.Name;
            }, 'assets', function(item) { return item.UniqueId; });

        // populate date/time if in history mode
        if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
            $('#RunReportFrom').val($('#txtDateFrom').val());
            $('#RunReportTo').val($('#txtDateTo').val());
            if ($('#RunReportTo').val() === '') {
                $('#RunReportTo').datetimepicker('setDate', moment().toDate());
            }
        }

        runReportOptionChanged();

        $(tracking.data.domNodes.modals.runReport).modal('show');
    }

    function openPlaceRoutingForPlace(place) {
        if (tracking.options.enabledFeatures.indexOf('ASSET_ROUTING') === -1) {
            return;
        }

        openDialogPanel(tracking.data.domNodes.dialogs.placeRouting, tracking.strings.ROUTE_ASSET_TO_PLACE, place, false, closePlaceRouting, 'place', 'route-asset', openPlaceRoutingForPlace);
        openPlaceRouting(null, place);
    }

    function openPlaceRoutingForAsset(asset) {
        if (tracking.options.enabledFeatures.indexOf('ASSET_ROUTING') === -1) {
            return;
        }
        openDialogPanel(tracking.data.domNodes.dialogs.placeRouting, tracking.strings.ROUTE_ASSET_TO_PLACE, asset, false, closePlaceRouting, 'asset', 'route-asset', openPlaceRoutingForAsset);
        openPlaceRouting(asset, null);
    }

    function openPlaceRouting(asset, place) {
        if (tracking.options.enabledFeatures.indexOf('ASSET_ROUTING') === -1) {
            return;
        }
        var routeGeofence = document.getElementById('place-route-geofence');
        if (tracking.user.canEditGeofences) {
            routeGeofence.classList.add('is-visible');
        }

        var options = [];
        for (var i = 0; i < tracking.data.places.length; i++) {
            var item = tracking.data.places[i];
            options.push(el('option', { value: item.Id }, item.Name));
        }
        setChildren(document.getElementById('PlaceRoutePlace'), options);

        // populate asset list
        options = [];
        for (var i = 0; i < tracking.data.assets.length; i++) {
            var item = tracking.data.assets[i];
            options.push(el('option', { value: item.Id }, item.Name));
        }
        setChildren(document.getElementById('PlaceRouteAsset'), options);

        $j('input[name=PlaceRouteVia]').prop('checked', false);
        $j('#PlaceRouteViaRoad').prop('checked', true);
        if (place != null) {
            $j('#PlaceRoutePlace').val(place.Id);
        }
        if (asset != null) {
            $j('#PlaceRouteAsset').val(asset.Id);
        }
    }

    function handleAjaxFormSubmission(endpoint, data, button, statusAlert, msgSuccess, msgError, callbackSuccess, callbackFailure) {
        toggleLoadingMessage(true, endpoint);
        var buttons = [];
        if (button !== null) {
            if (Array.isArray(button)) {
                buttons = button;
            } else {
                buttons = [button];
            }
        }
        _.each(buttons, function (button) {
            button.disabled = true;
        });
        if (statusAlert !== undefined && statusAlert !== null) {
            statusAlert.classList.remove('is-visible');
            statusAlert.textContent = '';
        }
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/' + endpoint),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                _.each(buttons, function (button) {
                    button.disabled = false;
                });
                toggleLoadingMessage(false, endpoint);
                var result = msg.d;
                if (result) {
                    if (result.Success == true) {
                        if (msgSuccess !== null && statusAlert !== undefined && statusAlert !== null) {
                            formShowSuccessMessage(statusAlert, msgSuccess);
                        }
                        if (callbackSuccess !== undefined && callbackSuccess !== null) {
                            callbackSuccess(result);
                        }
                    } else {
                        if (msgError !== null && statusAlert !== undefined && statusAlert !== null) {
                            formShowErrorMessage(statusAlert, msgError);
                            if (result.ErrorMessage != null && result.ErrorMessage !== '') {
                                formShowErrorMessage(statusAlert, statusAlert.textContent + ' ' + result.ErrorMessage);
                            }
                        }
                        if (callbackFailure !== undefined && callbackFailure !== null) {
                            callbackFailure(result);
                        }
                    }
                }
            },
            error: function (xhr, status, error) {
                _.each(buttons, function (button) {
                    button.disabled = false;
                });
                toggleLoadingMessage(false, endpoint);
                if (status !== undefined && status !== null && msgError !== null && statusAlert !== undefined && statusAlert !== null) {
                    formShowErrorMessage(statusAlert, msgError);
                } else {
                    utility.handleWebServiceError(msgError);
                }
            }
        });
    }

    function handleAjaxFormSubmissionWithGatewaySelection(endpoint, data, button, statusAlert, msgSuccess, msgError, callbackSuccess, callbackFailure, callbackDone, buttonLabel, dialogTitle) {
        var actiondialog = $(tracking.data.domNodes.modals.messageAction);
        var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
            var coreData = {
                assetId: id,
                groupIds: groupIds,
                assetIds: assetIds,
                gateway: gateway,
                gatewayTimeout: gatewayTimeout,
                gatewayRetries: gatewayRetries
            };

            var mergedData = _.extend(coreData, data);

            $.when(handleAjaxFormSubmission(endpoint, mergedData, button, statusAlert, msgSuccess, msgError, callbackSuccess, callbackFailure))
                .done(function () {
                    actiondialog.modal('hide');
                });
        };

        openActionDialog(buttonLabel, dialogTitle, callback);
    }

    function routesLoading() {
        if (!tracking.data.routing.calculatingRoute) {
            return;
        }
        var btn = document.getElementById('RoutingCalculateRoute');
        btn.disabled = true;
        var loading = document.getElementById('routing-loading');
        var results = document.getElementById('routing-results');
        var status = document.getElementById('routing-status');
        loading.classList.add('is-visible');
        results.classList.remove('is-visible');
        status.classList.remove('is-visible');
    }

    function routesFound(e) {
        if (!tracking.data.routing.calculatingRoute) {
            return;
        }
        tracking.data.routing.calculatingRoute = false;
        var btn = document.getElementById('RoutingCalculateRoute');
        btn.disabled = false;
        var loading = document.getElementById('routing-loading');
        var results = document.getElementById('routing-results');
        var status = document.getElementById('routing-status');
        loading.classList.remove('is-visible');
        status.classList.remove('is-visible');
        results.classList.add('is-visible');

        // todo: recreate waypoints based on e.waypoints?
        // empty routing-destinations
        _.each(e.waypoints, function (waypoint) {
            // add .routing-destination
        });
    }

    function routesWaypointsChanged(e) {
        // ensure UI is synchronized with map
        console.log('waypoints changed');
        console.log(e);
        if (e.waypoints.length === 0) {
            return;
        }
        var destinations = $('.routing-destination input', tracking.data.domNodes.dialogs.routing);
        var newWaypoints = e.waypoints.length - destinations.length;
        var modifiedWaypoints = newWaypoints > 0;
        for (var i = 0; i < newWaypoints; i++) {
            routingAddDestinationOption();
        }

        destinations = $('.routing-destination input', tracking.data.domNodes.dialogs.routing);
        // synchronize with waypoints

        _.each(destinations, function (item, index) {
            if (e.waypoints[index] === undefined) {
                return;
            }
            var $item = $(item);
            var latlng = e.waypoints[index].latLng;
            if (latlng === null) {
                return;
            }
            var existingLatLng = $item.data('point')
            if (existingLatLng === undefined || existingLatLng !== latlng) {
                modifiedWaypoints = true;
                addPointToRoutingDestination($item, latlng);
                $item.val(convertToLatLngPreference(latlng.lat, latlng.lng, null)).data('point', latlng);
                tracking.reverseGeocode(latlng, function (success, address) {
                    if (success && address !== null && address !== '') {
                        $item.val(address);
                    }
                });
            }
        });

        if (modifiedWaypoints) {
            routingCheckForPendingDestination();
        }
    }

    function routesError(e) {
        console.log('general routing error')
        console.log(e);
        if (!tracking.data.routing.calculatingRoute) {
            return;
        }
        tracking.data.routing.calculatingRoute = false;
        var btn = document.getElementById('RoutingCalculateRoute');
        btn.disabled = false;
        var loading = document.getElementById('routing-loading');
        var results = document.getElementById('routing-results');
        var status = document.getElementById('routing-status');
        loading.classList.remove('is-visible');
        status.classList.add('is-visible');
        status.classList.add('alert-danger');
        status.textContent = e.error.message.replace('HTTP request failed: ', '');
        if (e.error.target && e.error.target.responseText) {
            try {
                var osrmError = JSON.parse(e.error.target.responseText);
                if (osrmError.code && osrmError.code === 'NoRoute') {
                    status.textContent = tracking.strings.NO_ROUTE_FOUND;
                }
            } catch (e) {
                status.textContent = tracking.strings.ROUTING_FAILURE;
            }
        }
        results.classList.remove('is-visible');
    }

    function placeRoutesLoading() {
        var btn = document.getElementById('PlaceRouteCalculate');
        btn.disabled = true;
        var loading = document.getElementById('place-route-loading');
        var results = document.getElementById('place-route-results');
        var status = document.getElementById('place-route-status');
        loading.classList.add('is-visible');
        results.classList.remove('is-visible');
        status.classList.remove('is-visible');
    }

    function placeRoutesFound() {
        var btn = document.getElementById('PlaceRouteCalculate');
        btn.disabled = false;
        var loading = document.getElementById('place-route-loading');
        var results = document.getElementById('place-route-results');
        var status = document.getElementById('place-route-status');
        loading.classList.remove('is-visible');
        status.classList.remove('is-visible');
        results.classList.add('is-visible');
    }

    function placeRoutesError(e) {
        console.log('place route error');
        console.log(e);
        var btn = document.getElementById('PlaceRouteCalculate');
        btn.disabled = false;
        var loading = document.getElementById('place-route-loading');
        var results = document.getElementById('place-route-results');
        var status = document.getElementById('place-route-status');
        loading.classList.remove('is-visible');
        status.classList.add('is-visible');
        status.classList.add('alert-danger');
        status.textContent = e.error.message.replace('HTTP request failed: ', '');
        if (e.error.target && e.error.target.responseText) {
            try {
                var osrmError = JSON.parse(e.error.target.responseText);
                if (osrmError.code && osrmError.code === 'NoRoute') {
                    status.textContent = tracking.strings.NO_ROUTE_FOUND;
                }
            } catch (e) {
                status.textContent = tracking.strings.ROUTING_FAILURE;
            }
        }
        results.classList.remove('is-visible');
    }

    function closePlaceRouting() {
        console.log('close place routing');
        tracking.directionsService
            //.off('routingstart', placeRoutesLoading)
            .off('routesfound', placeRoutesFound)
            .off('routingerror', placeRoutesError);
    	//tracking.directionsService.remove();
        tracking.directionsService.getPlan().setWaypoints([]);
        $j('#place-route-results').removeClass('is-visible');
        $j('#place-route-directions').empty();
        if (tracking.data.routeLine != null) {
        	removeItemFromMap(tracking.data.routeLine);
        }
        tracking.data.routeLine = null;
        tracking.data.routeLineEncoded = null;
		tracking.data.routeLinesEncoded = null;
        $j('#PlaceRouteGeofence,#RouteGeofence,#RouteGeofenceCreate').addClass('disabled');

        var btn = document.getElementById('PlaceRouteCalculate');
        btn.disabled = false;
        var loading = document.getElementById('place-route-loading');
        var results = document.getElementById('place-route-results');
        var status = document.getElementById('place-route-status');
        loading.classList.remove('is-visible');
        status.classList.remove('is-visible');
        results.classList.remove('is-visible');
    }

    function closeRouting() {
        console.log('close routing');
        // remove directions from map and directions window
        if (!tracking.data.routing.isOpen) {
            return;
        }

        tracking.directionsService
            //.off('routingstart', placeRoutesLoading)
            .off('routesfound', routesFound)
            .off('routingerror', routesError)
            .off('waypointschanged', routesWaypointsChanged);
        //tracking.directionsService.remove();
        tracking.directionsService.getPlan().setWaypoints([]);

        // clear destinations and reset
        var $destinations = $('.routing-destination', tracking.data.domNodes.dialogs.routing);
        _.each($destinations, function (destination) {
            var $destinationInput = $('input', destination).val('');
            var location = $destinationInput.data('location');
            if (location !== undefined) {
                location.removeFrom(tracking.map);
                $destinationInput.removeData('location');
            }
        });
        if ($destinations.length > 2) {
            for (var i = 2; i < $destinations.length; i++) {
                var elem = $destinations[i];
                elem.parentNode.removeChild(elem);
            }
        }
        routingCheckForPendingDestination();

        tracking.data.routing.isOpen = false;
        tracking.data.routing.start = null;
        tracking.data.routing.end = null;
        tracking.data.routing.isUsingMap = false;
        tracking.data.routing.calculatingRoute = false;
        tracking.data.routing.mapClickDestination = 'waypoint';
        tracking.data.routing.waypoints = [];
        stopChoosingMapLocation(tracking.state.mapClickHandlers.ROUTING);

        var btn = document.getElementById('RoutingCalculateRoute');
        btn.disabled = false;
        var loading = document.getElementById('routing-loading');
        var results = document.getElementById('routing-results');
        var status = document.getElementById('routing-status');
        loading.classList.remove('is-visible');
        status.classList.remove('is-visible');
        results.classList.remove('is-visible');
    }

    function openRuler() {
        $j('#map-ruler').addClass('active');
        if (tracking.data.ruler.isOpen) {
            return;
        }
        tracking.data.ruler.isOpen = true;
        startChoosingMapLocation(tracking.state.mapClickHandlers.RULER);
        tracking.data.ruler.points = [];
        var dialog = $j(tracking.data.domNodes.infoDialogs.ruler);
        dialog.dialog('open');
        $j('#rulerPoints').find('li').remove();
    }

    function closeRuler() {
        $j('#map-ruler').removeClass('active');
        if (!tracking.data.ruler.isOpen) {
            return;
        }
        tracking.data.ruler.isOpen = false;
        stopChoosingMapLocation(tracking.state.mapClickHandlers.RULER);
        for (var i = 0; i < tracking.data.ruler.points.length; i++) {
			removeItemFromMap(tracking.data.ruler.points[i]);
        }
        tracking.data.ruler.points = [];
        if (tracking.data.ruler.polygon != null) {
        	removeItemFromMap(tracking.data.ruler.polygon);
        }
        tracking.data.ruler.polygon = null;

        $j('#rulerPoints').find('li').remove();
        $j('#rulerTotalDistance').text('');
    }

    function updateRoutingLocation(destination, latlng) {
        destination.val(convertToLatLngPreference(latlng.lat, latlng.lng, null)).data('point', latlng);
        tracking.reverseGeocode(latlng, function (success, address) {
            if (success && address !== '') {
                destination.val(address);
            }
        });
    }

    function routingAddDestinationOption() {
        var destinationList = document.getElementById('routing-destinations');
        var $destinations = $('.routing-destination', destinationList);
        var $lastDestination = $destinations.last();
        var newDestination = $lastDestination[0].cloneNode(true);
        var use = $lastDestination[0].querySelector('.route-icon use');
        use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#map-marker');
        var input = newDestination.querySelector('input');
        input.id = 'RoutingDestination-' + Date.now().toString();
        input.value = '';
        $(input).removeData('location');
        destinationList.appendChild(newDestination);
    }

    function routingCheckForPendingDestination() {
        var hasPendingDestination = false;
        var destinations = $('.routing-destination input', tracking.data.domNodes.dialogs.routing);
        var addDestination = document.getElementById('routing-add-destination');
        for (var i = 0; i < destinations.length; i++) {
            var $item = $(destinations[i]);
            if ($item.val() === '') {
                hasPendingDestination = true;
                tracking.data.routing.mapClickDestination = $item;
                $item.prop('placeholder', tracking.strings.ENTER_DESTINATION_OR_USE_MAP);
                addDestination.classList.remove('is-visible');
                break;
            }
        }

        // move to the next needed destination if un-entered
        tracking.data.routing.isUsingMap = hasPendingDestination;
        if (!hasPendingDestination) {
            tracking.data.routing.mapClickDestination = 'waypoint';
            stopChoosingMapLocation(tracking.state.mapClickHandlers.ROUTING);

            addDestination.classList.add('is-visible');
            // recalculate route
        } else {
            startChoosingMapLocation(tracking.state.mapClickHandlers.ROUTING);
        }

        // fix icons if they're out of order
        var destinationList = document.getElementById('routing-destinations');
        var $destinations = $('.routing-destination', destinationList);
        var disableRemoval = $destinations.length <= 2;
        for (var i = 0; i < $destinations.length; i++) {
            var remove = $destinations[i].querySelector('.remove');
            if (disableRemoval) {
                remove.classList.add('hidden');
            } else {
                remove.classList.remove('hidden');
            }
            var use = $destinations[i].querySelector('.route-icon use');
            if (i === ($destinations.length - 1)) {
                // last destination
                use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#map-marker-alt');
            } else {
                use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#map-marker');
            }
        }

    }

    function addPointToRoutingDestination($routingDestination, latlng) {
        // either create or move the marker associated with this location
        var existingLocation = $routingDestination.data('location');
        if (existingLocation === undefined) {
            //var locationIcon = L.icon({
            //    iconUrl: createMarkerPath('Generic', 'red', null, null, null, false),
            //    iconSize: [36, 36],
            //    iconAnchor: [18, 18]
            //});
            var location = L.marker(latlng, { draggable: true });
            addItemToMap(location);
            var destination = $routingDestination;
            location.on('dragend', function (e) {
                updateRoutingLocation(destination, e.target.getLatLng());
            });
            $routingDestination.data('location', location);
        } else {
            // move the location
            existingLocation.setLatLng(latlng);
        }
    }

    function addPointToRouting(latlng) {
        //if (!tracking.data.routing.isUsingMap) {
        //    return;
        //}

        if(tracking.data.routing.mapClickDestination == 'waypoint') {
    //        tracking.data.routing.waypoints.push({
    //        	location: latlng.toString(),
				//point: latlng,
    //            stopover: true
    //        });
        } else {
            // todo: add marker denoting position, use .data to mark position
            addPointToRoutingDestination(tracking.data.routing.mapClickDestination, latlng);

            tracking.data.routing.mapClickDestination.val(convertToLatLngPreference(latlng.lat, latlng.lng, null)).data('point', latlng);
            var dest = tracking.data.routing.mapClickDestination;
            tracking.reverseGeocode(latlng, function (success, address) {
                if (success && address !== null && address !== '') {
                    dest.val(address);
                }
            });

            dest.prop('placeholder', tracking.strings.ENTER_DESTINATION);
            routingCheckForPendingDestination();
            //$j('button.routing-map').prop('disabled', false).removeClass('active');
            //tracking.data.routing.mapClickDestination = 'waypoint';
        }
    }

    function addPointToRuler(latlng) {
        if (!tracking.data.ruler.isOpen) {
            return;
        }

		var rulerTitle = String.fromCharCode('a'.charCodeAt(0) + (tracking.data.ruler.points.length)).toUpperCase();
		var pointIcon = L.icon({
			iconUrl: createMarkerPath('Generic', 'gold', null, null, null, false),
			iconSize: [36, 36],
			iconAnchor: [18, 18]
		});
        var rulerPoint = L.marker(latlng, { icon: pointIcon, draggable: true, text: rulerTitle });

        tracking.data.ruler.points.push(rulerPoint);
		addItemToMap(rulerPoint);

		if ((tracking.data.ruler.polygon == null) && (tracking.data.ruler.points.length > 1)) {
			tracking.log('add geodesic line');
			tracking.data.ruler.polygon = L.geodesic([[tracking.data.ruler.points[0].getLatLng(), tracking.data.ruler.points[1].getLatLng()]], {
				weight: 5,
				color: '#cea843',
				wrap: true,
				steps: 50,
				opacity: 0.7
			});
        	addItemToMap(tracking.data.ruler.polygon);
        }

		rulerPoint.on('drag', function (e) {
			updateRuler();
		});

        // add point to ruler dialog
        var point = el('li', [
            text(rulerTitle + ' - ' + convertToLatLngPreference(rulerPoint.getLatLng().lat, rulerPoint.getLatLng().lng) + ' '),
            el('a.delete', { href: '#' }, tracking.strings.DELETE)
        ]);
        mount(document.getElementById('rulerPoints'), point);
        updateRuler();
    }

    function removeRulerPoint(index) {
        if (!tracking.data.ruler.isOpen) {
            return;
        }
        if (tracking.data.ruler.points.length === 0) {
            return;
        }
        if (tracking.data.ruler.polygon == null) {
            return;
        }
		removeItemFromMap(tracking.data.ruler.points[index]);
        var removed = tracking.data.ruler.points.splice(index, 1);
        $j('#rulerPoints li').eq(index).remove();
        updateRuler();
    }

    function updateRuler() {
        if (!tracking.data.ruler.isOpen) {
            return;
        }
        if (tracking.data.ruler.polygon == null) {
            return;
        }
        if (tracking.data.ruler.points.length <= 1) {
            removeItemFromMap(tracking.data.ruler.polygon);
            $('#rulerTotalDistance').text('');
            return;
        }

        var points = [];
        var rulerPointsContainer = document.getElementById('rulerPoints');
        var totalDistance = 0;
        var rulerPoints = [];
        for (var i = 0; i < tracking.data.ruler.points.length; i++) {
            var point = tracking.data.ruler.points[i].getLatLng().wrap();
            tracking.data.ruler.points[i].setLatLng(point);
            var pointDistance = null;
            points.push(point);
            var rulerTitle = String.fromCharCode('a'.charCodeAt(0) + i).toUpperCase();
            if (i > 0) {
                pointDistance = tracking.data.ruler.points[i - 1].getLatLng().distanceTo(point);
                totalDistance += pointDistance;
            }
            var title = rulerTitle + ' - ' + convertToLatLngPreference(point.lat, point.lng) + ' ';
            if (pointDistance != null) {
                title += '(' + convertFromMetresToUserDistancePreference(pointDistance) + ') ';
            }
            rulerPoints.push(el('li', [
                title,
                el('a.delete', { href: '#' }, tracking.strings.DELETE)
            ]));
        }
        setChildren(rulerPointsContainer, rulerPoints);
        tracking.data.ruler.polygon.setLatLngs([points]);
        addItemToMap(tracking.data.ruler.polygon);

        // update total distance
        if (tracking.data.ruler.points.length > 1) {
            document.getElementById('rulerTotalDistance').textContent = convertFromMetresToUserDistancePreference(totalDistance);
        }
    }

    function canAssetBeRouted(asset) {
        var assetMarker = getCurrentMarkerForAsset(asset);
        if (assetMarker === undefined) {
            return false;
        }
        return true;
    }

    function populateCustomAttributes(itemAttributes, attributeType, parentId) {
        var attributeinformation = [];
        if (itemAttributes == null
            || itemAttributes.length == 0
            || tracking.data.attributeGroups == null) {
            return attributeinformation;
        }

        for (var i = 0; i < tracking.data.attributeGroups.length; i++) {
            // attributes by group
            var group = tracking.data.attributeGroups[i];
            if (group.Type != attributeType) {
                continue;
            }
            var attributes = findAttributesForAttributeGroup(group, attributeType);
            if (attributes == null || attributes.length == 0) {
                continue;
            }
            var groupattributeinformation = [];
            // see if there's a matching value
            for (var k = 0 ; k < attributes.length; k++) {
                var attribute = attributes[k];
                for (var j = 0; j < itemAttributes.length; j++) {
                    var itemAttribute = itemAttributes[j];
                    if (attribute.Id == itemAttribute.Id) {
                        groupattributeinformation.push(includeRowIfNotNull(attribute.Name, itemAttribute.Value));
                    }
                }
            }
            groupattributeinformation = _.compact(groupattributeinformation);
            if (groupattributeinformation.length > 0) {
                attributeinformation.push(createAccordionCard('attribute-' + group.Id, parentId, group.Name, el('table', el('tbody', groupattributeinformation))));
            }
        }
        return attributeinformation;
    }

    function waypointGetRoute(waypoint) {
        if (waypoint == null) {
            return;
        }
        var asset = findAssetById(waypoint.AssetId);

        if (asset == null) {
            return;
        }

        var assetMarker = getCurrentMarkerForAsset(asset);
        if (assetMarker === undefined) {
            return;
        }
        var waypointMarker = null;
        for (var i = 0; i < waypointMarkers.length; i++) {
            var wpId = waypointMarkers[i].data.waypointId;
            if (waypoint.Id == wpId) {
                waypointMarker = waypointMarkers[i];
                break;
            }
        }
        var assetLocation = assetMarker.data.location;
        var waypointLocation = waypointMarker.data.location;
        var route = {
            origin: L.latLng(assetLocation.Lat, assetLocation.Lng),
            destination: L.latLng(waypointLocation.Lat, waypointLocation.Lng)
        };

        waypointClearRoute(waypoint);

        // new routing service just for this waypoint and asset
        var routingService = L.Routing.control({
            router: new L.Routing.OSRMv1({
                serviceUrl: '/services/route.ashx',
                language: getOSRMLanguage(tracking.language)
            }),
            formatter: new L.Routing.Formatter({
                language: getOSRMLanguage(tracking.language),
                units: (tracking.preferences.PREFERENCE_SPEED === 1 ? 'metric' : 'imperial')
            }),
            useZoomParameter: false
        }).on('routingerror', function (e) {
            $('#WaypointRoute').show().text(tracking.strings.WAYPOINT_ROUTE_ERROR).addClass('error');
        }).on('routeselected', function (e) {
            waypoint.route = e.route;
            $('#WaypointRoute').show().removeClass('error');
            // show clear route button
            $('#WaypointRemoveRoute').show();
            $('#WaypointGetRoute').hide();
        });
        routingService.control = routingService.onAdd(tracking.map);
        routingService.setWaypoints([route.origin, route.destination]);
        $('#WaypointRoutePanel').append(routingService.control);
        waypoint.routingService = routingService;
    }

    function waypointClearRoute(waypoint) {
        if (waypoint == null) {
            return;
        }

        if (waypoint.route != null) {
            waypoint.route = null;
            if (waypoint.routingService != null) {
                waypoint.routingService.remove();
            }
        }
        $('#WaypointRemoveRoute').hide();
        $('#WaypointGetRoute').show();
    }

    function waypointMarkComplete(waypoint) {
        if (waypoint == null) {
            return;
        }

        var asset = findAssetById(waypoint.AssetId);
        if (asset == null) {
            return;
        }

        var button = document.getElementById('WaypointMarkComplete');

        var key = 'waypoint-complete';
        toggleLoadingMessage(true, key);
        if (button !== null) {
            button.disabled = true;
        }
        var dataPost = {
            assetId: asset.Id,
            id: waypoint.Id
        };
        $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/WaypointMarkComplete'),
            data: JSON.stringify(dataPost),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    if (result.Success != true) {
                        utility.handleWebServiceError(tracking.strings.WAYPOINT_COMPLETE_ERROR);
                    }
                }
                toggleLoadingMessage(false, key);
                $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('close');
                if (button !== null) {
                    button.disabled = false;
                }
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.WAYPOINT_COMPLETE_ERROR);
                toggleLoadingMessage(false, key);
                if (button !== null) {
                    button.disabled = false;
                }
            }
        });
    }

    function populateGeofenceInformation(geofence) {
        var info = [];
        if (geofence.Description !== null && geofence.Description !== '') {
            info.push(el('.map-item-description', geofence.Description));
        }

        var itemPhoto = null;
        if (geofence.PhotoType != null && geofence.PhotoType != '') {
            itemPhoto = el('a', { href: '/uploads/images/fences/' + geofence.Id + '.' + geofence.PhotoType, target: '_blank' }, el('img', {
                src: '/uploads/images/fences/' + geofence.Id + '_thumb.' + geofence.PhotoType
            }));
        }

        var fenceInfo = [];
        var generalinformation = includeRowIfNotNull(tracking.strings.PHOTO, itemPhoto);
        if (generalinformation != null) {
            fenceInfo.push(createAccordionCard('general', 'geofence-information-accordion', tracking.strings.GENERAL_FIELDS, el('table', el('tbody', generalinformation))));
        }
        fenceInfo = _.compact(fenceInfo.concat(populateCustomAttributes(geofence.Attributes, 1, 'geofence-information-accordion')));
        if (fenceInfo.length > 0) {
            info.push(el('#geofence-information-accordion.position-accordion.mt-1', fenceInfo));
        }
        return el('.markercontent', info);
    }

    function populateWaypointInformation(waypoint, asset) {
        var info = [];
        info.push(el('div#WaypointRequestETASuccess.success', { style: { display: 'none' } }, tracking.strings.WAYPOINT_REQUESTED));
        info.push(el('div.form-group', [ el('label', tracking.strings.ASSET), el('div.item', asset.Name) ]));
        if (waypoint.Message != null && waypoint.Message !== '') {
            info.push(el('div.form-group', [el('label', tracking.strings.MESSAGE), el('div.item', waypoint.Message)]));
        }
        info.push(el('div#WaypointDistanceTo', { style: { display: waypoint.DistanceTo != null ? '' : 'none' }, dataset: { waypointId: waypoint.Id } }, el('div.form-group', [el('label', tracking.strings.DISTANCE_TO), el('div.item', waypoint.DistanceTo) ])));
        info.push(el('div#WaypointETA', { style: { display: waypoint.ETA != null ? '' : 'none' }, dataset: { waypointId: waypoint.Id } }, el('div.form-group', [el('label', tracking.strings.ETA), el('div.item', waypoint.ETA)])));
        var isRemoveVisible = (waypoint.route != null);
        var getRouteClass = '';
        if (!isRemoveVisible && !canAssetBeRouted(asset)) {
            getRouteClass = '.disabled';
        }

        var buttonItems = [
            el('button#WaypointGetRoute.btn.btn-primary' + getRouteClass, { style: { display: isRemoveVisible ? 'none' : '' }, dataset: { waypointId: waypoint.Id } }, [
                svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#compass' } })),
                text(' '),
                el('span', tracking.strings.GET_ROUTE)
            ]),
            el('button#WaypointRemoveRoute.btn.btn-secondary', { style: { display: isRemoveVisible ? '' : 'none' }, dataset: { waypointId: waypoint.Id } }, [
                svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#times' } })),
                text(' '),
                el('span', tracking.strings.CLEAR_ROUTE)
            ]),
        ];

        // asset have ETA ability? - not for shared users
        if (!tracking.user.isAnonymous) {
            if (!waypoint.IsServerSide && (($.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1) || ($.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) != -1))) {
                buttonItems.push(el('button#WaypointRequestETAUpdate.btn.btn-secondary', { dataset: { waypointId: waypoint.Id } }, [
                    svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#clock-solid' } })),
                    text(' '),
                    el('span', tracking.strings.REQUEST_ETA)
                ]));
            } else if (waypoint.IsServerSide) {
                buttonItems.push(el('button#WaypointMarkComplete.btn.btn-secondary', { dataset: { waypointId: waypoint.Id } }, [
                    svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#check' } })),
                    text(' '),
                    el('span', tracking.strings.MARK_COMPLETE)
                ]))
            }
        }
        info.push(el('div.form-group', buttonItems));
        info.push(el('div#WaypointRoute', { style: { display: isRemoveVisible ? '' : 'none' }, dataset: { waypointId: waypoint.Id } }, el('div#WaypointRoutePanel')));
        return el('div#waypoint-information', info);
    }

    function populateSharedViewListItem(label, text) {
        return el('li', [ el('div.meta-header', label + ':'), el('div.meta-item', text)]);
    }

    function populateSharedViewInformation(sharedView) {
        var frag = document.createDocumentFragment();

        var cont = document.createElement('div');
        cont.className = 'container-fluid';
        var row = document.createElement('div');
        row.className = 'section-meta row';
        var leftCol = document.createElement('div');
        leftCol.className = 'col-7';
        var list = document.createElement('ul');

        var name = populateSharedViewListItem(tracking.strings.DEFAULT_MODE, sharedView.Preferences.DefaultMode === tracking.mapModes.LIVE ? tracking.strings.MODE_LIVE : tracking.strings.MODE_HISTORY);
        list.appendChild(name);

        if (sharedView.Description !== null && sharedView.Description !== '') {
            var description = populateSharedViewListItem(tracking.strings.DESCRIPTION, sharedView.Description);
            description.className = 'pt-2';
            list.appendChild(description);
        }

        leftCol.appendChild(list);

        var shareCont = document.createElement('div');
        shareCont.className = 'pt-3';
        var shareButton = document.createElement('a');
        shareButton.href = '#';
        shareButton.className = 'btn btn-primary';
        shareButton.id = 'SharedViewMapInformationShare';

        var shareIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        var shareIconTitle = document.createElementNS('http://www.w3.org/2000/svg', 'title');
        shareIconTitle.textContent = tracking.strings.SHARE_THIS_VIEW;
        shareIcon.appendChild(shareIconTitle);
        var shareIconType = document.createElementNS('http://www.w3.org/2000/svg', 'use');
        shareIconType.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#share-alt-solid');
        shareIcon.appendChild(shareIconType);
        shareButton.appendChild(shareIcon);

        var shareLabel = document.createElement('span');
        shareLabel.textContent = tracking.strings.SHARE_THIS_VIEW;
        shareButton.appendChild(shareLabel);
        shareCont.appendChild(shareButton);
        leftCol.appendChild(shareCont);
        
        row.appendChild(leftCol);

        // right column
        var rightCol = document.createElement('div');
        rightCol.className = 'col-5 info-right';
        var rightList = document.createElement('ul');
        rightCol.appendChild(rightList);

        var status = populateSharedViewListItem(tracking.strings.STATUS, (sharedView.IsEnabled ? tracking.strings.ENABLED : tracking.strings.DISABLED));
        rightList.appendChild(status);
        var expires = populateSharedViewListItem(tracking.strings.EXPIRES, (sharedView.ExpiresOnEpoch === null ? tracking.strings.NEVER : moment.utc(sharedView.ExpiresOnEpoch).format(tracking.user.dateFormat)));
        rightList.appendChild(expires);
        var availability = populateSharedViewListItem(tracking.strings.AVAILABILITY, (sharedView.IsPublic ? tracking.strings.PUBLIC : tracking.strings.PRIVATE));
        rightList.appendChild(availability);
        var messaging = populateSharedViewListItem(tracking.strings.TWO_WAY_MESSAGING, (sharedView.IsMessagingEnabled ? tracking.strings.ENABLED : tracking.strings.DISABLED));
        rightList.appendChild(messaging);
        var created = populateSharedViewListItem(tracking.strings.CREATED_ON, moment.utc(sharedView.CreatedOnEpoch).format(tracking.user.dateFormat));
        rightList.appendChild(created);
        var updated = populateSharedViewListItem(tracking.strings.UPDATED_ON, (sharedView.UpdatedOnEpoch === null ? tracking.strings.NEVER : moment.utc(sharedView.UpdatedOnEpoch).format(tracking.user.dateFormat)) );
        rightList.appendChild(updated);
        row.appendChild(rightCol);

        cont.appendChild(row);
        frag.appendChild(cont);
        return frag;
    }

    function populatePlaceInformation(place) {
        var info = [];

        if (place.Description !== null && place.Description !== '') {
            var desc = el('div.map-item-description', place.Description);
            info.push(desc);
        }

        var itemPhoto = null;
        if (place.PhotoType != null && place.PhotoType != '') {
            itemPhoto = el('a', { href: '/uploads/images/places/' + place.Id + '.' + place.PhotoType, target: '_blank'}, 
                            el('img', { src: '/uploads/images/places/' + place.Id + '_thumb.' + place.PhotoType })
                        );
        }
        
        // location table
        info.push(el('table.map-item-details', el('tbody', el('tr', [ el('td.field', tracking.strings.LAT_LNG), el('td', convertToLatLngPreference(place.Location.Lat, place.Location.Lng, place.Grid)) ]))));

        var placeInfo = [];
        var generalinformation = _.compact([includeRowIfNotNull(tracking.strings.CONTACT, place.Contact), includeRowIfNotNull(tracking.strings.PHOTO, itemPhoto)]);
        if (generalinformation.length > 0) {
            placeInfo.push(createAccordionCard('general', 'place-information-accordion', tracking.strings.GENERAL_FIELDS, el('table', el('tbody', generalinformation))));
        }

        placeInfo = _.compact(placeInfo.concat(populateCustomAttributes(place.Attributes, 2, 'place-information-accordion')));
        if (placeInfo.length > 0) {
            info.push(el('#place-information-accordion.position-accordion.mt-1', placeInfo));
        }

        return el('.markercontent', info);
    }

    function initializeSharedViewInformationDialog() {
        tracking.data.domNodes.infoDialogs.sharedViewInformation.parentNode.classList.add('default-state');
        $dialog = $(tracking.data.domNodes.infoDialogs.sharedViewInformation);
        $dialog.dialog('option', 'title', createDialogTitleFragment(tracking.strings.EDIT_MODE + ' - ' + tracking.strings.SHARED_VIEWS));
        var dialogPanel = document.getElementById('dialog-shared-view-information');
        var dialogTitleName = dialogPanel.querySelector('div.ui-dialog-titlebar');

        dialogTitleName.classList.add('has-svg-icon');
        dialogTitleName.style.backgroundImage = null;

        dialogTitleName.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', getSvgIconForItemType('shared-views'));
        dialogTitleName.querySelector('svg').setAttribute('style', 'color: #bbbbbb; --color-primary: #bbbbbb; --color-secondary: #bbbbbb');
    }

    function deselectSharedView() {
        console.log('shared view de-selected. none active.');
        _.each(tracking.data.domNodes.sharedViews, clearSettingsNode);
        tracking.data.domNodes.panels.secondary.removeAttribute('data-chat-enabled');
        clearSharedViewData(true);
        tracking.data.sharedView.current = null;
        tracking.data.sharedView.temp = null;

        initializeSharedViewInformationDialog();

        var $dialog = $(tracking.data.domNodes.infoDialogs.sharedViewInformation);
        if (!$dialog.dialog('isOpen')) {
            moveOrOpenDialogRelativeTo($dialog, tracking.map._container, 'center center');
            $dialog.unbind('dialogclose');
        }

        //$(tracking.data.domNodes.infoDialogs.sharedViewInformation).dialog('close');
        // resetting map type to prior
        tracking.data.isSatelliteLabelOverlayEnabled = tracking.data.sharedView.priorIsSatelliteLabelOverlayEnabled;
        changeMapType(tracking.data.sharedView.priorMapType);
    }

    function selectSharedView(sharedView) {
        if (sharedView === null) {
            deselectSharedView();
            return;
        }

        tracking.data.domNodes.infoDialogs.sharedViewInformation.parentNode.classList.remove('default-state');

        if (tracking.data.sharedView.current !== null) {
            // clear out previous shared view
            console.log('selected shared view changed?'); // handled by other close callback?
        }

        if (tracking.data.sharedView.current === sharedView) {
            // nothing's changed
            console.log('selected shared view did not change.');
            return;
        }

        // deselect previous
        _.each(tracking.data.domNodes.sharedViews, clearSettingsNode);
        console.log('selected shared view changed');
        highlightActiveItemInPrimaryPanel('shared-views', sharedView.Id);
        tracking.data.sharedView.current = sharedView;
        clearSharedViewData(true);
        querySharedViewData(sharedView, null, false);

        // if secondary panel is opened, hide the sharedviewinformation dialog
        showSharedViewInformationOnMap(sharedView, !tracking.state.openPanels.secondary);
    }

    function showSharedViewInformationOnMap(sharedView, doOpen) {
        if (sharedView === undefined || sharedView === null) {
            return;
        }

        tracking.data.domNodes.infoDialogs.sharedViewInformation.parentNode.classList.remove('default-state');
        var dialog = tracking.data.domNodes.infoDialogs.sharedViewInformation;
        var $dialog = $(dialog);
        var content = populateSharedViewInformation(sharedView);
        createQuickActionsForSharedView(sharedView);
        dialog.setAttribute('data-shared-view-id', sharedView.Id);
        $dialog.data.sharedViewId = sharedView.Id;

        var contentArea = dialog.querySelector('.content-selected');

        while (contentArea.firstChild) {
            contentArea.removeChild(contentArea.firstChild);
        }
        contentArea.appendChild(content);

        $dialog.dialog('option', 'title', createDialogTitleFragment(sharedView.Name, tracking.strings.SHARED_VIEWS));
        var dialogPanel = document.getElementById('dialog-shared-view-information');
        var dialogTitleName = dialogPanel.querySelector('div.ui-dialog-titlebar');

        dialogTitleName.classList.add('has-svg-icon');
        dialogTitleName.style.backgroundImage = null;

        dialogTitleName.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', getSvgIconForItemType('shared-views', sharedView));
        dialogTitleName.querySelector('svg').setAttribute('style', 'color: ' + sharedView.Color + '; --color-primary: ' + sharedView.Color + '; --color-secondary: ' + sharedView.Color);

        if (doOpen) {
            moveOrOpenDialogRelativeTo($dialog, tracking.map._container, 'center center');
            $dialog.unbind('dialogclose');
        }
    }

    function showPlaceInformationOnMap(marker, place, isHover) {
        console.warn('showPlaceInformationOnMap');
        if (place === undefined || place === null) {
            return;
        }

        if (isHover === undefined || isHover === null) {
            isHover = false;
        }

        var isMarkerSelected = !isHover;

        // if there is already marker selected, and this is not it, do not show the information
        if (tracking.state.selectedMarker !== null) {
            if (tracking.state.selectedMarker !== marker) {
                return;
            }
            isMarkerSelected = true;
        }

        var latLng = marker.getLatLng();
        var dialog = $(tracking.data.domNodes.infoDialogs.mapItemInformation);
        dialog.data('placeId', place.Id);

        dialog.empty();
        dialog.append(populatePlaceInformation(place));

        // set zoom
        if (tracking.map.getZoom() < tracking.options.defaultZoom) {
        	tracking.map.setView(latLng, tracking.options.defaultZoom);
        } else {
			tracking.map.panTo(latLng);
        }

        // titlebar
        dialog.dialog('option', 'title', createDialogTitleFragment(place.Name));
        var dialogTitleBar = $('div.ui-dialog-titlebar', dialog.parent());
        var titleIcon = 'url(' + createMarkerPath('Generic', place.Color, null, null, null, false, null, false, false) + ')';
        dialogTitleBar[0].style.backgroundImage = titleIcon;

        // place dialog
        var icon = marker._icon;
        if (icon === undefined && tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined && tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
            icon = tracking.data.history.markerClustersByAssetId[asset.Id].getVisibleParent(marker)._icon;
        }
        var at = 'right center';
        if (icon === undefined || icon === null) {
            // if there's no icon for this location (live position that no longer is mapped), then center it on the map
            // todo: place it at the appropriate location on the map instead of in the center by showing its marker
            icon = tracking.map._container;
            at = 'center center';
        }

        // only move the dialog if its not already open
        if (!dialog.dialog('isOpen')) {
            dialog.dialog('option', 'position', {
                my: 'left center', at: at, of: icon, collision: 'flipfit', within: document.getElementById('map'), using:
                    function (param1, param2) {
                        $(this).css(param1);
                    }
            });
            dialog.dialog('open'); // dialog must be opened away from the marker so it can be moved without triggering
        } else {
            // dialog was already open but may have been resized due to content changes
            var parent = dialog.closest('.ui-dialog');
            parent.position({
                my: 'left center',
                at: at,
                of: icon,
                collision: 'flipfit',
                within: document.getElementById('map'),
                using: function (param1, param2) {
                    // if the dialog is currently in the same horizontal position as before
                    // then we will assume the user has not manually moved it and it can be
                    // repositioned programmatically to fit centered vertically
                    if (dialog.closest('.ui-dialog').position().left == param1.left) {
                        // just move the dialog vertically
                        $(this).css(param1);
                    }
                }
            });
        }

        if (!isMarkerSelected) {
            dialog.dialog({ dialogClass: 'no-close' });
        } else {
            dialog.dialog({ dialogClass: '' });
        }

        dialog.dialog('moveToTop');
        dialog.unbind('dialogclose');

        dialog.bind('dialogclose', function (event, ui) {
            // todo: bug here when selecting locations on the map
            // previous, temporary dialog is closed
            // should only mark as selected/unselected if a permanent dialog is opened

            var isHover = $j(this).data('isHover');
            if (isHover) {
                return;
            }
            tracking.log('dialog close');
            marker.data.selected = false;
            markerUnhover(marker); // todo: fix this recursive bug

            tracking.state.selectedMarker = null;
        });
    }

    function resizeInfoWindow() {
    	if (tracking.state.openWindow == null) {
			return;
    	}

    }

	function openInfoWindow(title, content, location) {
		var infobox = tracking.state.openWindow;
		if (infobox == null) {
			infobox = L.popup({
				maxWidth: 400,
				maxHeight: $j('#map').height() - 80,
				autoPan: false
			});
			tracking.state.openWindow = infobox;
		}
		// todo: will it have to be destroyed and recreated to update properly?
		infobox.setLatLng(location);
		//infobox.setContent(tracking.templates.infowindow.replace('{title}', title).replace('{content}', content));
		infobox.setContent(content);
		infobox.openOn(tracking.map);

		//infobox.setOptions({
		//	htmlContent: tracking.templates.infowindow.replace('{title}', title).replace('{content}', content),
		//	maxHeight: ($j('#map').height() - 75),
		//	maxWidth: 400
		//});

		//$j('#map_infowindow_close').off('click', 'a');
		//$j('#map_infowindow_close').on('click', 'a', function (e) {
		//	e.preventDefault();
		//	console.log('close click');
		//	if (tracking.state.openWindow != null) {
		//		tracking.state.openWindow.remove();
		//	}
		//});

	//	resizeInfoWindow();
	}

    function initEmergencyAudio() {
        var played = 0;
        tracking.audio.emergency = new Howl({
            src: [
                '/content/audio/emergency-2.ogg', 
                '/content/audio/emergency-2.mp3', 
                '/content/audio/emergency-2.m4a', 
                '/content/audio/emergency-2.wav'
            ],
            html5: true,
            loop: false,
            onplayerror: function () {
                tracking.audio.emergency.once('unlock', function () {
                    tracking.audio.isEmergencyPlaying = true;
                    tracking.audio.emergency.play();
                });
            },
            onend: function () {
                // we want to play this sound twice in a row
                played++;
                if (played > 1) {
                    tracking.audio.emergency.stop();
                    tracking.audio.isEmergencyPlaying = false;
                    played = 0;
                } else {
                    tracking.audio.emergency.play();
                }
            }
        });
        tracking.audio.text = new Howl({
            src: [
                '/content/audio/text-notification.ogg',
                '/content/audio/text-notification.mp3',
                '/content/audio/text-notification.m4a',
                '/content/audio/text-notification.wav'
            ],
            html5: true,
            loop: false,
            onplayerror: function () {
                tracking.audio.text.once('unlock', function () {
                    tracking.audio.text.play();
                });
            }
        });
    }

    function alertUserOfSystemNotification() {
        if (!tracking.preferences.PREFERENCE_EMERGENCY_AUDIO)
            return;

        $j('#jGrowl .system-notification').effect('highlight', {}, 1000);

        tracking.audio.text.play();
    }

    function alertUserOfTextMessage() {
        if (!tracking.preferences.PREFERENCE_EMERGENCY_AUDIO)
            return;

        $j('#jGrowl .text-message').effect('highlight', {}, 1000);

        tracking.audio.text.play();
        clearInterval(tracking.intervals.text);
        tracking.intervals.text = setInterval(checkTextNotifications, 1000 * 60 * 1);
    }

    function alertUserOfEmergency() {
        if (!tracking.preferences.PREFERENCE_EMERGENCY_AUDIO) {
            return;
        }

        if ((tracking.user.isAnonymous) || (tracking.options.hideEmergencyEvents)) {
            return;
        }

        // shake the emergency notification area and user is not hovering notifications
        $j('#jGrowl').effect('shake', {times: 2, direction: 'down', distance: 10}, 300);

        if (tracking.audio.isEmergencyPlaying !== true) {
            tracking.audio.isEmergencyPlaying = true;
            tracking.audio.emergency.play();
        }

		// reset the interval so there is always a consistent gap before replaying
		clearInterval(tracking.intervals.emergency);
        tracking.intervals.emergency = setInterval(checkEmergencyNotifications, 1000 * 10 * 1);
    }

    function queryAlertsRequiringAcknowledgement(defaultEvent) {
        if (tracking.user.isAnonymous) {
            return;
        }

        if (tracking.data.lastAlertId == null) {
            eventId = defaultEvent;
        } else {
            eventId = tracking.data.lastAlertId;
        }

        var dataPost = { eventId: eventId };
        $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetAlertsRequiringAcknowledgment'),
            data: JSON.stringify(dataPost),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                if (msg.d) {
                    var events = msg.d;
                    var eventsBefore = tracking.data.alerts.length;
                    //if (events.length > 0)
                    //    console.log(events);
                    addAssetAlerts(events);
                    if (tracking.data.alerts.length != eventsBefore) {
                        // flash update notification
                        var currBg = $j('#event-panel-tab-alerts').css('background-color');
                        $j('#event-panel-tab-alerts').stop().css('background-color', '#ffff9c').animate({ backgroundColor: currBg }, 3000, function () {
                            $j('#event-panel-tab-alerts').css('background-color', '');
                        });
                        $j('#alerts-control-btn').stop().css('background-color', '#ffff9c').animate({ backgroundColor: '#ffffff' }, 3000);
                    }
                }
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_ALERTS_ERROR);
            }
        });
    }

    function queryLatestEvents(defaultEvent) {
        if (tracking.data.pending.events) {
            return;
        }

        if (tracking.data.lastEventId === null) {
            if (defaultEvent === undefined || defaultEvent === null) {
                return;
            }
            eventId = defaultEvent;
        } else {
            eventId = tracking.data.lastEventId;
        }

        tracking.data.pending.events = true;
        var dataPost = { eventId: eventId };
        $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetLatestEvents'),
            data: JSON.stringify(dataPost),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                tracking.data.pending.events = false;
                if (msg.d) {
                    var events = msg.d;
                    var eventsBeforeCount = tracking.data.live.events.length;
                    if (events.length > 0) {
                        tracking.log(events.length + ' events received.');
                    }
                    addAssetEvents(events, tracking.mapModes.LIVE);
                    //if (tracking.data.live.events.length !== eventsBeforeCount) {
                    //    // flash update notification to indicate new events added
                    //    var currBg = $j('#event-panel-tab-events').css('background-color');
                    //    $j('#event-panel-tab-events').stop().css('background-color', '#ffff9c').animate({ backgroundColor: currBg }, 3000, function () {
                    //        $j('#event-panel-tab-events').css('background-color', '');
                    //    });
                    //    $j('#event-control-btn').stop().css('background-color', '#ffff9c').animate({ backgroundColor: '#ffffff' }, 3000);
                    //}
                }
            },
            error: function (xhr, status, error) {
                tracking.data.pending.events = false;
                utility.handleWebServiceError(tracking.strings.MSG_EVENTS_ERROR);
            }
        });
    }

    function queryLatestNotifications() {
        if (tracking.data.queryingNotifications) {
            return;
        }

        if (tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging) {
            return;
        }

        tracking.data.queryingNotifications = true;
        var dataPost = { lastMessageId: tracking.data.lastNotificationId };
        $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetLatestNotificationsForUser'),
            data: JSON.stringify(dataPost),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                if (msg.d) {
                    var notifications = msg.d;
                    for (var i = 0; i < notifications.length; i++) {
                        var notify = notifications[i];
                        var asset = findAssetById(notify.AssetId);
                        if (asset == null) {
                            continue;
                        }
                        var positionElement = null;
                        if (notify.PositionId != null) {
                            // show position button, will likely have to load from ajax or include lat/lng here
                            positionElement = el('button.notification-position.btn.btn-link.btn-sm', { dataset: { assetId: asset.Id, positionId: notify.PositionId } }, tracking.strings.SHOW_POSITION);
                        }
                        var replyElement = el('a.reply-to-text.btn.btn-link.btn-sm', { href: '#', dataset: { assetId: asset.Id } }, tracking.strings.REPLY);
                        var confirmElement = el('button.confirm.btn.btn-sm', { dataset: { assetId: asset.Id, notificationId: notify.Id}}, tracking.strings.CONFIRM);
                        var device = findDeviceById(asset.DeviceId);
                        if (!device.SupportsMessaging) {
                            replyElement = null;
                        }
                        var notifyText = notify.Text === null ? '' : notify.Text;
                        switch (notify.IsEmergency) {
                            case false: // text message
                                // todo: determine if the asset is valid and can be replied to? (right now asset will only be the asset that "received" the message)
                                var msg = [el('span.time', notify.CreatedOn), el('div.message', notifyText)];
                                var buttons = _.compact([confirmElement, positionElement, replyElement]);
                                $.jGrowl(msg, {
                                    header: notify.Type + ': ' + asset.Name, sticky: true, theme: 'text-message', closer: false,
                                    mustConfirm: true, notificationId: notify.Id, assetId: asset.Id, text: notify.Text, type: 'text', buttons: buttons,
                                    afterOpen: function (e, m, o) {
                                        if (tracking.preferences.PREFERENCE_EMERGENCY_AUDIO) {
                                            alertUserOfTextMessage();
                                        }
                                    },
                                    appendTo: '#map-mode-container'
                                });
                                break;
                            case true: // emergency/sos
                                if ((tracking.user.isAnonymous) || (tracking.options.hideEmergencyEvents)) {
                                    continue;
                                }
                                // group emergency messages where we kill emergency messages for the same asset
                                // that don't have a related text message -- only keep the latest
                                var msg = [el('span.time', notify.CreatedOn), el('div.message', notifyText)];
                                var buttons = _.compact([confirmElement, positionElement, replyElement]);
                                $.jGrowl(msg, {
                                    header: notify.Type + ': ' + asset.Name,
                                    sticky: true,
                                    theme: 'emergency-on',
                                    closer: false,
                                    mustConfirm: true,
                                    notificationId: notify.Id,
                                    assetId: asset.Id,
                                    text: notify.Text,
                                    type: 'emergency',
                                    buttons: buttons,
                                    afterOpen: function (e, m, o) {
                                        if (tracking.preferences.PREFERENCE_EMERGENCY_AUDIO) {
                                            tracking.throttles.emergencyAlert();
                                        }
                                    }, appendTo: '#map-mode-container'
                                });
                                break;
                        }
                        var thisId = parseInt(notify.Id);
                        if (tracking.data.lastNotificationId == null) {
                            tracking.data.lastNotificationId = thisId;
                        }
                        if (tracking.data.lastNotificationId < thisId) {
                            tracking.data.lastNotificationId = thisId;
                        }
                    }
                    tracking.data.queryingNotifications = false;
                }
            },
            error: function (xhr, status, error) {
                tracking.data.queryingNotifications = false;
                utility.handleWebServiceError('Could not retrieve message notifications.');
                console.log(error);
                console.log(status);
                console.log(xhr);
            }
        });
    }

    function resizeApp(isMapUpdated) {
        var height = $(window).height();
        //var topBarHeight = $j('#topbar').height();
        //var topOffset = 60 + topBarHeight;
        if ($('#nav-header').is(':visible')) {
            height -= 82;
        }
        if ($('#event-panel-container').is(':visible')) {
            height -= $('#event-panel-container').height() + 4; // 4 = padding not accounted for in .height() for some reason
        }

        var resizeHeight = height;

        // only resize the rest of the app if the height has actually changed
        if (resizeHeight === tracking.data.currentHeight) {
            if (isMapUpdated === true) {
                tracking.map.invalidateSize();
            }
            return;
        }

        //if ((tracking.user.isAnonymous) || (tracking.options.disableHeader)) {
        //    // if we are hiding the header, then re-adjust the height accordingly
        //    if ($j('#head').is(':hidden')) {
        //        resizeHeight += 60;
        //    }
        //}

        //#panel, #spsizer, #map

        // BUG: if collapsed in mobile, then we have nav-utility and nav-header above the map
        $('div.set-height').height(resizeHeight);
        $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('option', 'maxHeight', resizeHeight - 80);
        $(tracking.data.domNodes.infoDialogs.positionHistory).dialog('option', 'maxHeight', resizeHeight - 80);
        $(tracking.data.domNodes.infoDialogs.sharedViewInformation).dialog('option', 'maxHeight', resizeHeight - 80);

        tracking.data.currentHeight = resizeHeight;

        if (isMapUpdated) {
            tracking.map.invalidateSize();
        }
    }

    function previewGeofenceBuffer(buffer) {
        var latlngs = [];
        if (tracking.data.routeLine != null) {
            var routeLocs = tracking.data.routeLine.getLatLngs();
            if (routeLocs.length > 0) {
                if (_.isArray(routeLocs[0])) {
                    routeLocs = routeLocs[0];
                }
                _.each(routeLocs, function (elem) {
                    latlngs.push({ Lat: elem.lat, Lng: elem.lng });
                });
            }
        }
        var data = {
            encodedPolyline: tracking.data.routeLineEncoded,
            encodedPolylines: tracking.data.routeLinesEncoded,
            latlngs: latlngs,
            buffer: buffer
        };
        toggleLoadingMessage(false, 'polyline-geofence-preview');
        $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/BufferPolyline'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    if (result.Success === true) {
                        var points = result.Points;
                        var latLngs = [];
                        _.each(points, function (point) {
                            latLngs.push(L.latLng(point.Lat, point.Lng));
                        });
                        if (latLngs.length > 1) {
                            if (tracking.data.routePolygon === null) {
                                tracking.data.routePolygon = L.polygon(latLngs, {
                                    weight: 2,
                                    color: '#ff0000',
                                    opacity: 0.35,
                                    fillOpacity: 0.15,
                                    dashArray: '4'
                                });
                            } else {
                                tracking.data.routePolygon.setLatLngs(latLngs);
                            }
                            if (tracking.state.isPreviewingBuffer) {
                                addItemToMap(tracking.data.routePolygon);
                            }
                        }
                    }
                }
                toggleLoadingMessage(false, 'polyline-geofence-preview');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_ADD_GEOFENCE_ERROR);
                toggleLoadingMessage(false, 'polyline-geofence-preview');
            }
        });
    }

    function populateGatewayAccounts($container, $input, types, accountId) {
        $input.empty();
        var defaultAccountId = null;
        var includesDirectIp = false;
        _.each(types, function (typeId) {
            if (typeId === 9) {
                includesDirectIp = true;
            }
        });
        if (includesDirectIp) {
            $input.append($('<option>').val(-1).text(tracking.strings.DEFAULT_COMMERCIAL_ACCOUNT));
        } else {
            // none can be a valid choice for some gateways (such as sms) if it is not required
            $input.append($('<option>').val('').text(tracking.strings.GROUP_NONE));
        }
        for (var i = 0; i < types.length; i++) {
            // add all user-defined gateway accounts with this type
            var gatewayType = types[i];
            var addedGateways = false;
            for (var j = 0; j < tracking.data.gatewayAccounts.length; j++) {
                var gateAccount = tracking.data.gatewayAccounts[j];
                if (gateAccount.type != gatewayType) {
                    continue;
                }
                $input.append($('<option>').val(gateAccount.id).text(gateAccount.name));
                addedGateways = true;
                if (gateAccount.default) {
                    defaultAccountId = gateAccount.id;
                }
            }
            if ((gatewayType == 9) && (!addedGateways)) {
                // if additional iridium gateway accounts are not specified
                // then simply hide the default gateway to avoid confusion
                $container.hide();
            }
        }
        
        // select currently chosen account
        if (accountId !== null) {
            // SBD's default is valid, others are not
            if (accountId === '' && includesDirectIp) {
                $input.val(-1);
            } else {
                $input.val(accountId);
            }
        } else if (defaultAccountId !== null) {
            // choose the default gateway account, if specified
            $input.val(defaultAccountId);
        }
    }

    function loadSoftwarePackages(callback) {
        var data = {};
        toggleLoadingMessage(true, 'get-software-packages');
        var packageQuery = $.ajax({
            type: 'POST',
            url: wrapUrl('/Services/GPSService.asmx/GetSoftwarePackages'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                toggleLoadingMessage(false, 'get-software-packages');
                var result = msg.d;
                if (result) {
                    if (result.Success == true) {
                        tracking.data.softwarePackages = result.Packages;
                        if (callback !== undefined && callback !== null) {
                            callback();
                        }
                    } else {
                        utility.handleWebServiceError();
                    }
                }
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError();
                toggleLoadingMessage(false, 'get-software-packages');
            }
        });
    }

    function deviceDialogChange(elem, asset) {
        var dialog = $j('#edit-asset-dialog');
        var label = $j('#txtEditAssetUniqueIdLabel');
        var secondaryLabel = $j('#txtEditAssetSecondaryUniqueIdLabel');
        var input = $j('#txtEditAssetUniqueId');
        var secondaryInput = $j('#txtEditAssetSecondaryUniqueId');
        var imeiValid = $j('#txtEditAssetIMEIVerificationCode');
        var code = $j('#PortalRegistrationCode');
        var cannedMessageGroup = $j('#edit-asset-canned-message-group');
        var driverStatusTemplate = $j('#edit-asset-driver-status-template');
        var driverDeviceType = $j('#edit-asset-driver-device-type');
        var quickMessageTemplate = $j('#edit-asset-quick-message-template');
        var garminForms = $j('#edit-asset-garmin-forms');
        var addressBookGroup = $j('#edit-asset-address-book-group');
        var gatewayAccount = $j('#edit-asset-gateway-account');
        var secondaryGatewayAccount = $j('#edit-asset-secondary-gateway-account');
        var pollingInterval = $j('#edit-asset-polling-interval');
        var idpActivate = $j('#edit-asset-idp-activation');
        var bivyStatus = document.getElementById('edit-asset-bivy-status');
        var gatewayBehavior = $j('#edit-asset-gateway-behavior');
        var spotCustom = $j('#edit-asset-spot-custom');
        var assetConfigurationContainer = document.getElementById('edit-asset-configuration');
        var assetSoftwarePackageContainer = document.getElementById('edit-asset-software-package');
        var gatewayAccountSetup = $j('#gateway-account-setup');
        var secondaryGatewayAccountSetup = $j('#secondary-gateway-account-setup');
        var clearAssetHistory = $j('#edit-asset-clear-history');
        var encryptionKeys = $j('#AssetEncryptionKeys');
        var password = $j('#AssetPassword');
        var secondaryUniqueId = $j('#edit-asset-secondary-id');
        var secondaryGatewayAccountId = $('#ddlEditAssetSecondaryGatewayAccount');
        var inmarsatC = $j('#edit-asset-inmarsatc');
        var inmarsatCDetails = $j('#inmarsatc-gateway-details');
        var inmatsatCActivation = $j('#activate-inmarsatc');
        var microUseCorrectedCourse = $j('#edit-asset-micro-course');
        var suppressEmergency = $j('#edit-asset-suppress-emergency');
        //var synchronizeGeofences = $j('#edit-asset-synchronize-geofences');

        encryptionKeys.hide();
        password.hide();
        cannedMessageGroup.hide();
        driverStatusTemplate.hide();
        driverDeviceType.hide();
        quickMessageTemplate.hide();
        garminForms.hide();
        addressBookGroup.hide();
        gatewayAccount.hide();
        secondaryGatewayAccount.hide();
		pollingInterval.hide();
        spotCustom.hide();
        gatewayAccountSetup.hide();
        secondaryGatewayAccountSetup.hide();
        idpActivate.hide();
        bivyStatus.classList.remove('is-visible');
        gatewayBehavior.hide();
        microUseCorrectedCourse.hide();
        suppressEmergency.hide();
        clearAssetHistory.hide();
        assetConfigurationContainer.classList.remove('is-visible');
        assetSoftwarePackageContainer.classList.remove('is-visible');
        secondaryUniqueId.hide();
        inmarsatC.hide();
        inmarsatCDetails.hide();
        inmatsatCActivation.hide();
        //synchronizeGeofences.hide();
        //synchronizeGeofences.find('button').prop('disabled', true);
        clearAssetHistory.find('button').prop('disabled', true).hide();
        input.inputmask('remove');
        secondaryInput.inputmask('remove');
        input.rules('remove', 'regex');
        secondaryInput.rules('remove', 'regex');
        secondaryInput.removeClass('required');
        secondaryGatewayAccountId.removeClass('required');
        var device = findDeviceById($j(elem).val());
        if (device != null) {
            if (device.UniqueIdMask != null) {
                input.inputmask({'mask': device.UniqueIdMask });
            }
            if (device.UniqueIdRegex != null) {
                input.rules('add', { regex: device.UniqueIdRegex });
            }
            if (device.SecondaryUniqueIdMask != null) {
            	secondaryInput.inputmask({ 'mask': device.SecondaryUniqueIdMask });
            }
            if (device.SecondaryUniqueIdRegex != null) {
            	secondaryInput.rules('add', { regex: device.SecondaryUniqueIdRegex });
            }
            if (device.IsSecondaryIdRequired) {
            	secondaryInput.addClass('required');
                secondaryGatewayAccountId.addClass('required');
            }
            if (device.SupportsCannedMessages) {
                cannedMessageGroup.show();
            }
            if (device.SupportsAddressBooks) {
                addressBookGroup.show();
            }
            if (device.HasSecondaryId) {
                secondaryUniqueId.show();
            }

            if (device.GatewayAccountTypes !== null) {
                if (tracking.user.isAdmin) {
                    gatewayAccountSetup.show();
                }
                gatewayAccount.show();
                var accountId = (asset !== null && asset.GatewayAccountId !== null ? asset.GatewayAccountId : null);
                populateGatewayAccounts(gatewayAccount, $('#ddlEditAssetGatewayAccount'), device.GatewayAccountTypes, accountId);
            }

            if (device.SecondaryGatewayAccountTypes !== null) {
                if (tracking.user.isAdmin) {
                    secondaryGatewayAccountSetup.show();
                }
                secondaryGatewayAccount.show();
                var accountId = (asset !== null && asset.SecondaryGatewayAccountId !== null ? asset.SecondaryGatewayAccountId : null);
                populateGatewayAccounts(secondaryGatewayAccount, secondaryGatewayAccountId, device.SecondaryGatewayAccountTypes, accountId);
            }

            if (device.RequiresPollingInterval) {
                pollingInterval.show();
            }
            $j('#edit-asset-current-status .config span').hide();
            if (device.SupportedConfigurations != null) {
                assetConfigurationContainer.classList.add('is-visible');
                // hide non-supported configurations
                var configOptions = assetConfigurationContainer.querySelectorAll('option');
                _.each(configOptions, function(elem) {
                    if (_.indexOf(device.SupportedConfigurations, elem.value) !== -1) {
                        elem.classList.add('is-visible');
                    } else {
                        elem.classList.remove('is-visible');
                    }
                });
            }

            if ((asset != null) && (asset.Configuration != null) && (asset.Configuration != '')) {
                $j('#edit-asset-current-status .config-' + asset.Configuration + ' span').show();
            }

            driverStatusTemplate.show().find('button').prop('disabled', (asset == null)).prop('disabled', (asset == null));
            
            if (device.SupportsQuickMessages) {
                quickMessageTemplate.show().find('button').prop('disabled', (asset == null)).prop('disabled', (asset == null));
            }
            if (device.SupportsGarminForms) {
                garminForms.show();
            }

            if ($j.inArray(device.Id, tracking.devices.INMARSAT_C) !== -1) {
                inmarsatC.show();
                if (asset == null) {
                    inmatsatCActivation.show();
                } else if ((asset.InmarsatC != null) && (asset.InmarsatC.DNID == null || asset.InmarsatC.MemberNumber == null)) {
                    inmatsatCActivation.show();
                } else {
                    inmarsatCDetails.show();
                }
            }

            if ($j.inArray(device.Id, tracking.devices.SPOT) != -1) {
                spotCustom.show();
            }

            if (($j.inArray(device.Id, tracking.devices.NAL) != -1)
				|| ($j.inArray(device.Id, tracking.devices.GSAT_MICROS) != -1)
				|| ($j.inArray(device.Id, tracking.devices.QUECLINK) != -1)
				|| (device.Id == tracking.devices.IRIDIUM_EDGE)
                || ($j.inArray(device.Id, tracking.devices.HUGHES) != -1)
			) {
                password.show();
            }

            if (($j.inArray(device.Id, tracking.devices.NAL) != -1) || ($j.inArray(device.Id, tracking.devices.GSAT_MICROS) != -1)) {
                encryptionKeys.show();
                $j('#AssetEncryptionReceive,#AssetEncryptionTransmit').show();
            }

            if (($j.inArray(device.Id, tracking.devices.GSAT_MICROS) != -1)) {
                microUseCorrectedCourse.show();
                suppressEmergency.show();
                $j('#AssetEncryptionReceive').hide();
                $j('#txtEditAssetTransmitKey').rules('add', { regex: '^([0-9a-fA-F]){64}|(placeholder)$', messages: { regex: 'Encryption key must be 64 character hex' } });
            } else {
                $j('#txtEditAssetTransmitKey').rules('remove', 'regex');
                //$j('#txtEditAssetTransmitKey').rules('remove', 'maxlength');
            }


            if ($j.inArray(device.Id, tracking.devices.SKYWAVE_IDP_CELL_ONLY) == -1) {
                if (($j.inArray(device.Id, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1)
                    || ($j.inArray(device.Id, tracking.devices.SKYWAVE_IDP) != -1)
                    || ($j.inArray(device.Id, tracking.devices.QUAKE_AIC) != -1)
                    || ($j.inArray(device.Id, tracking.devices.NAL_GSM) != -1)
                    || (_.indexOf(tracking.devices.GSATMICRO_GSM, device.Id) !== -1)
                ) {
                    var minutes = 20;
                    gatewayBehavior.show();
                    $j('#EditAssetIDPGatewayBehaviorTimeout').val(minutes);
                    if (($j.inArray(device.Id, tracking.devices.SKYWAVE_IDP_DUAL_MODE) == -1)
                        && ($j.inArray(device.Id, tracking.devices.NAL_GSM) == -1)
                        && (_.indexOf(tracking.devices.GSATMICRO_GSM, device.Id) === -1)
                        && ($j.inArray(device.Id, tracking.devices.QUAKE_AIC) == -1)) {
                        // disable dual-mode options
                        $j('input[name=EditAssetIDPGatewayBehavior][value!=1]', gatewayBehavior).prop('disabled', true).prop('checked', false).next('label').children('input:text').prop('disabled', true);
                        $j('input[name=EditAssetIDPGatewayBehavior][value=1]', gatewayBehavior).prop('checked', true);
                    } else {
                        $j('input[name=EditAssetIDPGatewayBehavior]', gatewayBehavior).prop('disabled', false).prop('checked', false).next('label').children('input:text').prop('disabled', false);

                        if (asset != null) {
                            $j('input[name=EditAssetIDPGatewayBehavior][value="' + asset.DefaultIDPGateway + '"]', gatewayBehavior).prop('checked', true);
                            if (asset.DefaultIDPGatewayTimeout != null) {
                                $j('#EditAssetIDPGatewayBehaviorTimeout').val(asset.DefaultIDPGatewayTimeout);
                            }
                            if (asset.DefaultIDPGatewayRetries != null) {
                                $j('#EditAssetIDPGatewayBehaviorRetries,#EditAssetIDPGatewayBehaviorCellRetries').val(asset.DefaultIDPGatewayRetries);
                            }
                        } else {
                            $j('input[name=EditAssetIDPGatewayBehavior][value=3]', gatewayBehavior).prop('checked', true);
                        }
                    }
                }
            }

            if (($j.inArray(device.Id, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1)
                || ($j.inArray(device.Id, tracking.devices.SKYWAVE_IDP) != -1)) {
                // avl devices have access to driver device type (garmin/ibutton)
                driverDeviceType.show();
                // ibutton only available to avl 3.0
                if ((asset != null) && (asset.Configuration != 'AVL3') && (asset.Configuration != 'ATS') && (asset.Configuration != 'GSEAgentsV1') && (asset.Configuration != 'GSEAgentsV2')) {
                    $j('.ibutton', driverDeviceType).hide();
                } else {
                    $j('.ibutton', driverDeviceType).show();
                }

                if ($j.inArray(device.Id, tracking.devices.SKYWAVE_IDP_CELL_ONLY) == -1) {
                    if (tracking.user.isAdmin) {
                        if (asset != null)
                            idpActivate.show();
                    }
                }
            }

            if (asset !== null && device.Id === tracking.devices.BIVY_STICK) {
                document.getElementById('EditAssetBivyActive').checked = asset.Bivy !== null && asset.Bivy.Active === true;
                document.getElementById('EditAssetBivyRegistered').checked = asset.Bivy !== null && asset.Bivy.Registered === true;
                document.getElementById('EditAssetBivyMessaging').checked = asset.Bivy !== null && asset.Bivy.Messages === true;
                document.getElementById('EditAssetBivyPositions').checked = asset.Bivy !== null && asset.Bivy.Location === true;
                bivyStatus.classList.add('is-visible');
            }

            // load packages for device
            var softwarePackageList = document.getElementById('ddlEditAssetSoftwarePackage');
            // clear prior options
            while (softwarePackageList.firstChild) {
                softwarePackageList.removeChild(softwarePackageList.firstChild);
            }

            if (device.SoftwarePackageType !== null) {
                var hasDefault = false;
                var callback = function () {
                    var options = [];
                    var defaultOption = el('option', { value: '' }, tracking.strings.GROUP_NONE);

                    _.each(tracking.data.softwarePackages, function (package) {
                        var isForDevice = _.find(package.Devices, function (deviceAssignment) {
                            return deviceAssignment.Id === device.Id;
                        });
                        if (isForDevice === undefined) {
                            return;
                        }
                        // add each devicePackage as an <option>, selecting if its IsDefault
                        var option = el('option', { value: package.Id }, package.Tag + ' - ' + package.Version);
                        if (asset == null) {
                            if (isForDevice.IsDefault === true) {
                                hasDefault = true;
                                option.selected = true;
                                option.setAttribute('selected', 'selected');
                            }
                        } else {
                            if (asset.SoftwarePackageId !== null && package.Id === asset.SoftwarePackageId) {
                                hasDefault = true;
                                option.selected = true;
                                option.setAttribute('selected', 'selected');
                            }
                        }
                        options.push(option);
                    });
                    if (!hasDefault) {
                        defaultOption.selected = true;
                        defaultOption.setAttribute('selected', 'selected');
                    }
                    options.unshift(defaultOption);

                    assetSoftwarePackageContainer.classList.add('is-visible');
                    setChildren(softwarePackageList, options);
                };
                if (tracking.data.softwarePackages === null) {
                    loadSoftwarePackages(callback);
                } else {
                    callback();
                }
            }
        }

        if (asset != null) {
            if (tracking.user.isAdmin || tracking.user.canEditAssets) {
                // only show the clearAssetHistory for existing assets
                clearAssetHistory.show();
                clearAssetHistory.find('button').prop('disabled', false).show();
            }
        }

        $j('#edit-asset-mobile-instructions').hide();
        if ((device != null) && ($j.inArray(device.Id, tracking.devices.MOBILE) != -1)) {
        	$j('#edit-asset-mobile').show();
        	$j('#edit-asset-uniqueid').hide();
        	// require mobile username and password
        	$j('#txtEditAssetMobileUnme,#txtEditAssetMobilePwrd').addClass('required');
        	if(asset == null) {
				$j('#edit-asset-mobile-instructions').show();
        	}
        } else {
        	$j('#edit-asset-mobile').hide();
        	$j('#edit-asset-uniqueid').show();
        }

        if (((asset != null) && (device != null))
            && (($j.inArray(device.Id, tracking.devices.MOBILE) !== -1)
                || ($j.inArray(device.Id, tracking.devices.INMARSAT_C) !== -1))
        ) {
        	$j('#edit-asset-uniqueid').show();
        	$j('#txtEditAssetUniqueId').prop('disabled', true);
        	// don't require mobile username and password
        	$j('#txtEditAssetMobileUnme,#txtEditAssetMobilePwrd').removeClass('required');
        }

        addressBookGroup.find('button').prop('disabled', true).hide();
        cannedMessageGroup.find('button').prop('disabled', true).hide();

        if ((tracking.options.enableShoutConfigOta)
            && (asset != null)
            && (device != null)
            && ($j.inArray(device.Id, tracking.devices.NAL) != -1)) {
            addressBookGroup.find('button').prop('disabled', false).show();
            cannedMessageGroup.find('button').prop('disabled', false).show();
        }

        if ((asset == null)
            && (device != null)
            && ($j.inArray(device.Id, tracking.devices.NAL) != -1)
            && tracking.options.enableShoutConfigOta) {
            // show optional sync checkbox for when an asset is being added
            // when edited, it only applies if the group is being changed
            $j('#edit-asset-sync-ota').show();
        } else {
            $j('#edit-asset-sync-ota').hide();
        }

        // hardcoded differences
        switch (parseInt($j(elem).val())) {
            case tracking.devices.IRIDIUM_EXTREME:
                label.text('IMEI');
                input.addClass('imei 9575');
                imeiValid.addClass('imei-code');
                code.show();
                setTimeout(function () {
                    // this is a hack to get around timing issues with the .inputmask plugin
                    validateIMEI(input);
                }, 500);
                // supports canned message group/address book entry push
                addressBookGroup.find('button').prop('disabled', false).show();
                cannedMessageGroup.find('button').prop('disabled', false).show();
                break;
            default:
                label.text(tracking.strings.ASSET_UNIQUEID);
                input.removeClass('imei 9575');
                imeiValid.removeClass('imei-code');
                code.hide();
                break;
        }
        if (device != null) {
        	label.text($j('#ddlEditAssetDeviceTypes option[value=' + device.UniqueIdType + ']').text());
        	secondaryLabel.text($j('#ddlEditAssetDeviceTypes option[value=' + device.SecondaryUniqueIdType + ']').text());
        } else {
        	label.text($j('#ddlEditAssetDeviceTypes option[value=0]').text());
        	secondaryLabel.text($j('#ddlEditAssetDeviceTypes option[value=0]').text());
        }

        populateEditAssetLabelsTab(asset, device);
    }

    function dashToCamelCase(input) {
        return input.replace(/-([a-z])/gi, function ($0, $1) { return $1.toUpperCase(); });
    }

    function createCustomOverlay(bounds, url, opacity) {
    	var tileBounds = null;
    	if (bounds != null) {
    		tileBounds = L.latLngBounds(L.latLng(bounds.topright.lat, bounds.topright.lng), L.latLng(bounds.bottomleft.lat, bounds.bottomleft.lng));
    	}
		return L.tileLayer(url, { bounds: tileBounds, opacity: opacity });
    }

    function isCoordInBounds(coord, bounds, zoom) {
    	var num = 1 << zoom;
    	if (coord.y < 0 || coord.y >= num) {
    		return false;
    	}
    	if (bounds == null) {
    		return true;
    	}
    	if (bounds.topright == null) {
    		return true;
    	}
    	if (bounds.bottomleft == null) {
    		return true;
    	}
    	var topRightTile = LatLngToTileCoordinate(bounds.topright, zoom);
    	var bottomLeftTile = LatLngToTileCoordinate(bounds.bottomleft, zoom);
    	var xs = [];
    	var ys = [];
    	for (var i = bottomLeftTile.x; i <= topRightTile.x; i++) {
    		xs.push(i);
    	}
    	for (var i = topRightTile.y; i <= bottomLeftTile.y; i++) {
    		ys.push(i);
    	}
    	var inX = false;
    	for (var i = 0; i < xs.length; i++) {
    		if (xs[i] == coord.x) {
    			inX = true;
    			break;
    		}
    	}
    	if (!inX) {
    		return false;
    	}

    	var inY = false;
    	for (var i = 0; i < ys.length; i++) {
    		if (ys[i] == coord.y) {
    			inY = true;
    			break;
    		}
    	}
    	if (!inY) {
    		return false;
    	}
    	return true;
    }

    function setLabelsByTemplate(device, asset, template) {
        if (template == null)
            template = asset;
        if (device.OutputPinCount > 0) {
            if (template != null) {
                for (var i = 0; i < device.OutputPinCount; i++) {
                    var num = i + 1;
                    var label = getOutputLabelByIndex(template, num);
                    if (label == null) // no labels defined for this pin
                        continue;
                    var pinVal = (label.Label == null) ? '' : label.Label;
                    var onVal = (label.OnLabel == null) ? '' : label.OnLabel;
                    var offVal = (label.OffLabel == null) ? '' : label.OffLabel;
                    var pulseVal = (label.PulseLabel == null) ? '' : label.PulseLabel;
                    var onEnabled = !label.OnIsDisabled;
                    var offEnabled = !label.OffIsDisabled;
                    var pulseEnabled = !label.PulseIsDisabled;
                    $j('#EditAssetOutputLabel' + num).val(pinVal);
                    $j('#EditAssetOnLabel' + num).val(onVal);
                    $j('#EditAssetOnEnabled' + num).prop('checked', onEnabled);
                    $j('#EditAssetOnLabel' + num).prop('disabled', !onEnabled);
                    $j('#EditAssetOffLabel' + num).val(offVal);
                    $j('#EditAssetOffEnabled' + num).prop('checked', offEnabled);
                    $j('#EditAssetOffLabel' + num).prop('disabled', !offEnabled);
                    $j('#EditAssetPulseLabel' + num).val(pulseVal);
                    $j('#EditAssetPulseEnabled' + num).prop('checked', pulseEnabled);
                    $j('#EditAssetPulseLabel' + num).prop('disabled', !pulseEnabled);
                }
            }
        }
        if (template != null) {
            var num = 0;
            if (device.InputConfigurationPinsDigital != null) {
                for (var i = 0; i < device.InputConfigurationPinsDigital.length; i++) {
                    num++;
                    var label = getInputLabelByIndex(template, num);
                    if (label == null) // no labels defined for this pin
                        continue;

                    var override = getInputLabelByIndex(asset, num);

                    var pinVal = (label.Label == null) ? '' : label.Label;
                    var onVal = (label.OnLabel == null) ? '' : label.OnLabel;
                    var offVal = (label.OffLabel == null) ? '' : label.OffLabel;
                    var onEnabled = !label.OnIsDisabled;
                    var offEnabled = !label.OffIsDisabled;

                    $j('#EditAssetInputLabel' + num).val(pinVal);
                    $j('#EditAssetInputLabelPin' + num).val(device.InputConfigurationPinsDigital[i]);
                    $j('#EditAssetInputLabelNormallyOpen' + num).prop('checked', label.IsNormallyOpen);
                    $j('#EditAssetInputLabelEmergency' + num).prop('checked', label.IsEmergency);

                    $j('#EditAssetInputOnLabel' + num).val(onVal);
                    $j('#EditAssetInputOnEnabled' + num).prop('checked', onEnabled);
                    $j('#EditAssetInputOnLabel' + num).prop('disabled', !onEnabled);
                    $j('#EditAssetInputOffLabel' + num).val(offVal);
                    $j('#EditAssetInputOffEnabled' + num).prop('checked', offEnabled);
                    $j('#EditAssetInputOffLabel' + num).prop('disabled', !offEnabled);
                }
            }
            if (device.InputConfigurationPinsAnalog != null) {
                for (var i = 0; i < device.InputConfigurationPinsAnalog.length; i++) {
                    num++;
                    var label = getInputLabelByIndex(template, num);
                    if (label == null) {
                        // no labels defined for this pin
                        $j('#EditAssetInputAnalogBehavior' + num).val("0");
                        continue;
                    }
                    // asset overriding the template? not understanding this
                    var override = getInputLabelByIndex(asset, num);
                    var pinVal = (label.Label == null) ? '' : label.Label;
                    var maxVoltage = (override.MaxVoltage == null) ? ((label.MaxVoltage == null) ? '' : label.MaxVoltage) : override.MaxVoltage;
                    var fuelCapacity = (override.FuelCapacity == null) ? ((label.FuelCapacity == null) ? '' : label.FuelCapacity) : override.FuelCapacity;
                    var weightMax = (override.WeightMax == null) ? ((label.WeightMax == null) ? '' : label.WeightMax) : override.WeightMax;

                    $j('#EditAssetInputLabel' + num).val(pinVal);
                    $j('#EditAssetInputLabelPin' + num).val(device.InputConfigurationPinsAnalog[i]);
                    if (label.AnalogBehavior === null) {
                        $j('#EditAssetInputAnalogBehavior' + num).val("0");
                    } else {
                        $j('#EditAssetInputAnalogBehavior' + num).val(label.AnalogBehavior);
                    }
                    $j('#EditAssetInputLabelFuel' + num).prop('checked', label.IsFuel);
                    $j('#EditAssetInputAnalogUnit' + num).val(label.AnalogUnit);
                    $j('#EditAssetInputAnalogFactor' + num).val(label.AnalogFactor);
                    // can be overridden by asset
                    $j('#EditAssetInputMaxVoltage' + num).val(maxVoltage);
                    $j('#EditAssetInputFuelCapacity' + num).val(fuelCapacity);
                    $j('#EditAssetInputWeightMax' + num).val(weightMax);
                }
            }
        }
    }

    function populateEditAssetLabelsTab(asset, device) {
        $('#edit-asset-accordion .primary-card button').removeClass('disabled');
        if (!tracking.user.isAdmin) {
            $('#accordion-edit-asset-users-head button').addClass('disabled');
        }
        if (device == null) {
            $('#accordion-edit-asset-labels-head button').addClass('disabled');
            $('#accordion-edit-asset-garmin-head button').addClass('disabled');
            $('#accordion-edit-asset-assets-head button').addClass('disabled');
        } else {
            if (device.OutputPinCount === 0 && device.InputPinCount === 0) {
                $('#edit-asset-tab-labels').addClass('disabled');
            }
            if (!device.SupportsQuickMessages && !device.SupportsGarminForms) {
                $('#accordion-edit-asset-garmin-head button').addClass('disabled');
            }
            if (_.indexOf(tracking.devices.MOBILE, device.Id) === -1) {
                $('#accordion-edit-asset-assets-head').parent().removeClass('is-visible');
            } else {
                $('#accordion-edit-asset-assets-head').parent().addClass('is-visible');
            }
        }
        if (asset == null) {
            $('#accordion-edit-asset-alerts-head button').addClass('disabled');
        }

        $j('#edit-asset-labels-input, #labels-input-digital,#labels-input-analog').hide();
        $j('#edit-asset-labels-output').hide();
        if (device == null) {
            return;
        }
        var dialog = '#edit-asset-dialog';

        var inputGroupEl = function (type, inputId, checkboxId, isToggle ) {
            var checkbox = el('input.' + type + '.custom-control-input', { id: checkboxId, name: checkboxId, type: 'checkbox', checked: true });
            checkbox.setAttribute('checked', 'checked');
            return el('div.input-group.input-group-checkbox', [
                el('div.input-group-prepend' + (isToggle ? '.parameter-toggle' : ''),
                    el('div.input-group-text',
                        el('div.custom-control.custom-checkbox.empty-label', [
                            checkbox,
                            text(' '),
                            el('label.custom-control-label', { for: checkboxId }, ' ')
                        ])
                    )
                ),
                el('input.form-control', { id: inputId, name: inputId, type: 'text', maxlength: 250 })
            ]);
        }

        var arcConfiguration = $j('#ddlConfigurationARC');
        if (device.OutputPinCount > 0 || device.InputPinCount > 0) {
            if (device.OutputPinCount > 0) {
                $j('#edit-asset-labels-output').show();

                // pop the fields
                var rows = [];
                for (var i = 0; i < device.OutputPinCount; i++) {
                    var num = i + 1;
                    var pinName = 'EditAssetOutputLabel' + num;
                    var onName = 'EditAssetOnLabel' + num;
                    var onEnabledName = 'EditAssetOnEnabled' + num;
                    var offName = 'EditAssetOffLabel' + num;
                    var offEnabledName = 'EditAssetOffEnabled' + num;
                    var pulseName = 'EditAssetPulseLabel' + num;
                    var pulseEnabledName = 'EditAssetPulseEnabled' + num;
                    var cols = [
                        el('td.align-middle', el('label', { for: pinName }, num)),
                        el('td', el('input#' + pinName + '.form-control', { type: 'text', maxlength: 250})),
                        el('td.idp.arc', asset != null ? document.querySelector('#ddlConfigurationARC select').cloneNode(true) : null),
                        el('td', inputGroupEl('digital', onName, onEnabledName, false)),
                        el('td', inputGroupEl('digital', offName, offEnabledName, false)),
                        el('td', inputGroupEl('digital', pulseName, pulseEnabledName, false))
                    ];
                    rows.push(el('tr', cols));
                }
                setChildren(document.querySelector('#OutputLabels tbody'), rows);

                $j('#OutputLabels .idp').hide();
                if (asset != null) {
                    if (($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1) || ($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) != -1)) {
                        $j('#OutputLabels .idp').show();
                    }
                    if ($j.inArray(asset.Configuration, tracking.data.avlConfigurations) == -1) {
                        $j('#OutputLabels .avl').hide();
                    }
                    if ($j.inArray(asset.Configuration, tracking.data.arcConfigurations) == -1) {
                        $j('#OutputLabels .arc').hide();
                    }
                }
            }

            if (device.InputPinCount > 0) {
                $j('#edit-asset-labels-input').show();
                $j('#InputLabelsDigital tbody, #InputLabelsAnalog tbody').empty();
                $j('#InputLabelsAnalog span.capacity').text('Fuel Capacity (' + fuelText() + ')');
                var num = 0;

                if (device.InputConfigurationPinsDigital != null) {
                    $j('#labels-input-digital').show();
                    var html = '';
                    var rows = [];
                    for (var i = 0; i < device.InputConfigurationPinsDigital.length; i++) {
                        var ioConfig = null;
                        var avlConfig = null;
                        var arcConfig = null;
                        if (asset != null) {
                            if (($.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) != -1)
                                || (asset.DeviceId == tracking.devices.SKYWAVE_782)
                                || (asset.DeviceId == tracking.devices.SKYWAVE_782_CELL)
                                || (asset.DeviceId == tracking.devices.SKYWAVE_ST9100)) {
                                ioConfig = document.querySelector('#ddlConfigurationEIO select').cloneNode(true);
                            } else if ($.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1) {
                                ioConfig = document.querySelector('#ddlConfigurationEEIO select').cloneNode(true);
                            }
                            avlConfig = document.querySelector('#ddlConfigurationAVL select').cloneNode(true);
                            arcConfig = document.querySelector('#ddlConfigurationARC select').cloneNode(true);
                        }

                        num++;
                        var pinName = 'EditAssetInputLabel' + num;
                        var devicePinName = 'EditAssetInputLabelPin' + num;
                        var deviceNOName = 'EditAssetInputLabelNormallyOpen' + num;
                        var deviceSOSName = 'EditAssetInputLabelEmergency' + num;
                        var onName = 'EditAssetInputOnLabel' + num;
                        var onEnabledName = 'EditAssetInputOnEnabled' + num;
                        var offName = 'EditAssetInputOffLabel' + num;
                        var offEnabledName = 'EditAssetInputOffEnabled' + num;
                        
                        var cols = [
                            el('td.align-middle', el('label', { for: pinName }, (i+1))),
                            el('td', [
                                el('input.form-control', { type: 'text', maxlength: 250, id: pinName, name: pinName }),
                                el('input', { id: devicePinName, name: devicePinName, value: device.InputConfigurationPinsDigital[i], type: 'hidden' })
                            ]),
                            el('td.idp', i < 4 ? ioConfig : null),
                            el('td.idp.avl', avlConfig),
                            el('td.idp.arc', arcConfig),
                            el('td', inputGroupEl('digital', onName, onEnabledName, true)),
                            el('td', inputGroupEl('digital', offName, offEnabledName)),
                            el('td', el('div.custom-control.custom-checkbox.empty-label', [
                                el('input.digital.custom-control-input', { type: 'checkbox', id: deviceNOName, value: 'true' }),
                                el('label.custom-control-label', { for: deviceNOName }, ' ')
                            ])),
                            el('td', el('div.custom-control.custom-checkbox.empty-label', [
                                el('input.digital.custom-control-input', { type: 'checkbox', id: deviceSOSName, value: 'true' }),
                                el('label.custom-control-label', { for: deviceSOSName }, ' ')
                            ]))
                        ];
                        rows.push(el('tr', cols));
                    }
                    setChildren(document.querySelector('#InputLabelsDigital tbody'), rows);
                }
                $j('#InputLabelsDigital .idp').hide();
                if (asset != null) {
                    if (($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1) || ($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) != -1)) {
                        $j('#InputLabelsDigital .idp').show();
                    }
                    if ($j.inArray(asset.Configuration, tracking.data.avlConfigurations) == -1) {
                        $j('#InputLabelsDigital .avl').hide();
                    }
                    if ($j.inArray(asset.Configuration, tracking.data.arcConfigurations) == -1) {
                        $j('#InputLabelsDigital .arc').hide();
                    }
                }

                if (device.InputConfigurationPinsAnalog != null) {
                    if ((($j.inArray(device.Id, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1) || ($j.inArray(device.Id, tracking.devices.SKYWAVE_IDP) != -1)) && (asset != null) && ($j.inArray(asset.Configuration, tracking.data.avlConfigurations) != -1)) {
                        $j('#labels-input-analog .title').text(tracking.strings.SENSORS);
                    } else {
                        $j('#labels-input-analog .title').text(tracking.strings.INPUT + ' - ' + tracking.strings.ANALOG_PIN);
                    }
                    $j('#labels-input-analog').show();
                    var html = '';
                    var rows = [];
                    for (var i = 0; i < device.InputConfigurationPinsAnalog.length; i++) {
                        num++;
                        var pinName = 'EditAssetInputLabel' + num;
                        var devicePinName = 'EditAssetInputLabelPin' + num;
                        var deviceFuelName = 'EditAssetInputLabelFuel' + num;
                        var analogBehaviorName = 'EditAssetInputAnalogBehavior' + num;
                        var voltName = 'EditAssetInputMaxVoltage' + num;
                        var fuelCapName = 'EditAssetInputFuelCapacity' + num;
                        var maxWeightName = 'EditAssetInputWeightMax' + num;
                        var analogUnitName  = 'EditAssetInputAnalogUnit' + num;
                        var analogFactorName  = 'EditAssetInputAnalogFactor' + num;

                        // TODO: i18n
                        var behaviorSelect = el('select.analog.behavior.form-control', { id: analogBehaviorName }, [
                            el('option', { value: '0' }, 'None'),
                            el('option', { value: '9' }, 'Altitude'),
                            el('option', { value: '7' }, 'Battery Voltage'),
                            el('option', { value: '8' }, 'Battery Percentage'),
                            el('option', { value: '5' }, 'Engine RPM'),
                            el('option', { value: '1' }, 'Fuel Level'),
                            el('option', { value: '2' }, 'Fuel Consumed'),
                            el('option', { value: '3' }, 'Odometer'),
                            el('option', { value: '6' }, 'Power take-off (PTO)'),
                            el('option', { value: '4' }, 'Weight')
                        ]);

                        var cols = [
                            el('td.align-middle.center', el('label', { for: pinName }, (i + 1))),
                            el('td', [
                                el('input.form-control', { type: 'text', maxlength: 250, id: pinName, name: pinName }),
                                el('input', { id: devicePinName, name: devicePinName, value: device.InputConfigurationPinsAnalog[i], type: 'hidden' })
                            ]),
                            el('td.idp.avl.center', [ el('span.sensor'), text(' ') ]), // remove text(' ')?
                            el('td.idp.arc', asset != null ? document.querySelector('#ddlConfigurationARC select').cloneNode(true) : ' '),
                            el('td', el('input.analog.form-control', { id: analogUnitName, type: 'text', maxlength: 15 })),
                            el('td', el('input.analog.form-control', { id: analogFactorName, type: 'text', maxlength: 5 })),
                            el('td', behaviorSelect),
                            el('td', el('input.analog.override.fuel.form-control', { id: voltName, type: 'text', maxlength: 13 })),
                            el('td', el('input.analog.override.fuel.form-control', { id: fuelCapName, type: 'text', maxlength: 14 })),
                            el('td', el('input.analog.override.weight.form-control', { id: maxWeightName, type: 'text', maxlength: 14 }))
                        ];
                        rows.push(el('tr', cols));

                    }
                    setChildren(document.querySelector('#InputLabelsAnalog tbody'), rows);
                }
                $j('#InputLabelsAnalog .idp').hide();
                if (asset != null) {
                    if (($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1) || ($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) != -1)) {
                        $j('#InputLabelsAnalog .idp').show();
                    }
                    if ($j.inArray(asset.Configuration, tracking.data.avlConfigurations) == -1) {
                        $j('#InputLabelsAnalog .avl').hide();
                    }
                    if ($j.inArray(asset.Configuration, tracking.data.arcConfigurations) == -1) {
                        $j('#InputLabelsAnalog .arc').hide();
                    }
                }
            }

            $j('#EditAssetIOTemplate').empty();
            $j('#EditAssetIOTemplate').append($j('<option>').text(tracking.strings.GROUP_NONE).val(''));
            var selectedTemplate = null;
            for (var i = 0; i < device.IOTemplates.length; i++) {
                var template = device.IOTemplates[i];
                if ((asset != null) && (asset.IOTemplateId == template.Id)) {
                    selectedTemplate = template;
                }
                $j('#EditAssetIOTemplate').append($j('<option>').val(template.Id).text(template.Name).prop('selected', ((asset != null && asset.IOTemplateId) == template.Id ? true : false)));
            }

            if (selectedTemplate != null) {
                setLabelsByTemplate(device, asset, selectedTemplate);
                $j('#labels-input-analog input, #labels-input-analog select, #labels-input-digital input, #labels-output input').prop('disabled', true);
            } else {
                setLabelsByTemplate(device, asset, null);
            }

            // enable behavior overrides
            $j('#labels-input-analog select.behavior').each(function (index, elem) {
                toggleAnalogIOBehavior(this);
            });
        }
    }

    function zeroFill(number, width) {
        width -= number.toString().length;
        if (width > 0) {
            return new Array( width + (/\./.test( number ) ? 2 : 1) ).join( '0' ) + number;
        }
        return number + ""; // always return a string
    }

    function hash1(imei) {
        var res = 0;
        for (var i = 0; i < imei.length; i++) {
            res ^= ((res << 5) + (res >> 2) + imei.charCodeAt(i)) & 0xFFFF;
        }
        return zeroFill(res, 5);
    }

    function hash2(imei) {
        var res = hash1(imei);
        for (var i = imei.length; i > 0; i--) {
            res ^= ((res << 5) + (res >> 2) + imei.charCodeAt(i - 1)) & 0xFFFF;
        }

        return zeroFill(res, 5);
        // hash2 must be padded with zeroes to a length of 5
        /*res = res.toString();
	    var hashlength = res.length;
	    var zeroes = 5 - hashlength;
	    for (var i = 0; i < zeroes; i++) {
	        res = '0' + res;
	    }
	    return res;*/
    }

    function validateIMEIVerificationCode(elem) {
        // imei has to be valid
        var imei = $j('#txtEditAssetUniqueId');
        if (!validateIMEI(imei))
            return false;

        var input = $j(elem);
        var val = input.val();
        if ((val == null) || (val == ''))
            return false;
        $j('#IMEIVerificationError').hide();
        if (val.length == 5) {
            var isValid = tracking.data.validation.editAsset.element('#txtEditAssetIMEIVerificationCode');
            if (isValid != true)
                return false;
            isValid = (val == hash1(imei.val()));
            if (isValid != true) {
                $j('#IMEIVerificationError').show();
                return false;
            }
            // gen hash2
            $j('#txtEditAssetPortalRegistrationCode').val(hash2(imei.val()));
            return true;
        }
    }

    function validateIMEI(elem) {
        var input = $j(elem);
        if (!input.hasClass('imei'))
            return false;
        var val = input.val();

        // set to default message
        $j('#txtEditAssetPortalRegistrationCode').val(tracking.strings.MSG_VALID_IMEI);

        if ((val == null) || (val == ''))
            return false;

        val = val.trim();
        // validate input as 15 digit only
        if (val.length === 15) {
            var isValid = tracking.data.validation.editAsset.element('#txtEditAssetUniqueId');
            if (isValid !== true)
                return false;
            // if Iridium 9575, load Portal Registration Code message via ajax
            if ($j('#ddlEditAssetDevice').val() === '16') {
                //validateIMEIVerificationCode($j('#txtEditAssetIMEIVerificationCode'));
                // load portal code 2 by default now
                $j('#txtEditAssetPortalRegistrationCode').val(hash2(val));
            }
            return true;
        }
        return false;
    }

    function hideSidePanel() {
        $j('#topbar-left').css('left', '-25em');
        $j('#topbar-right').css('margin-left', '0');
        $j('#panel').hide();
        $j('#map').css('left', 0).css('margin-right', 0);
        $j('#map_tools,#map_tools_bottom,#map_panels').css('margin-left', 0);
        $j('#panel-expand').show();
        $j('#ds-v .shade, #event-panel,#event-panel-container').css('left', '0');
        $j('#panel-contract').hide();
        resizeApp(true);
    }

    function showSidePanel() {
        $j('#topbar-left').css('left', '0');
        $j('#topbar-right').css('margin-left', '25em');
        $j('#panel').show();
        $j('#map').css('left', '25em').css('margin-right', '25em');
        $j('#map_tools,#map_tools_bottom,#map_panels').css('margin-left', '25em');
        $j('#panel-contract').show();
        $j('#ds-v .shade, #event-panel,#event-panel-container').css('left', '25em');
        $j('#panel-expand').hide();
        resizeApp(true);
    }

    function addSystemNotification(notification) {
        tracking.data.systemNotifications.push(notification);
        showSystemNotification(notification);
    }

    function showSystemNotification(notification) {
        if (Cookies.get('notif') !== undefined && Cookies.get('notif') !== null) {
            var confirmedNotifications = Cookies.get('notif').split(',');
            console.log(confirmedNotifications);
            if (confirmedNotifications.indexOf(notification.id) !== -1)
                return;
        }
        var subject = tracking.strings.SYSTEM_NOTIFICATION;
        if ((notification.subject != null) && (notification.subject != '')) {
            subject = notification.subject;
        }
        $j.jGrowl(el('div.message', notification.text), {
            header: subject,
            sticky: true,
            theme: 'system-notification',
            closer: false,
            mustConfirm: true,
            notificationId: 'notify-' + notification.id,
            from: notification.from,
            to: notification.to,
            text: notification.text,
            type: 'notification',
            buttons: [el('button.confirm.btn.btn-sm.btn-secondary', { dataset: { notificationId: 'notify-' + notification.id}},tracking.strings.CONFIRM)],
            afterOpen: function (e, m, o) {
                if (tracking.preferences.PREFERENCE_EMERGENCY_AUDIO) {
                    alertUserOfSystemNotification();
                }
            },
            appendTo: '#map-mode-container'
        });
    }

    function setupSignalR() {
        // SignalR notification setup
        tracking.log('SignalR Setup.');
        $j.connection.hub.logging = true;
        var qs = {};
        if (tracking.user.isImpersonated) {
            qs.ishr = tracking.user.id;
        }
        $j.connection.hub.qs = qs;
        tracking.notificationConnection = $j.connection.hub;
        tracking.notificationService = $j.connection.notify;

        tracking.notificationService.client.idpCapabilitiesUpdate = function (assetId, capabilities) {
            tracking.log('SignalR IDP Capabilities Update: ' + assetId);
            console.log(capabilities);
            var asset = findAssetById(assetId);
            if (asset != null) {
                asset.IDP = capabilities;
            }

            if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
                && parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id')) === assetId) {
                // crypto service information may have updated
                var activeDialog = document.getElementById('dialog-functions').querySelector('.dialog');
                if (activeDialog === tracking.data.domNodes.dialogs.idpSendCommand) {
                    requestIDPCryptoInformation(assetId);
                }
            }
        };

        tracking.notificationService.client.newEvent = function (event) {
            tracking.log('SignalR Event: ' + event);
            // query for the event
            tracking.throttles.queryLatestEvents(event);
        };

        tracking.notificationService.client.newAlert = function (event) {
            tracking.log('SignalR Alert: ' + event);
            // query for the alert
            queryAlertsRequiringAcknowledgement(event);
        };

        tracking.notificationService.client.systemNotification = function (id, subject, text, from, to) {
            tracking.log('SignalR System Notification: ' + id + ', ' + subject + ', ' + text + ', ' + from + ', ' + to);
            var notification = {
                id: id.toString(),
                text: text,
                from: from,
                to: to,
                subject: subject
            };
            addSystemNotification(notification);
        };

        tracking.notificationService.client.newAssetState = function (assetId, state) {
            var asset = findAssetById(assetId);
            console.log(state);
            updateAssetState(asset, state);
        };

        tracking.notificationService.client.waypointUpdate = function (waypointId, status) {
            if (!tracking.user.isAnonymous) {
                tracking.log('SignalR Waypoint Update: ' + waypointId + ' (' + status + ')');
                updateWaypoint(waypointId);
            }
        };

        tracking.notificationService.client.waypointDeleted = function (waypointId) {
            if (!tracking.user.isAnonymous) {
                tracking.log('SignalR Waypoint Deleted: ' + waypointId);
                addOrUpdateWaypoint({ Id: waypointId, Status: 'Deleted' });
                updateWaypointListing();
            }
        };

        tracking.notificationService.client.driverUpdate = function (assetId, drivers) {
            tracking.log('SignalR Driver Status Update for Asset ' + assetId);
            console.log(drivers);
            var asset = findAssetById(assetId);
            if (asset != null) {
                asset.Drivers = drivers;
            }
        };

        tracking.notificationService.client.bivyStatusUpdate = function (assetId, status) {
            tracking.log('SignalR Bivy Status Update for Asset ' + assetId);
            tracking.log(status);

            var asset = findAssetById(assetId);
            if (asset != null) {
                asset.Bivy = status;
            }
        };

        tracking.notificationService.client.messageStatusUpdate = function (messageId, assetId, status, lastIncoming) {
            tracking.log('SignalR Message Status Updated: ' + messageId + ' (' + status + ') for Asset ' + assetId + ' at ' + lastIncoming);
            var asset = findAssetById(assetId);
            if (asset == null) {
                return;
            }

            if (asset.MessageStatuses == null) {
                asset.MessageStatuses = [];
            }

            // a message may change statuses
            var isFound = false;
            for (var i = 0; i < asset.MessageStatuses.length; i++) {
                var item = asset.MessageStatuses[i];
                if (item.Id == messageId) {
                    // replace this
                    isFound = true;
                    item.Status = status;
                    item.Time = lastIncoming;
                }
            }
            if (!isFound) {
                asset.MessageStatuses.push({
                    Id: messageId,
                    Status: status,
                    Time: lastIncoming
                });
            }

            tracking.throttles.updatePositionStatus();
        };

        tracking.notificationService.client.newMessage = function(messageId, assetId, lastIncoming, isPopup, status, deliveryType, messageTypeId) {
            // TODO shared view mode support
            // query for new message notifications
            tracking.log('SignalR Message: ' + messageId + ' of type ' + messageTypeId + ' for Asset ' + assetId + ' at ' + lastIncoming + ' (' + isPopup + ') of delivery type ' + deliveryType);

            // download the message and add it to live.messages and live.messagesByAssetId
            // and then update any badge indicators
            // one issue here is that it's going to pop even for messages the current user has created
            // so we may need to create/pass a HasRead property on the message which would automatically be true
            // for the user who creates the message, though we don't currently capture who creates an outgoing message
            // we'd also have to have a separate ReadMessages in MessageCounts (e.g. FromMobileRead or FromMobileUnread)
            // the alternative is to no pop any to-mobile messages

            // if we don't download the message
            // if(type == 1) {
            //  add it to live.messageCountsByAssetId[assetId]
            //  update visible badges
            //  update message listing (if currently opened)
            // }
            // badge should only be if FromMobile > 0 in live mode?
            assetId = parseInt(assetId);
            messageId = parseInt(messageId);
            deliveryType = parseInt(deliveryType);
            messageTypeId = parseInt(messageTypeId);
            var isForChat = tracking.data.MESSAGES_TEXT.indexOf(messageTypeId) !== -1;
            addMessageToAssetMessageCounts(assetId, messageId, deliveryType, isForChat);

            // when messages is clicked in live mode, query server for any messages since tracking.state.portalLoadedEpoch (using moment?)
            // and invalidate cache when MessageCounts are updated...

            var asset = findAssetById(assetId);
            if (asset == null) {
                return;
            }
            if (deliveryType === 0) { // record new outgoing message status
                if (asset.MessageStatuses == null) {
                    asset.MessageStatuses = [];
                }

                asset.MessageStatuses.push({
                    Id: messageId,
                    Status: status,
                    Time: lastIncoming
                });
            } else {
                // for now we're going to invalidate the asset's message cache
                // a better strategy is to cache per asset based on the message's epoch
                // or even accept the full message here to insert into the cache
                tracking.data.live.messages = _.reject(tracking.data.live.messages, function(msg) { return msg.AssetId === assetId; });
                tracking.data.live.messagesByAssetId = _.groupBy(tracking.data.live.messages, 'AssetId');
                if (isPopup) {
                    if (asset.UnconfirmedMessages == null) {
                        asset.UnconfirmedMessages = [messageId];
                    } else {
                        asset.UnconfirmedMessages.push(messageId);
                    }

                    tracking.throttles.queryLatestNotifications();
                } else {
                    asset.LastIncomingMessage = lastIncoming;
                }

                if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
                    && parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id')) === assetId) {
                    var activeDialog = document.getElementById('dialog-functions').querySelector('.dialog');

                    if (activeDialog === tracking.data.domNodes.dialogs.idpSendCommand
                        && messageTypeId === tracking.data.MESSAGE_IDP_COMMAND_RESPONSE) {
                        requestIDPCommandLog(assetId, true);
                    }

                    if (activeDialog === tracking.data.domNodes.dialogs.idpSendCommand
                        && (messageTypeId === tracking.data.MSG_IDP_UPDATER_TERMINAL_INFO_RESPONSE
                        || messageTypeId === tracking.data.MSG_IDP_UPDATER_STATE_RESPONSE)) {
                        requestIDPUpdaterInformation(assetId);
                    }

                    var IDP_COMMAND_RESPONSES = [38, 42, 45, 51, 72, 113, 127, 151]; // Core, AVL, Geofence, Geofence Status, Immobilizer, Garmin, ARC, Utility property responses
                    if (activeDialog === tracking.data.domNodes.dialogs.idpSendCommand
                        && _.indexOf(IDP_COMMAND_RESPONSES, messageTypeId) !== -1) {
                        // TODO: separate refreshes for each message type
                        requestAVLInformation(assetId);
                    }

                    // refresh the messages or chat dialogs if it is currently opened
                    if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                        if (!isForChat && activeDialog === tracking.data.domNodes.dialogs.assetMessages) {
                            // todo: only append to the existing listing, don't recreate
                            openMessagesForAsset(asset);
                        }

                        if (isForChat && activeDialog === tracking.data.domNodes.dialogs.assetChat) {
                            // todo: only append to the existing listing, don't recreate
                            openChatForAsset(asset);
                        }
                    }
                }
            }

            tracking.throttles.updatePositionStatus();
        };

        tracking.notificationService.client.messageConfirmed = function (positionId, messageId, acknowledgedOn, acknowledgedBy) {
            tracking.log('SignalR Message Confirmed: ' + positionId + ', ' + messageId + ', ' + acknowledgedOn + ', ' + acknowledgedBy);
            var confirmedBy = '';
            if (acknowledgedBy != null) {
                if (tracking.data.users !== undefined && tracking.data.users !== null) {
                    for (var k = 0; k < tracking.data.users.length; k++) {
                        if (tracking.data.users[k].Id == acknowledgedBy) {
                            confirmedBy = tracking.data.users[k].Username;
                            break;
                        }
                    }
                }
            }

            var notification = $j('#jGrowl div[data-notification-id=' + messageId + ']');
            if (confirmedBy != '') {
                confirmedBy = tracking.strings.CONFIRMED_BY.replace('{0}', confirmedBy);
            } else {
                confirmedBy = tracking.strings.CONFIRMED;
            }
            notification.find('button').prop('disabled', true);
            notification.parent().parent().append(el('div.jGrowl-acknowledge', [el('span.ui-icon.ui-icon-circle-check.acknowledge'), text(confirmedBy)]));
            setTimeout(function () {
                notification.trigger('jGrowl.close');
            }, 5000);
        };

        tracking.notificationService.client.alertAcknowledged = function (positionId, eventId) {
            // todo: retrieve the event information to replace it
            tracking.log('SignalR Alert Acknowledged: ' + positionId + ', ' + eventId);
            toggleLoadingMessage(true, 'get-event');
            var dataPost = {
                eventId: eventId
            };
            $j.ajax({
                type: 'POST',
                url: wrapUrl('/Services/GPSService.asmx/GetAssetEventById'),
                data: JSON.stringify(dataPost),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    toggleLoadingMessage(false, 'get-event');
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            console.log(result);
                            updateAcknowledgedEventData(result.AssetEvent.Event);
                        } else {
                            utility.handleWebServiceError(tracking.strings.MSG_GET_EVENT_ERROR);
                        }
                    }
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_GET_EVENT_ERROR);
                    toggleLoadingMessage(false, 'get-event');
                }
            });
        };

        tracking.notificationService.client.reloadPage = function () {
            tracking.log('Reloading page based on server request.');
            location.reload(true);
        }

        tracking.notificationService.client.checkVersion = function () {
            tracking.log('Checking page version based on server request.');
            tracking.checkVersion();
        }

        tracking.notificationService.client.newPosition = function(message) {
            // query for new position
            tracking.log('SignalR Position: ' + message);
            tracking.throttles.queryLatestNotifications();
            tracking.throttles.updateLiveAssets();
        };

        tracking.notificationConnection.stateChanged(signalRStateChange); //, transport: 'longPolling'
        tracking.notificationConnection.start({ waitForPageLoad: false })
            .done(signalRReady)
            .fail(signalRFail);
        tracking.notificationConnection.disconnected(function () {
            tracking.log('SignalR: Disconnected from server. Restarting.');
            tracking.notification.isConnected = false;
            tracking.notification.isReconnecting = true;
            tracking.notification.reconnectInterval = setInterval(reconnectCountdown, 1000);
            reconnectCountdown();
            $j('#disconnected').show();
        });
    }

    function addMessageToAssetMessageCounts(assetId, messageId, deliveryType, isForChat) {
        // TODO support for viewMode - though this method appears to apply to normal_live mode only
        // calling function needs to take shared views into consideration
        console.log('addMessageToAssetMessageCounts: ' + assetId + ', ' + messageId + ', ' + deliveryType + ', ' + isForChat);
        assetId = parseInt(assetId);
        messageId = parseInt(messageId);
        if (tracking.data.live.messageCountsByAssetId[assetId] === undefined) {
            var messageCount = {
                Id: assetId,
                FromMobile: (deliveryType === 1) ? 1 : 0,
                FromMobileChats: (deliveryType === 1 && isForChat) ? 1 : 0,
                FromMobileDevice: (deliveryType === 1 && !isForChat) ? 1 : 0,
                ToMobile: (deliveryType === 0) ? 1 : 0,
                ToMobileChats: (deliveryType === 0 && isForChat) ? 1 : 0,
                ToMobileDevice: (deliveryType === 0 && !isForChat) ? 1 : 0,
                FromMobileUnread: (deliveryType === 1) ? [messageId] : [],
                ToMobileUnread: (deliveryType === 0) ? [messageId] : []
            };
            tracking.data.live.messageCounts.push(messageCount);
            tracking.data.live.messageCountsByAssetId[assetId] = messageCount;
        } else {
            var counts = tracking.data.live.messageCountsByAssetId[assetId];
            if (deliveryType === 1) {
                counts.FromMobile += 1;
                if (isForChat) {
                    counts.FromMobileChats += 1;
                } else {
                    counts.FromMobileDevice += 1;
                }
                if (counts.FromMobileUnread === undefined) {
                    counts.FromMobileUnread = [messageId];
                } else {
                    counts.FromMobileUnread.push(messageId);
                }
            } else if (deliveryType === 0) {
                counts.ToMobile += 1;
                if (isForChat) {
                    counts.ToMobileChats += 1;
                } else {
                    counts.ToMobileDevice += 1;
                }
                if (counts.ToMobileUnread === undefined) {
                    counts.ToMobileUnread = [messageId];
                } else {
                    counts.ToMobileUnread.push(messageId);
                }
            }
        }
        updateAssetNotificationTime(assetId, 'messages', new Date().getTime());
        updateAssetFunctionBadges(getAssetDataGroupForCurrentViewMode(), assetId);
        updateGroupFunctionBadges(getAssetDataGroupForCurrentViewMode(), [assetId], 'asset');
    }

    function reconnectCountdown() {
        tracking.notification.reconnectLeft -= 1;
        $j('#ConnectionRetry').text(tracking.strings.CONNECTION_RETRY.replace('{0}', tracking.notification.reconnectLeft));
        if (tracking.notification.reconnectLeft <= 0) {
            clearInterval(tracking.notification.reconnectInterval);
            if (tracking.notification.reconnectSeconds < 600) {
                tracking.notification.reconnectSeconds *= 2;
            }
            tracking.notification.reconnectLeft = tracking.notification.reconnectSeconds;
            signalRReconnect();
        }
    }

    function signalRReconnect() {
        tracking.notificationConnection.start()
            .done(signalRReady)
            .fail(signalRFail);
    }

    function signalRStateChange(state) {
        var stateConversion = {0: 'connecting', 1: 'connected', 2: 'reconnecting', 4: 'disconnected'};
        tracking.log('SignalR state changed from: ' + stateConversion[state.oldState] + ' to: ' + stateConversion[state.newState]);
        $('#reconnecting').hide();
        if (stateConversion[state.newState] == 'disconnected') {
            // attempt to setup a new connection
        } else if (stateConversion[state.newState] == 'connected') {
            $('#disconnected').hide();
            tracking.notification.isConnected = true;
            tracking.notification.isReconnecting = false;
            tracking.notification.reconnectSeconds = 10;
            tracking.notification.reconnectLeft = 10;
            clearTimeout(tracking.notification.reconnectInterval);
            // may have missed notifications while disconnected
            if (stateConversion[state.oldState] === 'disconnected' || stateConversion[state.oldState] === 'reconnecting') {
                setTimeout(function() {
                    tracking.throttles.queryLatestNotifications();
                    if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                        tracking.throttles.updateLiveAssets(); 
                        tracking.throttles.queryLatestEvents();
                    }
                }, Math.round(Math.random() * 10000));
            }
        } else if (stateConversion[state.newState] == 'reconnecting') {
            if (!tracking.notification.isReconnecting) {
                $('#reconnecting').show();
            }
        }
    }

    function signalRFail() {
        // todo: determine if retry is needed or if will auto-retry
        tracking.log('SignalR failed loading.');
    }

    function signalRCheckIn() {
        tracking.log('SignalR Check-In');
        if (tracking.notificationConnection.transport == null) {
            // not connected... keep trying
            if (tracking.intervals.notification == null) {
                tracking.intervals.notification = setInterval(signalRCheckIn, 60000);
            }
        } else {
            if (tracking.notificationConnection.transport.name == 'foreverFrame') {
                tracking.intervals.notificationNum++;
                tracking.log('Increment SignalR Check In');
                if (tracking.intervals.notificationNum == 3) {
                    tracking.log('Clear SignalR Check-In Interval');
                    clearInterval(tracking.intervals.notification);
                    tracking.intervals.notification = setInterval(signalRCheckIn, 300000);
                    tracking.log('Longer Check-In Started');
                }
            }
            return tracking.notificationService.server.readyForNotifications();
        }
    }

    function signalRReady() {
        //tracking.log(tracking.notificationConnection.groupsToken);
        //tracking.notificationService.readyForNotifications();
        // stop retry interval if it exists
        signalRCheckIn().done(function() {
            tracking.log('Logged into SignalR: ' + tracking.notificationConnection.groupsToken);

            if (tracking.notificationConnection.transport.name == 'foreverFrame') {
                if (tracking.intervals.notification == null) {
                    tracking.intervals.notification = setInterval(signalRCheckIn, 60000);
                }
            }
        });

        // if we're on a foreverframe transport, we need to periodically check in
        // to workaround a bug whereby we are losing our notification association
        // on frame hiccups
    }

    function toggleAnalogIOBehavior(elem) {
        var behavior = parseInt($j(elem).val());
        var tr = $j(elem).parent().parent();
        tr.find('input.override').prop('disabled', true);
        switch (behavior) {
            case 1:
                tr.find('input.fuel').prop('disabled', false);
                break;
            case 4:
                tr.find('input.weight').prop('disabled', false);
                break;
        }
    }

    function openUpgradeDialog() {
        $j('#upgrade-tracking-modal').modal('show');
    }

    function highlightPosition(positionId, message) {
        console.log('show position: ' + positionId);
        var markers = getMapMarkersForDataGroup(getAssetDataGroupForCurrentViewMode());
        if (markers === undefined) {
            return false;
        }
        var marker = _.find(markers, function (marker) {
            return marker.data.location.Id === positionId;
        });

        // prioritize live/history markers over trip marker for same position
        // TODO doesn't take into account normal/shared view mode
        var isTripMarker = false;
        if (marker === undefined) {
            isTripMarker = true;
            var tripMarkers = getMapMarkersForDataGroup(getJourneyDataGroupForCurrentViewMode());
            if (tripMarkers === undefined) {
                return false;
            }
            var marker = _.find(tripMarkers, function (marker) {
                return marker.data.location.Id === positionId;
            });
        }

        if (marker !== undefined) {
            marker.data.message = message; // TODO what is this
            if (!isTripMarker) {
                var assetId = marker.data.assetId;
                toggleAssetActive(assetId, true, true);
            } else {
                var tripId = marker.data.tripId;
                var trip = findTripById(tripId);
                toggleTripActive(trip.JourneyId, tripId, true, true);
            }
            if (!marker.data.location.IsHidden) {
                addItemToMap(marker);
            }
            markerClick(marker, 'position', null, false);
            //marker.fire('click');
            return true;
        }

        return false;
    }

    function cleanupFenceEditing() {
        // clear out the MapToolbar items
        MapToolbar.removeAllFeatures();
        tracking.state.isEditingGeofence = false;
        stopChoosingMapLocation(tracking.state.mapClickHandlers.GEOFENCE);
    }

    function togglePlaceActive(placeId, makePlaceActive, updateUI) {
        var isCurrentlyActive = !isItemIncluded(tracking.user.displayPreferences.hiddenPlaces, placeId);
        if (makePlaceActive == null)
            makePlaceActive = !isCurrentlyActive;

        if (isCurrentlyActive == makePlaceActive)
            return;

        var place = findPlaceById(placeId);
        color = place.Color;

        if (tracking.data.placeMarkers != null) {
            for (var i = 0; i < tracking.data.placeMarkers.length; i++) {
                var markerPlaceId = tracking.data.placeMarkers[i].data.placeId;
                if (markerPlaceId == placeId) {
                    if (makePlaceActive) {
                        addItemToMap(tracking.data.placeMarkers[i], tracking.data.mapLayers.places);
                    } else {
                        removeItemFromMap(tracking.data.placeMarkers[i], tracking.data.mapLayers.places);
                    }
                }
            }
        }

        // change icon and set preferences
        if (makePlaceActive) {
            updatePlaceListingIcon(placeId, color, true);
            displayPreferencesRemove('hiddenPlaces', placeId);
        } else {
            updatePlaceListingIcon(placeId, color, false);
            displayPreferencesAdd('hiddenPlaces', placeId);
        }

        // ensure checkbox is proper
        updatePlaceVisibilityStatus(placeId, makePlaceActive);
        if (updateUI) {
            updateGroupVisibilityStatus('all-places');
        }
    }

    function toggleFenceActive(fenceId, makeFenceActive, updateUI) {
        var isCurrentlyActive = !isItemIncluded(tracking.user.displayPreferences.hiddenFences, fenceId);
        if (makeFenceActive == null) {
            makeFenceActive = !isCurrentlyActive;
        }

        if (isCurrentlyActive === makeFenceActive) {
            return false;
        }

        var fence = findFenceById(fenceId);

        // show/hide fence and its segments on the map
        if (tracking.data.fenceMarkers != null) {
            for (var i = 0; i < tracking.data.fenceMarkers.length; i++) {
                var markerFenceId = tracking.data.fenceMarkers[i].data.fenceId;
                if (markerFenceId == fenceId) {
                    if (makeFenceActive) {
                        addItemToMap(tracking.data.fenceMarkers[i]);
                    } else {
                        removeItemFromMap(tracking.data.fenceMarkers[i]);
                    }
                }
            }
        }

        // change icon and set preferences
        if (makeFenceActive) {
            updateFenceListingIcon(fence, true);
            displayPreferencesRemove('hiddenFences', fenceId);
        } else {
            updateFenceListingIcon(fence, false);
            displayPreferencesAdd('hiddenFences', fenceId);
        }

        // ensure checkbox is proper
        updateFenceVisibilityStatus(fenceId, makeFenceActive);
        if (updateUI) {
            updateGroupVisibilityStatus('all-fences');
        }
    }

    function toggleGroupActive(groupId, makeGroupActive) {
        $j('#group-' + groupId + ' input.showhide.group').prop('checked', makeGroupActive);
        // toggle assets on/off for this group
        // loop through assets in group
        for(var i = 0; i < tracking.data.assets.length; i++) {
            var asset = tracking.data.assets[i];
            for(var j = 0; j < asset.GroupIds.length; j++) {
                if (asset.GroupIds[j] === groupId) {
                    // todo: use a deferred method to update the UI
                    toggleAssetActive(asset.Id, makeGroupActive, false);
                    break;
                }
            }
        }

        // journeys
        if (groupId.indexOf('journey-') === 0) {
            var journeyId = parseInt(groupId.substring(8));
            var journey = findJourneyById(journeyId);
            if (journey !== null) {
                _.each(journey.Trips, function (trip) {
                    toggleTripActive(journey.Id, trip.Id, makeGroupActive, false);
                });
            }
        }

        // loop through subgroups
        for(var i = 0; i < tracking.data.groups.length; i++) {
            if (tracking.data.groups[i].ParentGroupId == groupId) {
                toggleGroupActive(tracking.data.groups[i].Id, makeGroupActive);
            }
        }
    }

    function updateActiveAssetInformation(viewMode) {
        viewMode = viewMode !== undefined ? viewMode : tracking.viewModes.NORMAL;
        // todo: if trips are visible, how do they interact with the asset information?
        if (viewMode !== tracking.state.activeViewMode) {
            return;
        }
        if (viewMode === tracking.viewModes.NORMAL) {
            if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                getLiveAssetsShown();
            } else {
                getHistoryAssetsAndPositionsShown(viewMode);
            }
        } else if (viewMode === tracking.viewModes.SHARED_VIEW) {
            getHistoryAssetsAndPositionsShown(viewMode);
        }
    }

    function addItemToMap(item, layer, viewMode) {
        if (item === undefined || item === null) {
            return;
        }

        var baseLayer = tracking.data.mapLayers.normal;
        if (viewMode === tracking.viewModes.SHARED_VIEW) {
            baseLayer = tracking.data.mapLayers.sharedView;
        }

        if (layer === undefined || layer === null) {
            if (!baseLayer.hasLayer(item)) {
                baseLayer.addLayer(item);
            }
        } else {
            if (!layer.hasLayer(item)) {
                layer.addLayer(item);
            }
        }
    }

    function removeItemFromMap(item, layer, viewMode) {
        if (item === undefined || item === null) {
            return;
        }

        var baseLayer = tracking.data.mapLayers.normal;
        if (viewMode === tracking.viewModes.SHARED_VIEW) {
            baseLayer = tracking.data.mapLayers.sharedView;
        }

        if (layer === undefined || layer === null) {
            baseLayer.removeLayer(item);
        } else {
            layer.removeLayer(item);
        }
    }

    function createMarkerCluster(asset, trip, sharedViewId) {
        var color = asset.Color == null ? '' : asset.Color;
        var markerCluster = L.markerClusterGroup({
            maxClusterRadius: 36,
            disableClusteringAtZoom: 17,
            removeOutsideVisibleBounds: true, // todo: may interfere with clicking a position in results
            iconCreateFunction: function (cluster) {
                return L.divIcon({
                    iconSize: [36, 36],
                    iconAnchor: [18, 18],
                    className: 'custom-cluster-icon',
                    html: '<img src="' + ((cluster.imagePath == null) ? cluster._group.imagePath : cluster.imagePath) + '" class="leaflet-marker-icon cluster-icon-36"><div class="marker-cluster-label">' + cluster.getChildCount() + '</div>'
                });
            }
        });
        markerCluster.imageData = { 
            assetClass: asset.Class, 
            color: color, 
            assetId: asset.Id 
        };
        if (trip !== undefined && trip !== null) {
            markerCluster.imageData.tripId = trip.Id;
        } else if (sharedViewId !== undefined && sharedViewId !== null) {
            markerCluster.imageData.sharedViewId = sharedViewId;
        }
        markerCluster.imagePath = createMarkerPath(asset.Class, color, null, null, asset.Id, false, null, false, false);
        //if (locations != null) {
        //    var clusterMarkers = _.filter(locations, function (item) { return item.marker != null && item.marker.data.location.IsHidden === false; });
        //    markerCluster.addLayers(clusterMarkers);
        //}
        return markerCluster;
    }

    function toggleTripActive(journeyId, tripId, makeTripActive, updateUI) {
        var journey = findJourneyById(journeyId);
        if (journey === null) {
            return false;
        }
        var trip = null;
        for (var i = 0; i < journey.Trips.length; i++) {
            if (journey.Trips[i].Id === tripId) {
                trip = journey.Trips[i];
                break;
            }
        }
        if (trip === null) {
            return false;
        }
        var asset = findAssetById(journey.AssetId);
        if (asset === null) {
            return false;
        }

        var isCurrentlyActive = isItemIncluded(tracking.user.displayPreferences.visibleTrips, tripId);
        if (makeTripActive === null) {
            makeTripActive = !isCurrentlyActive;
        }

        if (isCurrentlyActive === makeTripActive) {
            return;
        }

        var tripPositions = null;
        if (tracking.data.trips.positionsByTripId !== null && tracking.data.trips.positionsByTripId[tripId] !== undefined) {
            tripPositions = tracking.data.trips.positionsByTripId[tripId];
        }

        var setBounds = false;
        if (tripPositions !== null) {
            if (tracking.data.trips.mapLinesByTripId[trip.Id] !== undefined) {
                if (makeTripActive) {
                    addItemToMap(tracking.data.trips.mapLinesByTripId[trip.Id]);
                } else {
                    removeItemFromMap(tracking.data.trips.mapLinesByTripId[trip.Id]);
                }
            }

            if (makeTripActive) {
                if (tracking.preferences.PREFERENCE_GROUP_POSITIONS) {
                    if (tracking.data.trips.markerClustersByTripId[trip.Id] !== undefined) {
                        addItemToMap(tracking.data.trips.markerClustersByTripId[trip.Id]);
                    }
                } else {
                    _.each(tracking.data.trips.markersByTripId[trip.Id], function (item) {
                        if (!item.data.location.IsHidden) {
                            addItemToMap(item);
                        }
                    });
                }
            } else {
                // remove marker cluster
                if (tracking.preferences.PREFERENCE_GROUP_POSITIONS) {
                    if (tracking.data.trips.markerClustersByTripId[trip.Id] !== undefined) {
                        removeItemFromMap(tracking.data.trips.markerClustersByTripId[trip.Id]);
                    }
                } else {
                    _.each(tracking.data.trips.markersByTripId[trip.Id], function (item) {
                        removeItemFromMap(item);
                    });
                }
            }

            setBounds = true;
        }

        if (tripPositions === null && makeTripActive) {
            querySingleTrip(journey, trip);
        }

        updateTripVisibilityStatus(tripId, makeTripActive);

        // grey out / make active
        var index = _.indexOf(tracking.data.visible.trips, tripId);
        if (makeTripActive) {
            updateTripListingIcon(tripId, asset.Color, true, asset.Class);
            displayPreferencesAdd('visibleTrips', tripId);
            if (index === -1) {
                tracking.data.visible.trips.push(tripId);
            }
        } else {
            updateTripListingIcon(tripId, asset.Color, false, asset.Class);
            displayPreferencesRemove('visibleTrips', tripId);
            if (index !== -1) {
                tracking.data.visible.trips.splice(index, 1);
            }
        }
        s
        // these UI updates should really only happen after all assets have been toggled, so for group changes this is rough
        if (updateUI) {
            updateGroupVisibilityStatus('journey-' + journeyId);
            updateActiveAssetInformation();
        }
        if (setBounds) {
            setMapBounds();
        }
        return true;
    }

    function hideAllAssetsExcept(assetId) {
        _.each(tracking.data.assets, function (asset) {
            var makeActive = asset.Id === assetId;
            toggleAssetActive(asset.Id, makeActive, false);
        });

        _.each(tracking.data.groups, function (group) {
            updateGroupVisibilityStatus(group.Id);
        });
        updateGroupVisibilityStatus('all-assets');
        updateActiveAssetInformation();
    }

    function toggleAssetActive(assetId, makeAssetActive, updateUI) {
        var isDone = $.Deferred();

        var asset = findAssetById(assetId);
        if (asset === null) {
            isDone.resolve(true);
            return isDone;
        }

        var isCurrentlyActive = !isItemIncluded(tracking.user.displayPreferences.hiddenAssets, assetId);
        if (makeAssetActive === null) {
            // assuming toggling active/inactive if not specified
            makeAssetActive = !isCurrentlyActive;
        }

        if (isCurrentlyActive === makeAssetActive) {
            isDone.resolve(true);
            return isDone;
        }
        
        var deferreds = [];

        if (tracking.state.activeMapMode !== tracking.mapModes.LIVE) {
            var isAssetFound = tracking.data.history.positionsByAssetId[assetId] !== undefined;

            // toggle any map items for the asset
            if (isAssetFound) {
                // show/hide positions connecting line
                if (tracking.data.history.mapLinesByAssetId[asset.Id] !== undefined) {
                    if (makeAssetActive) {
                        addItemToMap(tracking.data.history.mapLinesByAssetId[asset.Id]);
                    } else {
                        removeItemFromMap(tracking.data.history.mapLinesByAssetId[asset.Id]);
                    }
                }

                // show/hide markers
                if (makeAssetActive) {
                    if (tracking.preferences.PREFERENCE_GROUP_POSITIONS) {
                        if (tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined) {
                            addItemToMap(tracking.data.history.markerClustersByAssetId[asset.Id]);
                        }
                    } else {
                        _.each(tracking.data.history.markersByAssetId[assetId], function (item) {
                            if (!item.data.location.IsHidden) {
                                addItemToMap(item);
                            }
                        });
                    }
                } else {
                    // remove marker cluster
                    if (tracking.preferences.PREFERENCE_GROUP_POSITIONS) {
                        if (tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined) {
                            removeItemFromMap(tracking.data.history.markerClustersByAssetId[asset.Id]);
                        }
                    } else {
                        _.each(tracking.data.history.markersByAssetId[assetId], function (item) {
                            removeItemFromMap(item);
                        });
                    }
                }

                setMapBounds();
            }

            if (!isAssetFound && makeAssetActive && tracking.state.hasQueriedHistory) {
                // query for this specific asset's results to add to existing
                deferreds.push(querySingleAsset(assetId));
            }
        } else {
            // show/hide latest position from map and from history
            _.each(tracking.data.live.markersByAssetId[assetId], function(marker) {
                if (makeAssetActive && marker.data.hide !== true) { // data.hide is for markers only temporarily visible, loaded outside of active view
                    addItemToMap(marker);
                } else {
                    removeItemFromMap(marker);
                }
            });
            if (tracking.data.live.mapLinesByAssetId[asset.Id] !== undefined) {
                if (makeAssetActive) {
                    addItemToMap(tracking.data.live.mapLinesByAssetId[asset.Id]);
                } else {
                    removeItemFromMap(tracking.data.live.mapLinesByAssetId[asset.Id]);
                }
            }
        }

        updateAssetVisibilityStatus(assetId, makeAssetActive);

        // grey out / make active
        var index = _.indexOf(tracking.data.visible.assets, assetId);
        if (makeAssetActive) {
            updateAssetListingIcon(assetId, asset.Color, true, asset.Class);
            displayPreferencesRemove('hiddenAssets', assetId);
            if (index === -1) {
                tracking.data.visible.assets.push(assetId);
            }
        } else {
            updateAssetListingIcon(assetId, asset.Color, false, asset.Class);
            displayPreferencesAdd('hiddenAssets', assetId);
            if (index !== -1) {
                tracking.data.visible.assets.splice(index, 1);
            }
        }

        // these UI updates should really only happen after all assets have been toggled, so for group changes this is rough
        if (updateUI) {
            // update group visibility statuses
            _.each(asset.GroupIds, function (assetGroupId) {
                updateGroupVisibilityStatus(assetGroupId);
            });
            updateGroupVisibilityStatus('all-assets');

            updateActiveAssetInformation();
        }
        $.when.apply($, deferreds).done(function () {
            isDone.resolve(true);
        });
        return isDone;
    }

    function getGroupAssetStatus(groupId) {
        var result = {
            items: 0,
            active: 0,
            assets: 0,
            assetIds: [],
            fences: 0,
            fenceIds: [],
            places: 0,
            placeIds: [],
            trips: 0,
            tripIds: [],
            sharedViews: 0,
            sharedViewIds: []
        };
        var groupContents = tracking.data.domNodes.groupContents[groupId];
        if (groupContents === undefined) {
            return result;
        }

        result.items = groupContents.getElementsByClassName('group-item').length;
        result.active = groupContents.getElementsByClassName('group-item active').length;
        var assets = groupContents.getElementsByClassName('assets-item');
        _.each(assets, function (elem) {
            result.assetIds.push(parseInt(elem.getAttribute('data-asset-id')));
        });
        result.assets = assets.length;
        var places = groupContents.getElementsByClassName('places-item');
        _.each(places, function (elem) {
            result.placeIds.push(parseInt(elem.getAttribute('data-place-id')));
        });
        result.places = places.length;
        var fences = groupContents.getElementsByClassName('fences-item');
        _.each(fences, function (elem) {
            result.fenceIds.push(elem.getAttribute('data-fence-id'));
        });
        result.fences = fences.length;
        var trips = groupContents.getElementsByClassName('journeys-item');
        _.each(trips, function (elem) {
            result.tripIds.push(parseInt(elem.getAttribute('data-trip-id')));
        });
        result.trips = trips.length;
        var sharedViews = groupContents.getElementsByClassName('shared-views-item');
        _.each(sharedViews, function (elem) {
            result.sharedViewIds.push(parseInt(elem.getAttribute('data-shared-view-id')));
        });
        result.sharedViews = sharedViews.length;

        var group = findGroupById(groupId);

        if (group !== null) {
            if (group.GroupIds !== undefined) {
                _.each(group.GroupIds, function (subGroupId) {
                    var groupCounts = getGroupAssetStatus(subGroupId);
                    result.items += groupCounts.items;
                    result.active += groupCounts.active;
                    result.assets += groupCounts.assets;
                    result.assetIds = _.union(result.assetIds, groupCounts.assetIds);
                    result.places += groupCounts.places;
                    result.placeIds = _.union(result.placeIds, groupCounts.placeIds);
                    result.fences += groupCounts.fences;
                    result.fenceIds = _.union(result.fenceIds, groupCounts.fenceIds);
                    result.trips += groupCounts.trips;
                    result.tripIds = _.union(result.tripIds, groupCounts.tripIds);
                    result.sharedViews += groupCounts.sharedViews;
                    result.sharedViewIds = _.union(result.sharedViewIds, groupCounts.sharedViewIds);
                });
            }
            group.AssetIds = _.map(result.assetIds, function(assetId) { return parseInt(assetId);});
        }

        _.each(result.assetIds, function (id) {
            var assetId = parseInt(id);
            var asset = findAssetById(assetId);
            if (asset.ParentGroupIds === undefined) {
                asset.ParentGroupIds = [];
            }
            if (asset.ParentGroupIds.indexOf(groupId) === -1) {
                asset.ParentGroupIds.push(groupId);
            }
        });
        return result;
    }

    function updateItemVisibilityStatus(itemId, makeActive, visibleList, domNodes) {
        var index = _.indexOf(visibleList, itemId);
        if (makeActive) {
            if (index === -1) {
                visibleList.push(itemId);
            }
        } else {
            if (index !== -1) {
                visibleList.splice(index, 1);
            }
        }
        var itemNodes = domNodes[itemId];
        if (!_.isArray(itemNodes)) {
            itemNodes = [itemNodes];
        }

        console.log(itemId);

        _.each(itemNodes, function (itemNode) {
            var visibilityIcon = itemNode.querySelector('.showhide');
            if (makeActive) {
                visibilityIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#visible');
                visibilityIcon.classList.add('active');
            } else {
                visibilityIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#invisible');
                visibilityIcon.classList.remove('active');
            }
        });
    }

    function updateTripVisibilityStatus(tripId, makeActive) {
        updateItemVisibilityStatus(tripId, makeActive, tracking.data.visible.trips, tracking.data.domNodes.trips);
    }

    function updateAssetVisibilityStatus(assetId, makeActive) {
        var index = _.indexOf(tracking.data.visible.assets, assetId);
        if (makeActive) {
            if (index === -1) {
                tracking.data.visible.assets.push(assetId);
            }
        } else {
            if (index !== -1) {
                tracking.data.visible.assets.splice(index, 1);
            }
        }
        _.each(tracking.data.domNodes.assets[assetId], function (assetNode) {
            var visibilityIcon = assetNode.querySelector('.showhide');
            if (makeActive) {
                visibilityIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#visible');
                visibilityIcon.classList.add('active');
            } else {
                visibilityIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#invisible');
                visibilityIcon.classList.remove('active');
            }
        });
    }

    function updatePlaceVisibilityStatus(placeId, makeActive) {
        var index = _.indexOf(tracking.data.visible.places, placeId);
        var placeNode = tracking.data.domNodes.places[placeId];
        var visibilityIcon = placeNode.querySelector('.showhide');
        if (makeActive) {
            visibilityIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#visible');
            visibilityIcon.classList.add('active');
            if (index === -1) {
                tracking.data.visible.places.push(placeId);
            }
        } else {
            visibilityIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#invisible');
            visibilityIcon.classList.remove('active');
            if (index !== -1) {
                tracking.data.visible.places.splice(index, 1);
            }
        }
    }

    function updateFenceVisibilityStatus(fenceId, makeActive) {
        var index = _.indexOf(tracking.data.visible.fences, fenceId);
        var fenceNode = tracking.data.domNodes.fences[fenceId];
        var visibilityIcon = fenceNode.querySelector('.showhide');
        if (makeActive) {
            visibilityIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#visible');
            visibilityIcon.classList.add('active');
            if (index === -1) {
                tracking.data.visible.fences.push(fenceId);
            }
        } else {
            visibilityIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#invisible');
            visibilityIcon.classList.remove('active');
            if (index !== -1) {
                tracking.data.visible.fences.splice(index, 1);
            }
        }
    }

    function updateGroupVisibilityStatus(groupId) {
        var group = findGroupById(groupId);
        var groupNode = document.getElementById('group-' + groupId);
        if (groupNode === null) {
            groupNode = tracking.data.domNodes.groups[groupId];
            if (groupNode === undefined) {
                return;
            }
        }
        var visibilityIcon = groupNode.querySelector('.showhide');
        var assetCounts = getGroupAssetStatus(groupId);
        if (assetCounts.active > 0) {
            if (assetCounts.items === assetCounts.active) {
                visibilityIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#visible');
                visibilityIcon.classList.add('active');
                visibilityIcon.classList.remove('indeterminate');
            } else {
                visibilityIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#indeterminate');
                visibilityIcon.classList.add('indeterminate');
                visibilityIcon.classList.remove('active');
            }
        } else {
            visibilityIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#invisible');
            visibilityIcon.classList.remove('active');
            visibilityIcon.classList.remove('indeterminate');
        }
        var groupCount = groupNode.querySelector('.item-count');
        var groupCountNumber = 0;
        if (groupCount !== null) {
            switch (groupId) {
                case 'all-fences':
                    groupCountNumber = assetCounts.fenceIds.length;
                    break;
                case 'all-places':
                    groupCountNumber = assetCounts.placeIds.length;
                    break;
                case 'all-shared-views':
                    groupCountNumber = assetCounts.sharedViewIds.length;
                    break;
                default:
                    if (groupId.indexOf('journey-') !== -1) {
                        groupCountNumber = assetCounts.tripIds.length;
                    } else {
                        groupCountNumber = assetCounts.assetIds.length;
                    }
                    break;
            }
            groupCount.textContent = groupCountNumber;
            if (groupCountNumber >= 1000) {
                groupCount.classList.add('large');
            } else {
                groupCount.classList.remove('large');
            }
        }
        if (group == null) {
            return;
        }
        if (group.ParentGroupId != null) {
            updateGroupVisibilityStatus(group.ParentGroupId);
        }
    }

    var loading = [];

    function updatePlaceListingIcon(id, color, isActive) {
        updateItemListingIcon(id, color, isActive, 'Generic', tracking.data.domNodes.places);
    }

    function updateFenceListingIcon(fence, isActive) {
        var color = getFenceColor(fence);
        updateItemListingIcon(fence.Id, color, isActive, 'Fence', tracking.data.domNodes.fences);
    }

    function updateSharedViewListingIcon(sharedView) {
        var color = getItemHexColor(sharedView);
        var isActive = sharedView.IsEnabled;
        var svgIcon = getSvgIconForItemType('shared-views', sharedView);
        var items = tracking.data.domNodes.sharedViews[sharedView.Id];
        if (!_.isArray(items)) {
            items = [items];
        }
        _.each(items, function (itemNode) {
            if (isActive) {
                itemNode.classList.remove('disabled');
                itemNode.classList.add('active');
            } else {
                itemNode.classList.add('disabled');
                itemNode.classList.remove('active');
            }
            var itemIcon = itemNode.querySelector('.list-item-icon');
            itemIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgIcon);
            itemIcon.style['color'] = color;
        });
    }

    function updateItemListingIcon(itemId, color, isActive, iconType, nodeList) {
        var alpha = isActive ? null : 50;
        var forceAlpha = isActive ? false : true;
        var iconPath = createMarkerPath(iconType, color, null, alpha, itemId, forceAlpha);
        var items = nodeList[itemId];
        if (!_.isArray(items)) {
            items = [items];
        }
        _.each(items, function (itemNode) {
            if (isActive) {
                itemNode.classList.remove('disabled');
                itemNode.classList.add('active');
            } else {
                itemNode.classList.add('disabled');
                itemNode.classList.remove('active');
            }
            var itemIcon = itemNode.querySelector('.item-header');
            itemIcon.style['background-image'] = 'url(' + iconPath + ')';
        });
    }

    function updateTripListingIcon(id, color, isActive, icon) {
        updateItemListingIcon(id, color, isActive, icon, tracking.data.domNodes.trips);
    }

    function updateAssetListingIcon(id, color, isActive, icon) {
        updateItemListingIcon(id, color, isActive, icon, tracking.data.domNodes.assets);
    }

    function rgbToHsl(r, g, b) {
        var min, max, i, l, s, maxcolor, h, rgb = [];
        rgb[0] = r / 255;
        rgb[1] = g / 255;
        rgb[2] = b / 255;
        min = rgb[0];
        max = rgb[0];
        maxcolor = 0;
        for (i = 0; i < rgb.length - 1; i++) {
            if (rgb[i + 1] <= min) { min = rgb[i + 1]; }
            if (rgb[i + 1] >= max) { max = rgb[i + 1]; maxcolor = i + 1; }
        }
        if (maxcolor == 0) {
            h = (rgb[1] - rgb[2]) / (max - min);
        }
        if (maxcolor == 1) {
            h = 2 + (rgb[2] - rgb[0]) / (max - min);
        }
        if (maxcolor == 2) {
            h = 4 + (rgb[0] - rgb[1]) / (max - min);
        }
        if (isNaN(h)) { h = 0; }
        h = h * 60;
        if (h < 0) { h = h + 360; }
        l = (min + max) / 2;
        if (min == max) {
            s = 0;
        } else {
            if (l < 0.5) {
                s = (max - min) / (max + min);
            } else {
                s = (max - min) / (2 - max - min);
            }
        }
        s = s;
        return { h: h.toFixed(0), s: s.toFixed(2), l: l.toFixed(2) };
    }

    function convertHexToRgb(hex) {
        if (hex.indexOf('#') === 0) {
            hex = hex.substring(1);
        }
        var r = parseInt(hex.substr(0, 2), 16);
        var g = parseInt(hex.substr(2, 2), 16);
        var b = parseInt(hex.substr(4, 2), 16);
        return { r: r, g: g, b: b};
    }

    function convertHexToHsl(hex) {
        var rgb = convertHexToRgb(hex);
        return rgbToHsl(rgb.r, rgb.g, rgb.b);
        //console.log(hsl);
        //console.log("hsl(" + hsl.h + ", " + Math.round(hsl.s * 100) + "%, " + Math.round(hsl.l * 100) + "%)");
    }

    function convertHexToLum(hex) {
        var rgb = convertHexToRgb(hex);
        //var lum = 0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b;
        var lum = Math.sqrt(0.241 * rgb.r + 0.691 * rgb.g + 0.068 * rgb.b);
    }

    function getBackgroundColorAsHex(elem) {
        var bg = null;
        if (elem.currentStyle) {
            bg = elem.currentStyle['backgroundColor'];
        } else if (window.getComputedStyle) {
            bg = document.defaultView.getComputedStyle(elem,null).getPropertyValue('background-color');
        }
        if (bg.search("rgb") == -1) {
            return bg;
        } else {
            return rgbToHex(bg);
        }
    }

    function rgbToHex(rgb) {
        rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
        function hex(x) {
            return ("0" + parseInt(x).toString(16)).slice(-2);
        }
        return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]);
    }

    function convertHexToSortable(hex) {
        var rgb = convertHexToRgb(hex);
        var lum = Math.sqrt(0.241 * rgb.r + 0.691 * rgb.g + 0.068 * rgb.b);
        var hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
        return { h: parseInt(hsl.h), lum: parseInt(lum*8), l: parseInt(hsl.l *8)};
    }

    function convertNamedColorToHex(color) {
        switch (color) {
            case 'blue':
                return '#5781fc';
            case 'green':
                return '#00e13c';
            case 'turquoise':
                return '#57dbdb';
            case 'orange':
                return '#fdbf67';
            case 'pink':
                return '#e14f9e';
            case 'purple':
                return '#7e55fc';
            case 'yellow':
                return '#fcf357';
            case 'white':
                return '#ffffff';
            case 'gold':
                return '#e1c400';
            case 'darkred':
                return '#940000';
            case 'darkgreen':
                return '#559501';
            case 'grey':
                return '#d4d4d4';
            case 'darkgrey':
                return '#535353';
            case 'brown':
                return '#43312a';
            case 'magenta':
                return '#bf00f2';
            case 'darkorange':
                return '#c64102';
            case 'darkblue':
                return '#034c8b';
            case 'red':
            default:
                return '#fd7567';
        }
    }

    // Snap To Roads Queueing
    function addSnapToRoadsRequestToQueue(item, ignoreMode) {
        tracking.data.snapToRoadsQueue.push(item);
        if (!tracking.state.isSnapToRoadsQueueRunning) {
            tracking.state.isSnapToRoadsQueueRunning = true;
            tracking.intervals.snapToRoadsInterval = setInterval(processSnapToRoadsQueue, 250); // 10 per second maximum, 50 ms cushion
        }
    }

    function clearSnapToRoadsQueue() {
        if (tracking.intervals.snapToRoadsInterval != null) {
            clearInterval(tracking.intervals.snapToRoadsInterval);
        }
        tracking.state.isSnapToRoadsQueueRunning = false;
        tracking.state.isSnapToRoadsQueueProcessing = false;
        tracking.data.snapToRoadsQueue = [];
    }

    function processSnapToRoadsQueue() {
        if (tracking.state.isSnapToRoadsQueueProcessing) {
            return;
        }
        tracking.state.isSnapToRoadsQueueProcessing = true;
        if (!tracking.state.isSnapToRoadsQueueRunning) {
            return;
        }

        // singleton that can only be started once
        if (tracking.data.snapToRoadsQueue.length == 0) {
            clearSnapToRoadsQueue();
            return;
        }

        var item = tracking.data.snapToRoadsQueue[0];

        // call snap.ashx with p in the format of lng,lat;
        // todo: include bearings
        var points = '';
        var timestamps = '';
        var radiuses = '';
        for (var i = 0; i < item.points.length; i++) {
            if (points != '') {
                points += ';';
                timestamps += ';';
                radiuses += ';';
            }
            var point = item.points[i].latLng;
            var timestamp = item.points[i].timestamp;
            var accuracy = item.points[i].accuracy;
            points += point.lng + ',' + point.lat;
            timestamps += timestamp;
            radiuses += (accuracy != null) ? ((accuracy > 50) ? 50 : accuracy) : '10';
        }

        var url = '/services/snap.ashx?p=' + points + '&t=' + timestamps + '&r=' + radiuses;
        $j.ajax({
            type: 'GET',
            url: wrapUrl(url),
            dataType: 'json',
            contentType: false,
            success: function (results) {
                var stopProcessing = true;
                var pathToAdd = [];
                if (results.code == 'Ok') {
                    var newPoints = decode_polyline(results.polyline);
                    for (var i = 0; i < newPoints.length; i++) {
                        pathToAdd.push(L.latLng(newPoints[i][0], newPoints[i][1]));
                    }
                } else {
                    for (var i = 0; i < item.points.length; i++) {
                        pathToAdd.push(item.points[i]);
                    }
                }

                tracking.data.snapToRoadsQueue.shift();

                if (item.target != null) {
                    var locations = item.target.getLatLngs();
                    for (var i = 0; i < pathToAdd.length; i++) {
                        locations.push(pathToAdd[i]);
                    }
                    item.target.setLatLngs(locations);
                }
                if (stopProcessing) {
                    tracking.state.isSnapToRoadsQueueProcessing = false;
                }
            },
            error: function () {
                var pathToAdd = [];
                for (var i = 0; i < item.points.length; i++) {
                    pathToAdd.push(item.points[i].latLng);
                }
                if (item.target != null) {
                    var locations = item.target.getLatLngs();
                    for (var i = 0; i < pathToAdd.length; i++) {
                        locations.push(pathToAdd[i]);
                    }
                    item.target.setLatLngs(locations);
                }
                tracking.data.snapToRoadsQueue.shift();
                tracking.state.isSnapToRoadsQueueProcessing = false;
            }
        });
    }

    // End Snap To Roads Queueing

    function shouldSplit(array, i, val) {
        if (array.length <= (i + 1))
            return false;
        return (Math.abs(val.latLng.lng) == 179.99999) && (Math.abs(array[i + 1].latLng.lng) == 179.99999);
    }

    function splitAtEvery(latLons) {
        var sections = [];
        var arrayClone = latLons.slice(0);
        $j.each(arrayClone, function (i, item) {
            var sectionsLength = 0;
            for (var j = 0; j < sections.length; j++) {
                var section = sections[j];
                sectionsLength += section.length;
            }
            if (shouldSplit(latLons, i, item) == true) {
                sections.push(arrayClone.slice(0, i + 1 - sectionsLength));
                arrayClone = arrayClone.slice(i + 1 - sectionsLength, arrayClone.length);
            }
        });
        sections.push(arrayClone);
        return sections;
    }

    function splitPathAcrossAntiMeridian(latLons) {
        // should return one array if not split, or more if yes split
        // will be split everytime the path crosses the antimeridian
        var normalizeIndexes = []; // indexes of points where the anti meridian is crossed
        for (var i = 0; i < latLons.length; i++) {
            var latLon = latLons[i].latLng;
            var previous = latLons[i - 1];
            if (previous != null) {
                // compare longitudes to determine if antimerdian has been crossed
                if (Math.abs(latLon.lng - previous.latLng.lng) > 180) {
                    normalizeIndexes.push(i);
                }
            }
        }
        var normalizeIndexesRef = normalizeIndexes.slice(0); // duplicate normalize indexes array
        for (var i = 0; i < normalizeIndexes.length; i++) {
            var flipDirection = (latLons[normalizeIndexesRef[i]].latLng.lng < 0) ? -1 : 1;
            // calculate latitude of break point instead of relying on chosen latitude
            var latLng0 = latLons[normalizeIndexesRef[i]-1].latLng;
            var latLng1 = latLons[normalizeIndexesRef[i]].latLng;
            var lng1 = (latLng1.lng < 0) ? (latLng1.lng + 360) : latLng1.lng;
            var lng0 = (latLng0.lng < 0) ? (latLng0.lng + 360) : latLng0.lng;
            var slope = (latLng1.lat - latLng0.lat) / (lng1 - lng0);
            var intercept = latLng0.lat - (slope * lng0);
            var newLatitude = (slope * 180) + intercept;
            // using basic 2D geometry (not worth the spherical calculations I don't think)
            // todo: position ids need to be populated at least as well
            latLons.splice(normalizeIndexesRef[i], 0, { latLng: L.latLng(newLatitude, flipDirection * -179.99999) });
            latLons.splice(normalizeIndexesRef[i] + 1, 0, { latLng: L.latLng(newLatitude, flipDirection * 179.99999) });
            normalizeIndexesUpdate = [];
            for (var j = 0; j < normalizeIndexesRef.length; j++) {
                normalizeIndexesUpdate[j] = normalizeIndexesRef[j] + 2;
            }
            normalizeIndexesRef = normalizeIndexesUpdate;
        }
        var sections = splitAtEvery(latLons);
        return sections;
    }

    function createLinePathsFromPositions(positions) {
        var linePath = [];
        for (var j = 0; j < positions.length; j++) {
            // add position to map and coposition filters
            var position = positions[j];
            if (position.IsHidden === true) {
                continue;
            }
            var point = {
                id: position.Id,
                latLng: L.latLng(position.Lat, position.Lng),
                timestamp: position.Epoch,
                accuracy: position.Accuracy
            };
            linePath.push(point);
        }

        // create line connecting positions
        // split the line across the antimeridian so that lines don't "cross" the globe and look wrong
        linePath = splitPathAcrossAntiMeridian(linePath);
        return linePath;
    }

    function createPositionHistorySummary(asset, trip, fromId, toId, e) {
        console.log(asset.Id + ' - ' + fromId + ' - ' + toId);
        var dialog = $(tracking.data.domNodes.infoDialogs.positionHistory);
        dialog.data('assetId', asset.Id);
        var allPositions = [];

        if (trip !== undefined && trip !== null) {
            dialog.data('tripId', trip.Id);
            allPositions = tracking.data.trips.positionsByTripId[trip.Id].Positions;
        } else if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
            dialog.removeData('tripId');
            allPositions = tracking.data.history.positionsByAssetId[asset.Id].Positions;
        } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
            dialog.removeData('tripId');
            allPositions = tracking.data.sharedView.positionsByAssetId[asset.Id].Positions;
        }

        var endIndex = 0;
        var fromIndex = allPositions.length - 1;
        if (fromId !== undefined && fromId !== null) {
            var from = _.find(allPositions, { Id: fromId });
            if (from !== undefined) {
                fromIndex = _.indexOf(allPositions, from);
            }
        }
        if (toId !== undefined && toId !== null) {
            var to = _.find(allPositions, { Id: toId });
            if (to !== undefined) {
                endIndex = _.indexOf(allPositions, to);
            }
        }

        // swap indexes if user chose a from after the to
        if (endIndex > fromIndex) {
            var fromIndexBefore = fromIndex;
            fromIndex = endIndex;
            endIndex = fromIndexBefore;
        }

        var historyPositions = allPositions.slice(endIndex, fromIndex + 1);

        var distance = 0;
        for (var i = endIndex; i <= fromIndex; i++) {
            if (i + 1 <= fromIndex) {
                distance += L.latLng(allPositions[i].Lat, allPositions[i].Lng).distanceTo(L.latLng(allPositions[i + 1].Lat, allPositions[i + 1].Lng));
            }
        }

        var begin = allPositions[fromIndex];
        var end = allPositions[endIndex];
        var beginTime = moment.unix(begin.Epoch);
        var endTime = moment.unix(end.Epoch);
        var duration = moment.duration(endTime.diff(beginTime));
        var durationDays = Math.floor(duration.asDays());
        var durationHours = ('0' + Math.floor(duration.asHours() % 24)).slice(-2);
        var durationDisplay = (durationDays > 0 ? durationDays + '.' : '') + durationHours + moment.utc(duration.asMilliseconds()).format(':mm:ss');
        var averageSpeed = duration.asSeconds() > 0 ? convertSpeedToPreference(distance / duration.asSeconds()) : 0;
        var maxSpeed = convertSpeedToPreference(_.max(_.map(historyPositions, 'Speed')));

        var beginTimeElements = [ text(begin.Time) ];
        if (begin.IsAcc === false) {
            beginTimeElements = [ el('span.inaccurate', begin.Time), el('sup', { title: tracking.strings.TIME_INACCURATE}, '*') ];
        }

        var beginAddress = includeRowIfNotNull(tracking.strings.ADDRESS, begin.Address, { style: { verticalAlign: 'top' }});
        var beginLatLng = includeRowIfNotNull(tracking.strings.LAT_LNG, el('a.location', { href: '#', dataset: { marker: begin.Id } }, convertToLatLngPreference(begin.DisplayLat, begin.DisplayLng, begin.Grid)));

        var endTimeElements = [ text(end.Time) ];
        if (end.IsAcc === false) {
            beginTimeElements = [el('span.inaccurate', end.Time), el('sup', { title: tracking.strings.TIME_INACCURATE }, '*')];
        }

        var endAddress = includeRowIfNotNull(tracking.strings.ADDRESS, end.Address, { style: { verticalAlign: 'top' } });
        var endLatLng = includeRowIfNotNull(tracking.strings.LAT_LNG, el('a.location', { href: '#', dataset: { marker: end.Id } }, convertToLatLngPreference(end.DisplayLat, end.DisplayLng, end.Grid)));

        var options = [];
        _.each(allPositions, function (position) {
            options.push(el('option', { value: position.Id }, position.Time));
        });
        var positionSelect = el('select.form-control.time-picker', options);

        var positionEndSelect = positionSelect.cloneNode(true);
        var beginOption = positionSelect.querySelector('option[value="' + begin.Id + '"]');
        if (beginOption != null) {
            beginOption.selected = true;
        }
        var endOption = positionEndSelect.querySelector('option[value="' + end.Id + '"]');
        if (endOption != null) {
            endOption.selected = true;
        }

        var beginHeader = el('div#positions-begin-header.input-group', el('div.input-group-prepend', el('span.input-group-text', tracking.strings.BEGIN)));
        var endHeader = el('div#positions-end-header.input-group', el('div.input-group-prepend', el('span.input-group-text', tracking.strings.END)));

        var beginCard = el('div.card.mb-2', [
            el('div.card-header.p-0.d-flex.align-items-center', beginHeader),
            el('div.card-body.p-2', el('table', el('tbody', [beginAddress,beginLatLng])))
        ]);
        var endCard = el('div.card.mb-2', [
            el('div.card-header.p-0.d-flex.align-items-center', endHeader),
            el('div.card-body.p-2', el('table', el('tbody', [endAddress, endLatLng])))
        ]);
        var detailsCard = el('div.card.mb-2', [
            el('div.card-header.p-1.pl-2.pr-2.d-flex.align-items-center', tracking.strings.DETAILS),
            el('div.card-body.p-2', el('table', el('tbody', [
                includeRowIfNotNull(tracking.strings.POSITIONS, historyPositions.length),
                includeRowIfNotNull(tracking.strings.DISTANCE, convertFromMetresToUserDistancePreference(distance)),
                includeRowIfNotNull(tracking.strings.AVERAGE_SPEED, averageSpeed),
                includeRowIfNotNull(tracking.strings.MAX_SPEED, maxSpeed),
                includeRowIfNotNull(tracking.strings.DURATION, durationDisplay)
            ])))
        ]);
        var content = el('div.markercontent', [
            beginCard,
            endCard,
            detailsCard
        ]);

        setChildren(dialog[0], content);

        document.getElementById('positions-begin-header').appendChild(positionSelect);
        document.getElementById('positions-end-header').appendChild(positionEndSelect);

        if (trip !== undefined && trip !== null) {
            dialog.dialog('option', 'title', createDialogTitleFragment(trip.Name, asset.Name));
        } else {
            dialog.dialog('option', 'title', createDialogTitleFragment(asset.Name, (tracking.options.hideDeviceName ? null : asset.DeviceName)));
        }
        var dialogTitleBar = $('div.ui-dialog-titlebar', dialog.parent());
        var titleIcon = 'url(' + createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false, null, false, false) + ')';
        dialogTitleBar[0].style.backgroundImage = titleIcon;

        // quick actions
        var historyQuickActions = document.getElementById('position-history-dialog-actions-list');
        var actionAssetOptions = historyQuickActions.querySelector('a[data-action="path-asset-options"]');
        var actionHideAsset = historyQuickActions.querySelector('a[data-action="path-asset-hide"]');
        var actionHideTrip = historyQuickActions.querySelector('a[data-action="path-trip-hide"]');
        actionAssetOptions.setAttribute('data-asset-id', asset.Id);
        actionHideAsset.setAttribute('data-asset-id', asset.Id);
        var actionTripOptions = historyQuickActions.querySelector('a[data-action="path-trip-options"]');
        if (trip !== undefined && trip !== null) {
            actionTripOptions.classList.remove('disabled');
            actionHideTrip.classList.remove('disabled');
            actionTripOptions.setAttribute('data-trip-id', trip.Id);
            actionHideTrip.setAttribute('data-trip-id', trip.Id);
            actionHideTrip.setAttribute('data-journey-id', trip.JourneyId);
            actionHideAsset.classList.remove('disabled');
        } else {
            actionHideAsset.classList.add('disabled');
            actionHideTrip.classList.add('disabled');
            actionTripOptions.classList.add('disabled');
        }
        var actionHistory = historyQuickActions.querySelector('a[data-action="path-history"]');
        actionHistory.setAttribute('data-from', begin.Time);
        actionHistory.setAttribute('data-to', end.Time);
        actionHistory.setAttribute('data-asset-id', asset.Id);

        if (e !== undefined) {
            mouseEvent = e.originalEvent;
            mouseEvent.preventDefault = true;
            // position dialog if not already opened
            if (!dialog.dialog('isOpen')) {
                dialog.dialog('option', 'position',
                    {
                        of: mouseEvent,
                        my: 'left+15 center',
                        at: 'right center',
                        collision: 'flipfit',
                        within: tracking.data.domNodes.map,
                        using: function (param1, param2) {
                                $(this).css(param1);
                        }
                    }
                );
            } else {
                //$('#position-history-dialog').closest('.ui-dialog').position({ of: mouseEvent, my: 'left+15 center', at: 'right center' });
            }
        }
        dialog.dialog('open');
    }

    function createPolylineFromLinePaths(linePath, asset, trip, dashArray) {
        var lineColor = convertNamedColorToHex(asset.Color);
        var sequentialLatLngs = [];
        for (var i = 0; i < linePath.length; i++) {
            var sequentialSection = [];
            for (var j = 0; j < linePath[i].length; j++) {
                sequentialSection.push(linePath[i][j].latLng);
            }
            sequentialLatLngs.push(sequentialSection);
        }

        var line = L.polyline(sequentialLatLngs, {
            weight: 3,
            color: lineColor,
            opacity: 0.75,
            dashArray: dashArray
        });
        line.assetId = asset.Id;
        line.tripId = (trip !== undefined && trip !== null) ? trip.Id : undefined;
        line.on('click', function (e) {
            var asset = findAssetById(this.assetId);
            if (asset == null) {
                return;
            }
            var trip = findTripById(this.tripId);
            createPositionHistorySummary(asset, trip, undefined, undefined, e);
        });
        return line;
    }

    function snapLinePathsToRoads(target, linePath) {
        // this function will queue and update target's mapLine with the snapped polylines
        // directions service only handles 10 waypoints at a time
        // take sections of 10 at a time from the positions
        // must tie sections together as well (8 waypoints max)
        var priorEndingPosition = null;
        var routePositions = [];
        for (var i = 0; i < linePath.length; i++) {
            var section = linePath[i];
            var positionPoints = section.reverse();
            var isValidRoute = (section.length > 1);

            if (isValidRoute) {
                tracking.log("Creating directions route from " + positionPoints.length + " positions.");
                // todo: never leave one position leftover
                for (var j = 0; j < positionPoints.length; j += 15) {
                    //  construct the routes and QUEUE them up due to processing limits
                    var startIndex = j;
                    var endIndex = (positionPoints.length > (j + 14) ? (j + 14) : positionPoints.length);
                    routePositions = positionPoints.slice(startIndex, endIndex + 1);
                    if (priorEndingPosition != null) {
                        // add prior ending position as first position (origin)
                        // in order to have a continuous, connected path
                        routePositions.unshift(priorEndingPosition);
                    }
                    var originalRoutePositions = routePositions.slice();
                    var origin = routePositions.shift();
                    var destination = routePositions.pop();
                    var routeWaypoints = [];
                    for (var k = 0; k < routePositions.length; k++) {
                        routeWaypoints.push({
                            location: routePositions[k]
                        });
                    }
                    var route = {
                        origin: origin,
                        destination: destination,
                        waypoints: routeWaypoints
                    };
                    priorEndingPosition = destination;
                    var queueItem = {
                        target: target,
                        points: originalRoutePositions,
                        route: route
                    };
                    addSnapToRoadsRequestToQueue(queueItem);
                }
            }
        }
    }

    //function createPositionLinesForAsset(asset, positions) {
    //    if (!asset.DrawLinesBetweenPositions) {
    //        return;
    //    }

    //    var linePaths = createLinePathsFromPositions(positions);
    //    var line = createPolylineFromLinePaths(linePaths, asset);
    //    tracking.data.history.mapLinesByAssetId[asset.Id] = line;

    //    if (asset.SnapLinesToRoads && tracking.options.enabledFeatures.indexOf('UI_SNAP_LINES_TO_ROADS') !== -1) {
    //        // kind of hacky to set the first position of the line this way
    //        // as the first position may not be here?
    //        if (linePaths.length > 0 && linePaths[0].length > 0) {
    //            tracking.data.history.mapLinesByAssetId[asset.Id].setLatLngs(linePaths[0][0].latLng); // what problem is this hack fixing?
    //            snapLinePathsToRoads(tracking.data.history.mapLinesByAssetId[asset.Id], linePaths);
    //        } else {
    //            tracking.data.history.mapLinesByAssetId[asset.Id].setLatLngs([]);
    //        }
    //    }
    //    if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
    //        addItemToMap(line);
    //    }
    //}

    function createPositionLinesForAsset(asset, positions, viewMode) {
        if (!asset.DrawLinesBetweenPositions) {
            return;
        }

        console.log('createPositionLinesForAsset');
        var dataSource = tracking.data.history;
        if (viewMode === tracking.viewModes.SHARED_VIEW) {
            dataSource = tracking.data.sharedView;
        } else {
            viewMode = tracking.viewModes.NORMAL;
        }

        var linePaths = createLinePathsFromPositions(positions);
        var line = createPolylineFromLinePaths(linePaths, asset);
        dataSource.mapLinesByAssetId[asset.Id] = line;

        if (asset.SnapLinesToRoads && tracking.options.enabledFeatures.indexOf('UI_SNAP_LINES_TO_ROADS') !== -1) {
            // kind of hacky to set the first position of the line this way
            // as the first position may not be here?
            if (linePaths.length > 0 && linePaths[0].length > 0) {
                dataSource.mapLinesByAssetId[asset.Id].setLatLngs(linePaths[0][0].latLng); // what problem is this hack fixing?
                snapLinePathsToRoads(dataSource.mapLinesByAssetId[asset.Id], linePaths);
            } else {
                dataSource.mapLinesByAssetId[asset.Id].setLatLngs([]);
            }
        }
        addItemToMap(line, null, viewMode);
    }

    //function createPositionLinesForSharedViewAsset(asset, positions) {
    //    if (!asset.DrawLinesBetweenPositions) {
    //        return;
    //    }

    //    var linePaths = createLinePathsFromPositions(positions);
    //    var line = createPolylineFromLinePaths(linePaths, asset);
    //    tracking.data.sharedView.mapLinesByAssetId[asset.Id] = line;

    //    if (asset.SnapLinesToRoads && tracking.options.enabledFeatures.indexOf('UI_SNAP_LINES_TO_ROADS') !== -1) {
    //        // kind of hacky to set the first position of the line this way
    //        // as the first position may not be here?
    //        if (linePaths.length > 0 && linePaths[0].length > 0) {
    //            tracking.data.sharedView.mapLinesByAssetId[asset.Id].setLatLngs(linePaths[0][0].latLng); // what problem is this hack fixing?
    //            snapLinePathsToRoads(tracking.data.sharedView.mapLinesByAssetId[asset.Id], linePaths);
    //        } else {
    //            tracking.data.sharedView.mapLinesByAssetId[asset.Id].setLatLngs([]);
    //        }
    //    }
    //    addItemToMap(line, null, tracking.viewModes.SHARED_VIEW);
    //}

    function createPositionLinesForTrip(trip, asset, positions) {
        // still draw the lines even if the preference is not set if we're only including start/end positions
        if (!asset.DrawLinesBetweenPositions && trip.IncludeAllPositions) {
            return;
        }

        var linePaths = createLinePathsFromPositions(positions);
        var line = createPolylineFromLinePaths(linePaths, asset, trip, '4 8');
        tracking.data.trips.mapLinesByTripId[trip.Id] = line;
        if (asset.SnapLinesToRoads && tracking.options.enabledFeatures.indexOf('UI_SNAP_LINES_TO_ROADS') !== -1) {
            // kind of hacky to set the first position of the line this way
            // as the first position may not be here?
            tracking.data.trips.mapLinesByTripId[trip.Id].setLatLngs(linePaths[0][0].latLng); // what problem is this hack fixing?
            snapLinePathsToRoads(tracking.data.trips.mapLinesByTripId[trip.Id], linePaths);
        }
        addItemToMap(line);
    }

    function updateAssetPositionLines(asset, clearExisting, viewMode) {
        if (viewMode === undefined || viewMode === null) {
            viewMode = tracking.viewModes.NORMAL;
        }

        var assetJourneys = _.filter(tracking.data.journeys, function(item) { return item.AssetId === asset.Id; });

        // asset preference for drawn lines may have been updated
        if (!asset.DrawLinesBetweenPositions || clearExisting) {
            // clear any lines drawn for the asset
            if (viewMode === tracking.viewModes.NORMAL) {
                if (tracking.data.live.mapLinesByAssetId[asset.Id] !== undefined) {
                    removeItemFromMap(tracking.data.live.mapLinesByAssetId[asset.Id]);
                    delete tracking.data.live.mapLinesByAssetId[asset.Id];
                }
                if (tracking.data.history.mapLinesByAssetId[asset.Id] !== undefined) {
                    removeItemFromMap(tracking.data.history.mapLinesByAssetId[asset.Id]);
                    delete tracking.data.history.mapLinesByAssetId[asset.Id];
                }
                _.each(assetJourneys, function (journey) {
                    _.each(journey.Trips, function (trip) {
                        if (tracking.data.trips.mapLinesByTripId[trip.Id] !== undefined) {
                            removeItemFromMap(tracking.data.trips.mapLinesByTripId[trip.Id]);
                            delete tracking.data.trips.mapLinesByTripId[trip.Id];
                        }
                    });
                });
            } else if (viewMode === tracking.viewModes.SHARED_VIEW && tracking.data.sharedView.mapLinesByAssetId[asset.Id] !== undefined) {
                removeItemFromMap(tracking.data.sharedView.mapLinesByAssetId[asset.Id], null, tracking.viewModes.SHARED_VIEW);
                delete tracking.data.sharedView.mapLinesByAssetId[asset.Id];
            }
        }
        
        if (asset.DrawLinesBetweenPositions) {
            // draw new lines if preference has been enabled
            if (viewMode === tracking.viewModes.NORMAL) {
                if (tracking.data.history.mapLinesByAssetId[asset.Id] && tracking.data.history.positionsByAssetId[asset.Id] !== undefined) {
                    var assetVisiblePositions = _.filter(tracking.data.history.positionsByAssetId[asset.Id].Positions, function(item) { return !item.IsHidden; });
                    createPositionLinesForAsset(asset, assetVisiblePositions, tracking.viewModes.NORMAL);
                }

                _.each(assetJourneys, function (journey) {
                    _.each(journey.Trips, function (trip) {
                        if (tracking.data.trips.mapLinesByTripId[trip.Id] === undefined && tracking.data.trips.positionsByTripId[trip.Id] !== undefined) {
                            var tripVisiblePositions = _.filter(tracking.data.trips.positionsByTripId[trip.Id].Positions, function (item) { return !item.IsHidden; });
                            createPositionLinesForTrip(trip, asset, tripVisiblePositions);
                        }
                    });
                });
            } else if (viewMode === tracking.viewModes.SHARED_VIEW && tracking.data.sharedView.positionsByAssetId[asset.Id] !== undefined) {
                var assetVisiblePositions = _.filter(tracking.data.sharedView.positionsByAssetId[asset.Id].Positions, function (item) { return !item.IsHidden; });
                createPositionLinesForAsset(asset, assetVisiblePositions, tracking.viewModes.SHARED_VIEW);
            }
        }
    }

    function showFilterResults(num, type) {
        var results = tracking.data.domNodes.filter.assetResults;
        var resultNone = tracking.strings.FILTER_NO_ASSETS;
        var resultOne = tracking.strings.FILTER_SINGLE_ASSET;
        var resultMany = tracking.strings.FILTER_ASSETS;
        switch (type) {
            case 'fences':
                results = tracking.data.domNodes.filter.fenceResults;
                resultNone = tracking.strings.FILTER_NO_GEOFENCES;
                resultOne = tracking.strings.FILTER_SINGLE_GEOFENCE;
                resultMany = tracking.strings.FILTER_GEOFENCES;
                break;
            case 'places':
                results = tracking.data.domNodes.filter.placeResults;
                resultNone = tracking.strings.FILTER_NO_PLACES;
                resultOne = tracking.strings.FILTER_SINGLE_PLACE;
                resultMany = tracking.strings.FILTER_PLACES;
                break;
            case 'shared-views':
                results = tracking.data.domNodes.filter.sharedViewResults;
                resultNone = tracking.strings.FILTER_NO_SHARED_VIEWS;
                resultOne = tracking.strings.FILTER_SINGLE_SHARED_VIEW;
                resultMany = tracking.strings.FILTER_SHARED_VIEWS;
                break;
        }

        results.classList.add('is-visible');

        var text = results.querySelector('span');
        if (num === 0) {
            text.textContent = resultNone;
        } else if (num === 1) {
            text.textContent = resultOne;
        } else {
            text.textContent = resultMany.replace('{0}', num);
        }
    }

    function getHistoryAssetsAndPositionsShown(viewMode) {
        viewMode = viewMode !== undefined ? viewMode : tracking.viewModes.NORMAL;
        var visibleAssetIds = getVisibleAssetIds(viewMode);
        var visiblePositions = 0;
        var dataSource = viewMode === tracking.viewModes.SHARED_VIEW ? tracking.data.sharedView : tracking.data.history;
        var visibleAssetHistory = _.filter(dataSource.normalizedPositionsByAssetId, function(item, id) { return _.includes(visibleAssetIds, parseInt(id)); });
        //var visibleAssetHistory = _.filter(dataSource.normalizedPositions, function (assetPositions, index, list) {
        //    return _.includes(visibleAssetIds, assetPositions.Id);
        //});

        var visibleAssetsWithPositions = visibleAssetHistory.length;
        _.each(visibleAssetHistory, function (assetHistory) {
            //visiblePositions += assetHistory.Positions.length;
            visiblePositions += assetHistory.length;
        });

        document.getElementById('live-noresults').classList.remove('is-visible');
        var noResults = document.getElementById('history-noresults');
        if (visibleAssetsWithPositions > 0 && visiblePositions > 0) {
            var msg_assetsPositions = tracking.strings.MSG_ASSETS_AND_POSITIONS;
            msg_assetsPositions = msg_assetsPositions.replace('{0}', visibleAssetsWithPositions); // num assets
            msg_assetsPositions = msg_assetsPositions.replace('{2}', (visibleAssetsWithPositions > 1 ? 's' : '')); // assets plural
            msg_assetsPositions = msg_assetsPositions.replace('{1}', visiblePositions); // num positions
            msg_assetsPositions = msg_assetsPositions.replace('{3}', (visiblePositions > 1 ? 's' : '')); // positions plural
            tracking.data.domNodes.mapTools.visibleSummary.textContent = msg_assetsPositions;

            tracking.data.domNodes.mapMode.visibleAssetsHistory.textContent = visibleAssetsWithPositions;
            var visibleText = visiblePositions;
            if (dataSource.limitedData !== null) {
                visibleText += ' / ' + dataSource.limitedData.counts.Positions;
            }
            tracking.data.domNodes.mapMode.visiblePositionsHistory.textContent = visibleText;

            noResults.classList.remove('is-visible');
        } else {
            tracking.data.domNodes.mapTools.visibleSummary.textContent = '';
            tracking.data.domNodes.mapMode.visibleAssetsHistory.textContent = tracking.strings.GROUP_NONE;
            tracking.data.domNodes.mapMode.visiblePositionsHistory.textContent = tracking.strings.GROUP_NONE;
            if (tracking.state.hasQueriedHistory) {
                noResults.classList.add('is-visible');
            }
        }
    }

    function getLiveAssetsShown() {
        var visibleAssetsWithPositions = _.filter(tracking.data.live.latestPositionsByAssetId, function (item) {
            return _.includes(tracking.data.visible.assets, item.AssetId);
        });

        document.getElementById('history-noresults').classList.remove('is-visible');
        var noResults = document.getElementById('live-noresults');
        if (visibleAssetsWithPositions.length > 0) {
            var msg_liveAssets = tracking.strings.MSG_ASSETS_SHOWN_LIVE;
            msg_liveAssets = msg_liveAssets.replace('{0}', visibleAssetsWithPositions.length); // num assets
            msg_liveAssets = msg_liveAssets.replace('{1}', (visibleAssetsWithPositions.length > 1 ? 's' : '')); // assets plural
            tracking.data.domNodes.mapTools.visibleSummary.textContent = msg_liveAssets;
            tracking.data.domNodes.mapMode.visibleAssetsLive.textContent = visibleAssetsWithPositions.length;
            noResults.classList.remove('is-visible');
        } else {
            tracking.data.domNodes.mapTools.visibleSummary.textContent = '';
            tracking.data.domNodes.mapMode.visibleAssetsLive.textContent = tracking.strings.GROUP_NONE;
            if (tracking.state.hasQueriedLive && tracking.data.visible.assets.length > 0) {
                noResults.classList.add('is-visible');
            }
        }
    }

    function getLatLngFromPosition(position) {
        return L.latLng(position.Lat, position.Lng);
    }

    function deleteAsset(asset) {
        if (tracking.data.domNodes.infoDialogs.mapItemInformation.data !== undefined
            && tracking.data.domNodes.infoDialogs.mapItemInformation.data !== null
            && tracking.data.domNodes.infoDialogs.mapItemInformation.data.assetId === asset.Id
            && $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('isOpen')) {
            $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('close');
        }

        if ($(tracking.data.domNodes.infoDialogs.positionHistory).data('assetId') == asset.Id) {
            $(tracking.data.domNodes.infoDialogs.positionHistory).dialog('close');
        }

        _.each(asset.GroupIds, function (groupId) {
            removeAssetFromGroup(asset, groupId);
        });
        removeAssetFromGroup(asset, 'all-assets');

        var panel = tracking.data.domNodes.panels.secondary;
        if (panel.getAttribute('data-group-for') === 'assets' && parseInt(panel.getAttribute('data-item-id')) === asset.Id) {
            closeSecondaryPanel();
        }

        // remove positions and variable definitions

        //remove where tracking.data.assetUsers[i].AssetId == id
        if (tracking.data.assetUsers != null) {
            for (var i = tracking.data.assetUsers.length - 1; i >= 0; i -= 1) {
                if (tracking.data.assetUsers[i].AssetId == asset.Id) {
                    tracking.data.assetUsers.splice(i, 1);
                }
            }
        }

        //remove where tracking.data.history.positions[i].Id == id
        if (tracking.data.history.positions != null) {
            for (var i = tracking.data.history.positions.length - 1; i >= 0; i -= 1) {
                if (tracking.data.history.positions[i].Id == asset.Id)
                    tracking.data.history.positions.splice(i, 1);
            }
        }

        tracking.data.history.normalizedPositions = _.reject(tracking.data.history.normalizedPositions, function (item) { return item.AssetId === asset.Id; });
        delete tracking.data.history.assetIdsWithResults[asset.Id];

        //remove where tracking.data.live.positions[i].AssetId == id
        if (tracking.data.live.positions != null) {
            for (var i = tracking.data.live.positions.length - 1; i >= 0; i -= 1) {
                if (tracking.data.live.positions[i].AssetId == asset.Id)
                    tracking.data.live.positions.splice(i, 1);
            }
        }

        tracking.data.live.normalizedPositions = _.reject(tracking.data.live.normalizedPositions, function (item) { return item.AssetId === asset.Id; });

        if (tracking.data.live.latestPositions != null) {
            for (var i = tracking.data.live.latestPositions.length - 1; i >= 0; i -= 1) {
                if (tracking.data.live.latestPositions[i].AssetId == asset.Id)
                    tracking.data.live.latestPositions.splice(i, 1);
            }
        }

        //remove where tracking.data.markers.assetId == id ... setMap(null)
        if (tracking.data.live.markers !== null) {
            for (var i = tracking.data.live.markers.length - 1; i >= 0; i -= 1) {
                var assetId = tracking.data.live.markers[i].data.assetId;
                if (assetId == asset.Id) {
                    removeItemFromMap(tracking.data.live.markers[i]);
                    tracking.data.live.markers.splice(i, 1);
                }
            }
        }
        if (tracking.data.history.markers !== null) {
            for (var i = tracking.data.history.markers.length - 1; i >= 0; i -= 1) {
                var assetId = tracking.data.history.markers[i].data.assetId;
                if (assetId == asset.Id) {
                    removeItemFromMap(tracking.data.history.markers[i]);
                    tracking.data.history.markers.splice(i, 1);
                }
            }
        }

        if (tracking.data.domNodes.assets[asset.Id] !== undefined) {
            delete tracking.data.domNodes.assets[asset.Id];
        }

        // remove events and messages from live and history
        var assetEvents = _.filter(tracking.data.live.events, function (evt) { return evt.AssetId === asset.Id });
        var assetEventIds = _.map(assetEvents, 'Id');
        tracking.data.live.eventIds = _.without(tracking.data.live.eventIds, assetEventIds);
        _.each(assetEventIds, function (eventId) {
            delete tracking.data.live.normalizedEventIds[eventId];
            delete tracking.data.history.normalizedEventIds[eventId];
        });
        tracking.data.live.events = _.filter(tracking.data.live.events, function (evt) { return evt.AssetId !== asset.Id });
        tracking.data.live.normalizedEvents = _.reject(tracking.data.live.normalizedEvents, function(item) { return item.AssetId === asset.Id; });
        tracking.data.history.events = _.filter(tracking.data.history.events, function (evt) { return evt.AssetId !== asset.Id });
        tracking.data.history.normalizedEvents = _.reject(tracking.data.history.normalizedEvents, function (item) { return item.AssetId === asset.Id; });
        tracking.data.live.messages = _.filter(tracking.data.live.messages, function (item) { return item.AssetId !== asset.Id });
        tracking.data.history.messages = _.filter(tracking.data.history.messages, function (item) { return item.AssetId !== asset.Id });
        tracking.data.history.normalizedMessages = _.filter(tracking.data.history.normalizedMessages, function (item) { return item.AssetId !== asset.Id });

        if (tracking.data.visible.assets != null) {
            var index = _.indexOf(tracking.data.visible.assets, asset.Id);
            if (index !== -1) {
                tracking.data.visible.assets.splice(index, 1);
            }
        }

        if (tracking.data.assets != null) {
            for (var i = tracking.data.assets.length - 1; i >= 0; i -= 1) {
                if (tracking.data.assets[i].Id == asset.Id)
                    tracking.data.assets.splice(i, 1);
            }
        }

        tracking.data.assetsById = _.keyBy(tracking.data.assets, 'Id');
        if (tracking.data.live.latestPositionsByAssetId[asset.Id] !== undefined) {
            delete tracking.data.live.latestPositionsByAssetId[asset.Id];
        }
        if (tracking.data.live.notificationTimesByAssetId[asset.Id] !== undefined) {
            delete tracking.data.live.notificationTimesByAssetId[asset.Id];
        }
        tracking.data.live.positionsByAssetId = _.groupBy(tracking.data.live.positions, 'AssetId');
        tracking.data.live.normalizedPositionsByAssetId = _.groupBy(tracking.data.live.normalizedPositions, 'AssetId');
        tracking.data.live.normalizedEventsByAssetId = _.groupBy(tracking.data.live.normalizedEvents, 'AssetId');
        tracking.data.live.messagesByAssetId = _.groupBy(tracking.data.live.messages, 'AssetId');
        tracking.data.live.markersByAssetId = _.groupBy(tracking.data.live.markers, function (marker) { return marker.data.assetId; });
        tracking.data.live.markersByPositionId = _.keyBy(tracking.data.live.markers, function (marker) { return marker.data.location.Id; });
        tracking.data.history.positionsByAssetId = _.keyBy(tracking.data.history.positions, 'Id');
        tracking.data.history.normalizedPositionsByAssetId = _.groupBy(tracking.data.history.normalizedPositions, 'AssetId');
        tracking.data.history.normalizedEventsByAssetId = _.groupBy(tracking.data.history.normalizedEvents, 'AssetId');
        tracking.data.history.messagesByAssetId = _.groupBy(tracking.data.history.messages, 'AssetId');
        tracking.data.history.normalizedMessagesByAssetId = _.groupBy(tracking.data.history.normalizedMessages, 'AssetId');
        tracking.data.history.markersByAssetId = _.groupBy(tracking.data.history.markers, function (marker) { return marker.data.assetId; });
        tracking.data.history.markersByPositionId = _.keyBy(tracking.data.history.markers, function (marker) { return marker.data.assetId; });

        updateActiveAssetInformation(tracking.viewModes.NORMAL);

        if (tracking.data.assets.length === 0) {
            document.getElementById('filter-assets').querySelector('.filter-box').classList.remove('is-visible');
        }

        indexAssetsForSearch();
    }

    function clearAssetPositions(asset) {
        // remove ui elements
        var li = $j('#assets-all').find('li.asset-' + asset.Id);
        $j('div.locations', li).remove();

        //remove where tracking.data.history.positions[i].Id == id
        if (tracking.data.history.positions != null) {
            for (var i = tracking.data.history.positions.length - 1; i >= 0; i -= 1) {
                if (tracking.data.history.positions[i].Id == asset.Id)
                    tracking.data.history.positions.splice(i, 1);
            }
        }

        tracking.data.history.normalizedPositions = _.reject(tracking.data.history.normalizedPositions, function (item) { return item.AssetId === asset.Id; });
        delete tracking.data.history.assetIdsWithResults[asset.Id];

        //remove where tracking.data.live.positions[i].AssetId == id
        if (tracking.data.live.positions != null) {
            for (var i = tracking.data.live.positions.length - 1; i >= 0; i -= 1) {
                if (tracking.data.live.positions[i].AssetId == asset.Id)
                    tracking.data.live.positions.splice(i, 1);
            }
        }

        tracking.data.live.normalizedPositions = _.reject(tracking.data.live.normalizedPositions, function(item) { return item.AssetId === asset.Id; });
        
        if (tracking.data.live.latestPositions != null) {
            for (var i = tracking.data.live.latestPositions.length - 1; i >= 0; i -= 1) {
                if (tracking.data.live.latestPositions[i].AssetId == asset.Id)
                    tracking.data.live.latestPositions.splice(i, 1);
            }
        }

        //remove where tracking.data.markers.assetId == id ... setMap(null)
        if (tracking.data.live.markers !== null) {
            for (var i = tracking.data.live.markers.length - 1; i >= 0; i -= 1) {
                var assetId = tracking.data.live.markers[i].data.assetId;
                if (assetId == asset.Id) {
                    removeItemFromMap(tracking.data.live.markers[i]);
                    tracking.data.live.markers.splice(i, 1);
                }
            }
        }
        if (tracking.data.history.markers !== null) {
            for (var i = tracking.data.history.markers.length - 1; i >= 0; i -= 1) {
                var assetId = tracking.data.history.markers[i].data.assetId;
                if (assetId == asset.Id) {
                    removeItemFromMap(tracking.data.history.markers[i]);
                    tracking.data.history.markers.splice(i, 1);
                }
            }
        }
        if (tracking.data.live.latestPositionsByAssetId[asset.Id] !== undefined) {
            delete tracking.data.live.latestPositionsByAssetId[asset.Id];
        }
        if (tracking.data.live.notificationTimesByAssetId[asset.Id] !== undefined) {
            delete tracking.data.live.notificationTimesByAssetId[asset.Id];
        }
        tracking.data.live.positionsByAssetId = _.groupBy(tracking.data.live.positions, 'AssetId');
        tracking.data.live.normalizedPositionsByAssetId = _.groupBy(tracking.data.live.normalizedPositions, 'AssetId');
        tracking.data.live.markersByAssetId = _.groupBy(tracking.data.live.markers, function (marker) { return marker.data.assetId; });
        tracking.data.live.markersByPositionId = _.keyBy(tracking.data.live.markers, function(marker) { return marker.data.location.Id; });
        tracking.data.history.positionsByAssetId = _.groupBy(tracking.data.history.positions, 'Id');
        tracking.data.history.normalizedPositionsByAssetId = _.groupBy(tracking.data.history.normalizedPositions, 'AssetId');
        tracking.data.history.markersByAssetId = _.groupBy(tracking.data.history.markers, function (marker) { return marker.data.assetId; });
        tracking.data.history.markersByPositionId = _.keyBy(tracking.data.history.markers, function (marker) { return marker.data.location.Id; });
        // TODO trips for asset should be deleted here as well

        updateActiveAssetInformation(tracking.viewModes.NORMAL);
    }

    function deleteTrip(trip) {
        tracking.data.domNodes.trips[trip.Id].parentNode.removeChild(tracking.data.domNodes.trips[trip.Id]);
        var panel = tracking.data.domNodes.panels.secondary;
        if (panel.getAttribute('data-group-for') === 'trips' && parseInt(panel.getAttribute('data-item-id')) === trip.Id) {
            closeSecondaryPanel();
        }

        _.each(tracking.data.trips.markersByTripId[trip.Id], function (marker) {
            removeItemFromMap(marker);
        });

        delete tracking.data.trips.markersByTripId[trip.Id];
        delete tracking.data.trips.eventsByTripId[trip.Id];
        delete tracking.data.trips.positionsByTripId[trip.Id];
        delete tracking.data.trips.messagesByTripId[trip.Id];
        delete tracking.data.trips.messageCountsByTripId[trip.Id];

        var journey = findJourneyById(trip.JourneyId);
        if (journey != null) {
            for (var i = 0; i < journey.Trips.length; i++) {
                if (journey.Trips[i].Id == trip.Id) {
                    journey.Trips.splice(i, 1);
                }
            }
        }

        var groupId = 'journey-' + trip.JourneyId;
        updateGroupVisibilityStatus(groupId);
    }

    function deleteJourney(journey) {
        var groupId = 'journey-' + journey.Id;
        tracking.data.domNodes.groups[groupId].parentNode.removeChild(tracking.data.domNodes.groups[groupId]);
        var panel = tracking.data.domNodes.panels.secondary;
        if (panel.getAttribute('data-group-for') === 'journeys' && parseInt(panel.getAttribute('data-item-id')) === journey.Id) {
            closeSecondaryPanel();
        }

        for (var j = journey.Trips.length - 1; j >= 0; j--) {
            deleteTrip(journey.Trips[j]);
        }

        for (var i = 0; i < tracking.data.journeys.length; i++) {
            if (tracking.data.journeys[i].Id == journey.Id) {
                tracking.data.journeys.splice(i, 1);
                break;
            }
        }
        tracking.data.journeysById = _.keyBy(tracking.data.journeys, 'Id');
        displayPreferencesRemove('expandedGroups', groupId);
        updateGroupVisibilityStatus(groupId);
    }

    function deletePlace(place) {
        if (tracking.data.domNodes.infoDialogs.mapItemInformation.data !== undefined
            && tracking.data.domNodes.infoDialogs.mapItemInformation.data !== null
            && tracking.data.domNodes.infoDialogs.mapItemInformation.data.placeId === place.Id
            && $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('isOpen')) {
            $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('close');
        }

        tracking.data.domNodes.places[place.Id].parentNode.removeChild(tracking.data.domNodes.places[place.Id]);
        var panel = tracking.data.domNodes.panels.secondary;
        if (panel.getAttribute('data-group-for') === 'places' && parseInt(panel.getAttribute('data-item-id')) === place.Id) {
            closeSecondaryPanel();
        }

        if (tracking.data.placeUsers != null) {
            for (var i = tracking.data.placeUsers.length - 1; i >= 0; i -= 1) {
                if (tracking.data.placeUsers[i].PlaceId == place.Id) {
                    tracking.data.placeUsers.splice(i, 1);
                }
            }
        }

        // remove place marker
        if (tracking.data.placeMarkers != null) {
            for (var i = tracking.data.placeMarkers.length - 1; i >= 0; i -= 1) {
                //var placeId = $j.data(tracking.data.placeMarkers[i], 'placeId');
                var placeId = tracking.data.placeMarkers[i].data.placeId;
                if (placeId == place.Id) {
                    removeItemFromMap(tracking.data.placeMarkers[i]);
                    tracking.data.placeMarkers.splice(i, 1);
                }
            }
        }

        // remove from tracking.data.places
        for (var i = 0; i < tracking.data.places.length; i++) {
            if (tracking.data.places[i].Id == place.Id)
                tracking.data.places.splice(i, 1);
        }

        // if last fence, show no places
        if (tracking.data.places.length == 0) {
            $j('#no-all-places').addClass('is-visible');
            document.getElementById('filter-places').querySelector('.filter-box').classList.remove('is-visible');
        }
        updateGroupVisibilityStatus('all-places');
        indexPlacesForSearch();
    }

    function deleteFence(fence) {
        // close dialog/settings for fence if currently open
        var panel = tracking.data.domNodes.panels.secondary;
        if (panel.getAttribute('data-group-for') === 'fences' && panel.getAttribute('data-item-id') === fence.Id) {
            closeSecondaryPanel();
        }

        if (tracking.data.domNodes.infoDialogs.mapItemInformation.data !== undefined
            && tracking.data.domNodes.infoDialogs.mapItemInformation.data !== null
            && tracking.data.domNodes.infoDialogs.mapItemInformation.data.fenceId === fence.Id
            && $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('isOpen')) {
            $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('close');
        }

        // remove fence from UI - currently not grouped
        var fenceNode = tracking.data.domNodes.fences[fence.Id]
        fenceNode.parentNode.removeChild(fenceNode);

        removeFencePaths(fence.Id);

        if (tracking.data.fenceUsers != null) {
            for (var i = tracking.data.fenceUsers.length - 1; i >= 0; i -= 1) {
                if (tracking.data.fenceUsers[i].FenceId == fence.Id) {
                    tracking.data.fenceUsers.splice(i, 1);
                }
            }
        }

        // remove from tracking.data.fences
        for (var i = 0; i < tracking.data.fences.length; i++) {
            if (tracking.data.fences[i].Id == fence.Id)
                tracking.data.fences.splice(i, 1);
        }

        displayPreferencesRemove('hiddenFences', fence.Id);
        updateGroupVisibilityStatus('all-fences');

        // if last fence, show no fences
        if (tracking.data.fences.length == 0) {
            $j('#no-all-fences').addClass('is-visible');
            document.getElementById('filter-fences').querySelector('.filter-box').classList.remove('is-visible');
        }

        indexFencesForSearch();
    }

    function deleteAssetGroup(deleted) {
        // remove group from UI
        var li = $j('#group-' + deleted.Id);
        li.remove();

        // remove group from tracking.data.groups
        for (var i = 0; i < tracking.data.groups.length; i++) {
            if (tracking.data.groups[i].Id == deleted.Id) {
                tracking.data.groups.splice(i, 1);
                break;
            }
        }
        tracking.data.groupsById = _.keyBy(tracking.data.groups, 'Id');

        var modifiedGroupIds = [];
        _.each(tracking.data.groups, function (group) {
            if (_.indexOf(group.GroupIds, deleted.Id)) {
                group.GroupIds = _.without(group.GroupIds, deleted.Id);
                modifiedGroupIds.push(group.Id);
            }
        });

        if (tracking.data.assetGroupUsers != null) {
            for (var i = tracking.data.assetGroupUsers.length - 1; i >= 0; i -= 1) {
                if (tracking.data.assetGroupUsers[i].AssetGroupId == deleted.Id) {
                    tracking.data.assetGroupUsers.splice(i, 1);
                }
            }
        }

        // remove group id from all asset's groupIds listings
        for (var i = 0; i < tracking.data.assets.length; i++) {
            var asset = tracking.data.assets[i];
            for (var j = 0; j < asset.GroupIds.length; j++) {
                if (asset.GroupIds[j] == deleted.Id) {
                    asset.GroupIds.splice(j, 1);
                    break;
                }
            }
        }

        // remove group expanded preference
        displayPreferencesRemove('expandedGroups', deleted.Id);

        // update group status for parent groups
        _.each(modifiedGroupIds, function (groupId) {
            updateGroupVisibilityStatus(groupId);
        });
    }

    function convertDegreesDecimalMinutes(lat, lng) {
        var directionLat = (lat > 0) ? 'N' : 'S';
        var directionLng = (lng > 0) ? 'E' : 'W';
        var degreesLat = Math.floor(Math.abs(lat));
        var degreesLng = Math.floor(Math.abs(lng));
        var minutesLat = ((Math.abs(lat) - Math.abs(degreesLat)) * 60).toFixed(4);
        var minutesLng = ((Math.abs(lng) - Math.abs(degreesLng)) * 60).toFixed(4);
        return degreesLat + '°' + minutesLat + '\'' + directionLat + ' ' + degreesLng + '°' + minutesLng + '\'' + directionLng;
    }

    function convertDecimalDegrees(lat, lng) {
        var originalLat = lat;
        lat = Math.abs(lat);
        var degreesLat = Math.floor(lat);
        var minutesLat = Math.floor((lat - degreesLat) * 60);
        var secondsLat = ((((lat - degreesLat) * 60) - minutesLat) * 60).toFixed(4);
        var directionLat = (originalLat > 0) ? 'N' : 'S';

        var originalLng = lng;
        lng = Math.abs(lng);
        var degreesLng = Math.floor(lng);
        var minutesLng = Math.floor((lng - degreesLng) * 60);
        var secondsLng = ((((lng - degreesLng) * 60) - minutesLng) * 60).toFixed(4);
        var directionLng = (originalLng > 0) ? 'E' : 'W';

        return degreesLat + "°" + minutesLat + "'" + secondsLat + "\"" + directionLat + ' ' + degreesLng + "°" + minutesLng + "'" + secondsLng + "\"" + directionLng;
    }

    function requeryPositionsForAsset(asset) {
        var hadLocationHistory = false;

        var assetHistory = tracking.data.history.normalizedPositionsByAssetId[asset.Id];
        if (assetHistory !== undefined && assetHistory.length > 0) {
            hadLocationHistory = true;

            // remove markers from map
            tracking.data.history.markers = _.without(tracking.data.history.markers, tracking.data.history.markersByAssetId[asset.Id]);
            _.each(tracking.data.history.markersByAssetId[asset.Id], function (item) {
                removeItemFromMap(item);
                delete tracking.data.history.markersByPositionId[item.data.location.Id];
            });
            tracking.data.history.markersByAssetId[asset.Id] = [];

            tracking.data.history.normalizedPositions = _.reject(tracking.data.history.normalizedPositions, function (item) { return item.AssetId === asset.Id; });
            delete tracking.data.history.assetIdsWithResults[asset.Id];
            tracking.data.history.normalizedPositionsByAssetId = _.groupBy(tracking.data.history.normalizedPositions, 'AssetId');

            asset.Positions = null; // TODO remove this reference everywhere
            if (tracking.data.history.mapLinesByAssetId[asset.Id] !== undefined) {
                // remove polyline
                removeItemFromMap(tracking.data.history.mapLinesByAssetId[asset.Id]);
                delete tracking.data.history.mapLinesByAssetId[asset.Id];
            }
            if (tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined) {
                // remove marker cluster
                tracking.data.history.markerClustersByAssetId[asset.Id].clearLayers();
                // TODO delete?
            }
        }

        if (tracking.data.history.positions != null) {
            for (var k = 0; k < tracking.data.history.positions.length; k++) {
                if (tracking.data.history.positions[k].Id === asset.Id) {
                    // remove from position results
                    tracking.data.history.positions = tracking.data.history.positions.splice(k, 1);
                    break;
                }                
            }
        }
        tracking.data.history.positionsByAssetId = _.keyBy(tracking.data.history.positions, 'Id');        

        tracking.throttles.updateLiveAssets();
        if (hadLocationHistory) {
            querySingleAsset(asset.Id);
        }
    }

    function updateMarkersForAsset(asset) {
        if(tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined) {
            tracking.data.history.markerClustersByAssetId[asset.Id].imageData = { assetClass: asset.Class, color: asset.Color, assetId: asset.Id };
            tracking.data.history.markerClustersByAssetId[asset.Id].imagePath = createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false, null, false, false);
        }

        var hexColor = convertNamedColorToHex(asset.Color);
        if (tracking.data.history.mapLinesByAssetId[asset.Id] !== undefined) {
            tracking.data.history.mapLinesByAssetId[asset.Id].setStyle({ color: hexColor });
        }
        if (tracking.data.live.mapLinesByAssetId[asset.Id] !== undefined) {
            tracking.data.live.mapLinesByAssetId[asset.Id].setStyle({ color: hexColor });
        }

        _.each(tracking.data.history.markersByAssetId[asset.Id], function (marker) {
            changeMarkerIcon(marker, null, marker.data.alpha, false);
        });

        _.each(tracking.data.live.markersByAssetId[asset.Id], function (marker) {
            changeMarkerIcon(marker, null, marker.data.alpha, false);
        });

        var journeys = _.filter(tracking.data.journeys, { AssetId: asset.Id });
        _.each(journeys, function (journey) {
            _.each(journey.Trips, function (trip) {
                _.each(tracking.data.trips.markersByTripId[trip.Id], function (marker) {
                    changeMarkerIcon(marker, null, marker.data.alpha, false);
                });
            });
        });
    }

    function updateAssetGroup(updated) {
        // if name/color updated, then update the live values
        // replacing all references for asset's groups
        for (var i = 0; i < tracking.data.groups.length; i++) {
            if (tracking.data.groups[i].Id === updated.Id) {
                var group = tracking.data.groups[i];
                group.Name = updated.Name;
                group.Color = updated.Color;
                group.ColorSorted = convertHexToSortable(group.Color);
            }
        }

        // find li for group and update name/color
        // group could be hidden so search domNodes
        tracking.data.domNodes.groupColors[updated.Id] = updated.Color;

        // may also be open in secondaryPanel, so refresh in case name/color was updated
        if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'groups'
            && tracking.data.domNodes.panels.secondary.getAttribute('data-item-id') === updated.Id) {
            openAssetGroupSettingsPanel(updated);
        }

        var li = tracking.data.domNodes.groups[updated.Id];
        if (li !== undefined) {
            li.style.borderColor = updated.Color;
            var name = li.querySelector('.group-name');
            name.textContent = updated.Name;
        }

        createGroupColorStyles();
        //var li = $j('#group-' + updated.Id).eq(0);
        //if (li != null) {
        //    //li.css('background-color', updated.Color);
        //    li.css('border-color', updated.Color);
        //    $j('a.group span', li).text(updated.Name);
        //}
    }

    function queryVisibleTrips() {
        var deferreds = [];
        _.each(tracking.data.visible.trips, function(tripId) {
            var trip = findTripById(tripId);
            if (trip === null) {
                return;
            }
            var journey = findJourneyById(trip.JourneyId);
            if (journey === null) {
                return;
            }
            deferreds.push(querySingleTrip(journey, trip));
        });
        return deferreds;
    }

    function addFenceAndPlaceMarkersToSharedView(placeIds, fenceIds) {
        var fenceIdLookup = _.keyBy(fenceIds);
        var placeIdLookup = _.keyBy(placeIds);
        var fenceMarkers = _.filter(tracking.data.fenceMarkers, function (item) { return fenceIdLookup[item.data.fenceId] !== undefined; });
        var placeMarkers = _.filter(tracking.data.placeMarkers, function (item) { return placeIdLookup[item.data.placeId] !== undefined; });
        tracking.data.sharedView.fenceMarkers = fenceMarkers;
        tracking.data.sharedView.placeMarkers = placeMarkers;
        _.each(fenceMarkers, function (item) {
            addItemToMap(item, null, tracking.viewModes.SHARED_VIEW);
        });
        _.each(placeMarkers, function (item) {
            addItemToMap(item, null, tracking.viewModes.SHARED_VIEW);
        });
    }

    function cloneSharedView(sharedView) {
        var clonedPreferences = _.clone(sharedView.Preferences);
        var cloned = _.clone(sharedView);
        cloned.Preferences = clonedPreferences;
        return cloned;
    }

    function compareSharedViews(newView, oldView) {
        console.log('compareSharedViews', newView, oldView);
        // first check all properties that would require a re-processing
        var isReprocessing = false;
        var changeMade = false;
        if ((newView.IsMessagingEnabled !== oldView.IsMessagingEnabled)
            || (newView.FromDateEpoch !== oldView.FromDateEpoch)
            || (newView.ToDateEpoch !== oldView.ToDateEpoch)
            || (!_.isEqual(newView.AssetIds, oldView.AssetIds))
            || (!_.isEqual(newView.AssetGroupIds, oldView.AssetGroupIds))
            || (newView.IsTimeframeRelative !== oldView.IsTimeframeRelative)
            || (newView.RelativeTimeframeNumber !== oldView.RelativeTimeframeNumber)
            || (newView.RelativeTimeframeType !== oldView.RelativeTimeframeType)) {
            isReprocessing = true;
            //querySharedViewData(newView, null, false);
            throttledQuerySharedViewData(newView, null, false);
            console.log('change made');
            return;
        }

        // add/remove fences/places
        if (!_.isEqual(newView.FenceIds, oldView.FenceIds) || !_.isEqual(newView.PlaceIds, oldView.PlaceIds)) {
            _.each(tracking.data.sharedView.fenceMarkers, function (item) { removeItemFromMap(item, null, tracking.viewModes.SHARED_VIEW); });
            _.each(tracking.data.sharedView.placeMarkers, function (item) { removeItemFromMap(item, null, tracking.viewModes.SHARED_VIEW); });
            addFenceAndPlaceMarkersToSharedView(newView.PlaceIds, newView.FenceIds);
            changeMade = true;
        }

        // all properties that can just be modified on the view go here
        if (newView.Preferences.MapType !== oldView.Preferences.MapType || newView.Preferences.RemoveRoads !== oldView.Preferences.RemoveRoads) {
            tracking.data.isSatelliteLabelOverlayEnabled = !newView.Preferences.RemoveRoads;
            changeMapType(_.findKey(tracking.data.MAP_TYPES, function (item) { return item === newView.Preferences.MapType; }));
            changeMade = true;
        }

        if (newView.Preferences.PositionConsolidation !== oldView.Preferences.PositionConsolidation) {
            if (!newView.Preferences.PositionConsolidation) {
                // remove clusters and show visible markers
                _.each(tracking.data.sharedView.markerClustersByAssetId, function (item) { item.clearLayers(); });
                _.each(tracking.data.sharedView.markers, function (item) { if (!item.data.isHidden) { addItemToMap(item, null, tracking.viewModes.SHARED_VIEW); } });
            } else {
                // remove visible markers, adding them to appropriate clusters (were they ever created?)
                _.each(tracking.data.sharedView.markersByAssetId, function (item, assetId) {
                    _.each(item, function (marker) {
                        removeItemFromMap(marker, null, tracking.viewModes.SHARED_VIEW);
                    });

                    if (tracking.data.sharedView.markerClustersByAssetId[assetId] === undefined) {
                        var asset = findAssetById(assetId);
                        tracking.data.sharedView.markerClustersByAssetId[assetId] = createMarkerCluster(asset, null, newView.Id);
                    }

                    tracking.data.sharedView.markerClustersByAssetId[assetId].clearLayers();
                    tracking.data.sharedView.markerClustersByAssetId[assetId].addLayers(item);
                    addItemToMap(tracking.data.sharedView.markerClustersByAssetId[assetId], null, tracking.viewModes.SHARED_VIEW);
                });
            }
            changeMade = true;
        }

        if (changeMade) {
            console.log('changes made');
        }
    }

    //var throttledCompareSharedViews = _.throttle(compareSharedViews, 250, { leading: false });

    function clearSharedViewData(clearLimitedData) {
        console.log('clearSharedViewData');
        _.each(tracking.data.sharedView.markers, function (item) {
            removeItemFromMap(item, null, tracking.viewModes.SHARED_VIEW);
        });
        _.each(tracking.data.sharedView.mapLinesByAssetId, function (item) {
            removeItemFromMap(item, null, tracking.viewModes.SHARED_VIEW);
        });
        _.each(tracking.data.sharedView.markerClustersByAssetId, function (item) {
            removeItemFromMap(item, null, tracking.viewModes.SHARED_VIEW);
        });
        _.each(tracking.data.sharedView.fenceMarkers, function(item) {
            removeItemFromMap(item, null, tracking.viewModes.SHARED_VIEW);
        });
        _.each(tracking.data.sharedView.placeMarkers, function (item) {
            removeItemFromMap(item, null, tracking.viewModes.SHARED_VIEW);
        });

        tracking.data.sharedView.markers = [];
        tracking.data.sharedView.fenceMarkers = [];
        tracking.data.sharedView.placeMarkers = [];
        tracking.data.sharedView.events = [];
        tracking.data.sharedView.normalizedEvents = [];
        tracking.data.sharedView.isMessagesLoaded = false;
        tracking.data.sharedView.normalizedMessages = [];
        tracking.data.sharedView.normalizedEventIds = {};
        tracking.data.sharedView.markerClustersByAssetId = {};
        tracking.data.sharedView.mapLinesByAssetId = {};
        tracking.data.sharedView.positionsByAssetId = {};
        tracking.data.sharedView.markersByAssetId = {};
        tracking.data.sharedView.normalizedPositions = [];
        tracking.data.sharedView.normalizedPositionsByAssetId = {};
        tracking.data.sharedView.messageCountsByAssetId = {};
        tracking.data.sharedView.fromDate = null;
        tracking.data.sharedView.toDate = null;
        tracking.data.sharedView.isDateFiltered = false;
        tracking.data.sharedView.isLimited = false;
        tracking.data.sharedView.isLoadedLimitedData = false;
        tracking.data.sharedView.messageCounts = {
            FromMobile: 0,
            ToMobile: 0,
            FromMobileChats: 0,
            ToMobileChats: 0,
            FromMobileDevice: 0,
            ToMobileDevice: 0
        };
        if (clearLimitedData) {
            tracking.data.sharedView.limitedData = null;
            updateMapModePanel();
            updateActiveAssetInformation(tracking.viewModes.SHARED_VIEW);
        } else {
            showResultLimitsIfApplicable({ IsLimited: false, IncludesLimitedData: false }, false);
        }
    }    

    function updateLimitedDataResults(viewMode) {
        var source = null;
        if (viewMode === tracking.viewModes.NORMAL) {
            source = tracking.data.history;
        } else if (viewMode === tracking.viewModes.SHARED_VIEW) {
            source = tracking.data.sharedView;
        }
        if (source === null || source.limitedData === undefined || source.limitedData === null) {
            return;
        }

        // update UI
        // reverse paging system
        var lastVisibleNumber = source.limitedData.counts.Positions - (source.limitedData.limit * (source.limitedData.currentPage - 1));
        var firstVisibleNumber = lastVisibleNumber - source.limitedData.limit + 1;
        if (firstVisibleNumber < 1) {
            firstVisibleNumber = 1;
        }

        document.getElementById('limited-data-range').textContent = '#' + firstVisibleNumber + ' - #' + lastVisibleNumber;
        document.getElementById('limited-data-from').textContent = source.limitedData.pageDates[source.limitedData.currentPage].visibleFromLocal;
        document.getElementById('limited-data-to').textContent = source.limitedData.pageDates[source.limitedData.currentPage].visibleToLocal;

        // enable/disable paging buttons
        if (source.limitedData.currentPage === source.limitedData.pages) {
            document.getElementById('limited-data-prev').disabled = true;
            document.getElementById('limited-data-prev').classList.add('disabled');
        } else {
            document.getElementById('limited-data-prev').disabled = false;
            document.getElementById('limited-data-prev').classList.remove('disabled');
        }
        if (source.limitedData.currentPage === 1) {
            document.getElementById('limited-data-next').disabled = true;
            document.getElementById('limited-data-next').classList.add('disabled');
        } else {
            document.getElementById('limited-data-next').disabled = false;
            document.getElementById('limited-data-next').classList.remove('disabled');
        }
    }

    function handleLimitedDataResult(viewMode, fromDateUtc, toDateUtc, counts, limit) {
        var source = null;
        if (viewMode === tracking.viewModes.NORMAL) {
            source = tracking.data.history;
        } else if (viewMode === tracking.viewModes.SHARED_VIEW) {
            source = tracking.data.sharedView;
        }
        if (source === null) {
            return;
        }

        var orderedPositions = _.sortBy(source.normalizedPositions, 'Epoch');
        var firstVisible = _.find(orderedPositions, function (item) { return !item.Position.IsHidden; });
        var lastVisible = _.find(orderedPositions.reverse(), function (item) { return !item.Position.IsHidden; });
        var firstVisibleTime = null;
        var firstVisibleTimeLocal = null;
        var lastVisibleTime = null;
        var lastVisibleTimeLocal = null;
        if (firstVisible !== undefined) {
            firstVisibleTime = moment(firstVisible.Epoch * 1000).subtract(tracking.user.tickOffset, 'ms').format(tracking.user.dateWithStandardTimeFormat);
            firstVisibleTimeLocal = firstVisible.Position.Time;
        }
        if (lastVisible !== undefined) {
            lastVisibleTime = moment(lastVisible.Epoch * 1000).subtract(tracking.user.tickOffset, 'ms').format(tracking.user.dateWithStandardTimeFormat);
            lastVisibleTimeLocal = lastVisible.Position.Time;
        }
        if (source.limitedData === null) {
            // TODO set visible-positions-history.textContent to counts.Positions
            var pages = Math.ceil(counts.Positions / limit);

            source.limitedData = {
                counts: counts,
                currentPage: 1,
                pages: pages,
                fromDateUtc: fromDateUtc,
                fromDateEpoch: (fromDateUtc !== null && fromDateUtc !== '') ? moment.utc(fromDateUtc, tracking.user.dateWithStandardTimeFormat).valueOf() : null,
                toDateUtc: toDateUtc,
                toDateEpoch: (toDateUtc !== null && toDateUtc !== '') ? moment.utc(toDateUtc, tracking.user.dateWithStandardTimeFormat).valueOf() : null,
                pageDates: {},
                limit: limit
            };
            // TODO proper timezone support
            source.limitedData.fromDate = source.limitedData.fromDateEpoch !== null ? moment(source.limitedData.fromDateEpoch).subtract(tracking.user.tickOffset).format(tracking.user.dateFormat) : null;
            source.limitedData.toDate = source.limitedData.toDateEpoch !== null ? moment(source.limitedData.toDateEpoch).subtract(tracking.user.tickOffset).format(tracking.user.dateFormat) : null;
        }

        source.limitedData.visibleFromDateEpoch = firstVisible !== undefined ? (firstVisible.Epoch * 1000) : null;  // TODO use an incrementing counter instead of time
        source.limitedData.visibleToDateEpoch = lastVisible !== undefined ? (lastVisible.Epoch * 1000) : null;  // TODO use an incrementing counter instead of time
        source.limitedData.visibleFrom = firstVisibleTime;
        source.limitedData.visibleFromLocal = firstVisibleTimeLocal;
        source.limitedData.visibleTo = lastVisibleTime;
        source.limitedData.visibleToLocal = lastVisibleTimeLocal;
        source.limitedData.pageDates[source.limitedData.currentPage] = {
            fromUtc: fromDateUtc,
            toUtc: toDateUtc,
            visibleFrom: firstVisibleTime,
            visibleFromLocal: firstVisibleTimeLocal,
            //visibleFrom: (source.limitedData.currentPage !== source.limitedData.pages ? firstVisibleTime : null),
            visibleFromUtc: moment.utc(firstVisible.Epoch * 1000).format(tracking.user.dateWithStandardTimeFormat),
            //visibleFromUtc: (source.limitedData.currentPage !== source.limitedData.pages ? moment.utc(firstVisible.Epoch * 1000).format(tracking.user.dateWithStandardTimeFormat) : null),
            visibleFromEpoch: firstVisible.Epoch * 1000,
            //visibleFromEpoch: (source.limitedData.currentPage !== source.limitedData.pages ? (firstVisible.Epoch * 1000) : null),
            visibleTo: lastVisibleTime,
            visibleToLocal: lastVisibleTimeLocal,
            //visibleTo: (source.limitedData.currentPage !== 1 ? lastVisibleTime : null),
            visibleToUtc: moment.utc(lastVisible.Epoch * 1000).format(tracking.user.dateWithStandardTimeFormat),
            //visibleToUtc: (source.limitedData.currentPage !== 1 ? moment.utc(lastVisible.Epoch * 1000).format(tracking.user.dateWithStandardTimeFormat) : null),
            visibleToEpoch: lastVisible.Epoch * 1000
            //visibleToEpoch: (source.limitedData.currentPage !== 1 ? (lastVisible.Epoch * 1000) : null)
        };
        updateLimitedDataResults(viewMode);
    }

    function querySharedViewData(sharedView, dateFilter, loadLimitedData) {
        console.log('query shared view', sharedView);
        var fromUtc = null;
        var fromUtcEpoch = null;
        var toUtc = null;
        var toUtcEpoch = null;
        
        // override shared view's date ranges to show data that otherwise would exceed display limits
        var isDateFiltered = false;
        if (dateFilter !== undefined && dateFilter !== null) {
            fromUtc = dateFilter.fromUtc;
            toUtc = dateFilter.toUtc;
            isDateFiltered = true;
        } else {
            if (sharedView.IsTimeframeRelative) {
                var momentType = sharedView.RelativeTimeframeType === 'm' ? 'M' : sharedView.RelativeTimeframeType;
                var fromLocal = moment().subtract(sharedView.RelativeTimeframeNumber, momentType).subtract(tracking.user.tickOffset, 'ms');
                //fromUtcEpoch = moment.utc()
                fromUtc = moment.utc().subtract(sharedView.RelativeTimeframeNumber, momentType).format(tracking.user.dateFormat);
                document.getElementById('txtDateFrom').value = fromLocal.format(tracking.user.dateFormat); // utc to local
                document.getElementById('txtDateTo').value = '';
            } else {
                if (sharedView.FromDateEpoch !== null) {
                    var fromLocal = moment(sharedView.FromDateEpoch);
                    fromUtc = moment.utc(sharedView.FromDateEpoch).format(tracking.user.dateFormat);
                    document.getElementById('txtDateFrom').value = fromLocal.subtract(tracking.user.tickOffset, 'ms').format(tracking.user.dateFormat); // utc to local
                } else {
                    document.getElementById('txtDateFrom').value = '';
                }
                if (sharedView.ToDateEpoch !== null) {
                    var toLocal = moment(sharedView.ToDateEpoch);
                    toUtc = moment.utc(sharedView.ToDateEpoch).format(tracking.user.dateFormat);
                    document.getElementById('txtDateTo').value = toLocal.subtract(tracking.user.tickOffset, 'ms').format(tracking.user.dateFormat); // utc to local
                } else {
                    document.getElementById('txtDateTo').value = '';
                }
            }
        }

        var assetIds = sharedView.AssetIds.slice(0);
        var assetGroupIds = sharedView.AssetGroupIds;
        _.each(assetGroupIds, function (assetGroupId) {
            var group = findGroupById(assetGroupId);
            if (group !== null) {
                var groupAssetIds = findAssetIdsUnderGroup(group);
                assetIds = assetIds.concat(groupAssetIds);
            }
        });
        var sharedViewId = null;
        if (sharedView !== null) {
            sharedViewId = sharedView.Id;
        }
        
        var data = {
            request: {
                sharedViewId: sharedViewId,
                fromDate: fromUtc, // UTC
                toDate: toUtc, // UTC
                isMessagingEnabled: sharedView.IsMessagingEnabled,
                assetIds: _.uniq(assetIds),
                loadLimitedData: loadLimitedData,
                isMobile: tracking.state.isMobile,
                format: tracking.user.dateFormat,
                lang: tracking.user.dateCulture
            }
        };

        // set some dates in the map mode panel
        tracking.data.sharedView.fromDate = fromUtc;
        tracking.data.sharedView.toDate = toUtc;

        return handleAjaxFormSubmission('GetSharedViewData', data, null, null, null, tracking.strings.MSG_QUERY_SHARED_VIEW_ERROR, function (result) {
            if (tracking.data.sharedView.current !== sharedView && tracking.data.sharedView.temp !== sharedView) {
                // if the results came back after the shared view has changed, discard them
                return;
            }

            if (result.IsLimited === true && result.IncludesLimitedData === false) {
                document.getElementById('txtDateFrom').value = fromUtc; // isn't this utc?
                document.getElementById('txtDateTo').value = toUtc; // isn't this utc?
                showResultLimitsIfApplicable(result, false);
                return;
            }

            // clear previous results
            clearSharedViewData(!isDateFiltered); // TODO clear limitedData here under specific circumstances?

            tracking.data.sharedView.isDateFiltered = isDateFiltered;
            tracking.data.sharedView.isLimited = result.IsLimited;
            tracking.data.sharedView.isLoadedLimitedData = result.IncludesLimitedData;
            
            _.each(result.Assets, function (assetResult) {
                processSharedViewData(sharedView.Id, assetResult, sharedView.Preferences.PositionConsolidation);
            });
            
            tracking.data.sharedView.events = result.Events;
            tracking.data.sharedView.messageCounts = {
                FromMobile: 0,
                FromMobileChats: 0,
                FromMobileDevice: 0,
                ToMobile: 0,
                ToMobileChats: 0,
                ToMobileDevice: 0
            };

            _.each(tracking.data.sharedView.messageCountsByAssetId, function (item) {
                tracking.data.sharedView.messageCounts.FromMobile += item.FromMobile;
                tracking.data.sharedView.messageCounts.FromMobileChats += item.FromMobileChats;
                tracking.data.sharedView.messageCounts.FromMobileDevice += item.FromMobileDevice;
                tracking.data.sharedView.messageCounts.ToMobile += item.ToMobile;
                tracking.data.sharedView.messageCounts.ToMobileChats += item.ToMobileChats;
                tracking.data.sharedView.messageCounts.ToMobileDevice += item.ToMobileDevice;
            });

            var normalizedSharedViewEvents = [];
            _.each(result.Events, function (item) {
                if (tracking.data.sharedView.eventsById[item.Id] === undefined) {
                    tracking.data.sharedView.eventsById[item.Id] = normalizeAssetData(item.AssetId, 'event', item);
                }
                normalizedSharedViewEvents.push(tracking.data.sharedView.eventsById[item.Id]);
                tracking.data.sharedView.normalizedEvents.push(tracking.data.sharedView.eventsById[item.Id]);
                tracking.data.sharedView.normalizedEventIds[item.Id] = true;
            });

            tracking.data.sharedView.normalizedEvents = normalizedSharedViewEvents;

            // data manipulation/loading completed by this step

            // add geofences and places
            addFenceAndPlaceMarkersToSharedView(sharedView.PlaceIds, sharedView.FenceIds);

            // map type
            tracking.data.isSatelliteLabelOverlayEnabled = !sharedView.Preferences.RemoveRoads;
            changeMapType(_.findKey(tracking.data.MAP_TYPES, function (item) { return item === sharedView.Preferences.MapType; }));

            updateAssetFunctionBadges(tracking.dataGroups.SHARED_VIEW_HISTORY, sharedView.Id);
            showResultLimitsIfApplicable(result, isDateFiltered);

            if (tracking.data.sharedView.isLimited && tracking.data.sharedView.isLoadedLimitedData || (tracking.data.sharedView.limitedData !== null)) {
                handleLimitedDataResult(tracking.viewModes.SHARED_VIEW, fromUtc, toUtc, result.Counts, result.Limit);
            } else {
                tracking.data.sharedView.limitedData = null;
            }
            updateActiveAssetInformation(tracking.viewModes.SHARED_VIEW);
            updateMapModeDateRange();

            resizeApp(true);
            setMapBounds();
        });
    }

    var throttledQuerySharedViewData = _.throttle(querySharedViewData, 250, { leading: false });

    function querySingleTrip(journey, trip) {
        if (trip.IsQueried === true) {
            return null;
        }
        trip.IsQueried = true;
        toggleLoadingMessage(true, 'trip' + trip.Id);
        var data = {
            journeyId: journey.Id,
            tripId: trip.Id,
            zoomLevel: null,
            sensitivity: null,
            loadLimitedData: true,
            isMobile: tracking.state.isMobile
        };
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetTripData'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                if (msg.d) {
                    //tracking.state.hasQueriedHistory = true;
                    var result = msg.d;

                    _.each(result.Assets, function (assetResult) {
                        // should only be a single asset returned
                        processTripData(journey, trip, assetResult);
                    });
                    //var events = result.Events;
                    //_.each(events, function (item) {
                    //    var existingTripEvent = _.find(tracking.data.trips.events, function (evt) { return evt.Id === item.Id; });
                    //    if (existingTripEvent !== undefined) {
                    //        return;
                    //    }
                    //    tracking.data.trips.events.push(item);
                    //});
                    tracking.data.trips.eventsByTripId[trip.Id] = result.Events;

                    var normalizedTripEvents = [];
                    _.each(result.Events, function (item) {
                        if (tracking.data.eventsById[item.Id] === undefined) {
                            tracking.data.eventsById[item.Id] = normalizeAssetData(item.AssetId, 'event', item);
                        }
                        normalizedTripEvents.push(tracking.data.eventsById[item.Id]);
                        tracking.data.trips.normalizedEvents.push(tracking.data.eventsById[item.Id]);
                        tracking.data.trips.normalizedEventIds[item.Id] = true;
                    });

                    tracking.data.trips.normalizedEventsByTripId[trip.Id] = normalizedTripEvents;

                    updateAssetFunctionBadges(tracking.dataGroups.JOURNEY_HISTORY, trip.Id);
                    updateGroupFunctionBadges(tracking.dataGroups.JOURNEY_HISTORY, [trip.Id], 'trip');
                    resizeApp(true);
                    setMapBounds();
                }
                toggleLoadingMessage(false, 'trip' + trip.Id);
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_SINGLE_ASSET_ERROR);
                toggleLoadingMessage(false, 'trip' + trip.Id);
            }
        });
    }

    function querySingleAsset(assetId) {
        // add a single asset's positions to the existing query results
        var fromDate = tracking.data.history.fromDate;
        var toDate = tracking.data.history.toDate;
        // disable show button and show loading message
        tracking.data.domNodes.mapMode.show.disabled = true;
        toggleLoadingMessage(true, assetId);
        var data = {
            fromDate: fromDate,
            toDate: toDate,
            assetIds: assetId,
            zoomLevel: null,
            sensitivity: null,
            format: tracking.user.dateFormat,
            lang: tracking.user.dateCulture,
            loadLimitedData: false,
            isMobile: tracking.state.isMobile
        };
        return $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetAssetPositionsForDateRange'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json'
        })
        .done(function (msg) {
            tracking.data.domNodes.mapMode.show.disabled = false;
            toggleLoadingMessage(false, assetId);
            if (msg.d) {
                var result = msg.d;
                if (result.IsLimited === true && result.IncludesLimitedData === false) {
                    showResultLimitsIfApplicable(result, false);
                    return;
                }

                tracking.state.hasQueriedHistory = true;                
                _.each(result.Assets, function (assetResult) {
                    processAssetHistoryPositionsResult(assetResult);
                });
                if (result.Events !== null) {
                    addAssetEvents(result.Events, tracking.mapModes.HISTORY);
                }
                createHistoryPositionResults();
                if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
                    resizeApp(true);
                    setMapBounds();
                }
                showResultLimitsIfApplicable(result, false);
                tracking.data.history.isLimited = result.IsLimited;
                tracking.data.history.isLoadedLimitedData = result.IncludesLimitedData;
            }
        })
        .fail(function (xhr, status, error) {
            utility.handleWebServiceError(tracking.strings.MSG_SINGLE_ASSET_ERROR);
            // re-enable show button and clear loading message
            tracking.data.domNodes.mapMode.show.disabled = false;
            toggleLoadingMessage(false, assetId);
        });
    }

    function requeryHistory() {
        if (tracking.state.activeMapMode === tracking.mapModes.LIVE || tracking.data.history.toDate !== '') {
            clearInterval(tracking.intervals.history);
            return;
        }

        queryActiveAssets();
    }

    function closePrimaryPanel() {
        if (tracking.state.openPanels.secondary === true) {
            closeSecondaryPanel();
        }
        hidePrimaryPanel();
        tracking.state.openPanels.primary = false;
    }

    function hidePrimaryPanel() {
        hideSecondaryPanel();
        var wasClosed = tracking.data.domNodes.panels.primary.classList.contains('is-closed');
        var wasOpen = tracking.data.domNodes.panels.primary.classList.contains('is-opened');
        tracking.data.domNodes.panels.primary.classList.add('is-closed');
        tracking.data.domNodes.panels.primary.classList.remove('is-opened');
        tracking.data.domNodes.nav.utility.classList.add('is-closed');
        tracking.data.domNodes.nav.utility.classList.remove('is-opened');
        tracking.data.domNodes.nav.primary.classList.remove('is-active');
        tracking.data.domNodes.content.base.classList.add('is-expanded');
        tracking.data.domNodes.content.base.classList.remove('is-retracted');
        var button = document.getElementById('toggle-nav-primary');
        button.classList.remove('active');
        button.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#expand');
        //tracking.state.openPanels.primary = false; // this isn't accurate, should just be hidden
        if (tracking.map !== null && (wasOpen || !wasClosed)) {
            tracking.map.invalidateSize({ pan: false, debouceMoveend: true });
        }
    }

    function moveSecondaryPanelToPrimary() {
        console.log('move panels');
        tracking.data.domNodes.content.base.classList.remove('is-collapsed');
        tracking.data.domNodes.content.base.classList.add('is-retracted');
        tracking.data.domNodes.nav.utility.classList.add('is-opened');
        tracking.data.domNodes.nav.primary.classList.add('is-active');
        tracking.data.domNodes.nav.utility.classList.remove('is-expanded');
        tracking.data.domNodes.panels.secondary.classList.add('is-over-primary');
    }

    function openSecondaryPanel(closePrevious) {
        //var notificationOptions = document.getElementById('panel-options-notifications');
        //if (notificationOptions !== null) {
        //    notificationOptions.classList.remove('is-visible');
        //}
        if (tracking.state.openPanels.secondary === true && closePrevious) {
            // was already open with something, close out whatever was there before
            if (tracking.data.domNodes.panels.secondary.closeCallback !== undefined
                && tracking.data.domNodes.panels.secondary.closeCallback !== null) {
                tracking.data.domNodes.panels.secondary.closeCallback();
                tracking.data.domNodes.panels.secondary.closeCallback = undefined;
            }
        }
        //if (tracking.state.openPanels.secondary !== true) { // could be hidden and not just closed
        openPrimaryPanel();
        tracking.data.domNodes.panels.secondary.classList.remove('is-over-primary');
        tracking.data.domNodes.panels.secondary.classList.add('is-visible');
        tracking.data.domNodes.panels.secondary.classList.add('d-flex');
        tracking.data.domNodes.nav.utility.classList.remove('is-closed');
        tracking.data.domNodes.nav.utility.classList.add('is-expanded');
        tracking.data.domNodes.content.base.classList.add('is-collapsed');
        tracking.data.domNodes.content.base.classList.remove('is-retracted');
        if (tracking.state.openPanels.secondary !== true) {
            tracking.state.openPanels.secondary = true;
            tracking.map.invalidateSize({ pan: false, debouceMoveend: true });
            tracking.data.domNodes.simpleBars.secondary.recalculate();
        }
        //}
    }

    function hideSecondaryPanel() {
        tracking.data.domNodes.panels.secondary.classList.remove('is-visible');
        tracking.data.domNodes.panels.secondary.classList.remove('d-flex');
        //tracking.data.domNodes.nav.utility.classList.remove('is-closed'); // why
        tracking.data.domNodes.nav.utility.classList.remove('is-expanded');
        tracking.data.domNodes.content.base.classList.remove('is-collapsed');
        tracking.data.domNodes.content.base.classList.add('is-retracted');
    }

    function closeSecondaryPanel() {
        hideSecondaryPanel();
        tracking.data.domNodes.panels.secondary.removeAttribute('data-group-for');
        tracking.data.domNodes.panels.secondary.removeAttribute('data-item-type');
        tracking.data.domNodes.panels.secondary.removeAttribute('data-item-id');
        if (tracking.data.domNodes.panels.secondary.closeCallback !== undefined
            && tracking.data.domNodes.panels.secondary.closeCallback !== null) {
            tracking.data.domNodes.panels.secondary.closeCallback();
            tracking.data.domNodes.panels.secondary.closeCallback = undefined;
        }
        _.each(tracking.data.domNodes.assets, function (assetNodes) { _.each(assetNodes, clearSettingsNode); });
        _.each(tracking.data.domNodes.groups, clearSettingsNode);
        _.each(tracking.data.domNodes.fences, clearSettingsNode);
        _.each(tracking.data.domNodes.places, clearSettingsNode);
        _.each(tracking.data.domNodes.trips, clearSettingsNode);
        //_.each(tracking.data.domNodes.sharedViews, clearSettingsNode);
        if (tracking.state.openPanels.secondary === true) {
            tracking.map.invalidateSize({ pan: false, debouceMoveend: true });
        }
        tracking.state.openPanels.secondary = false;
    }

    function openPrimaryPanel() {
        // may be hidden but not closed
        //if (tracking.state.openPanels.primary !== true) {
        var wasOpen = tracking.data.domNodes.panels.primary.classList.contains('is-opened');
        var wasClosed = tracking.data.domNodes.panels.primary.classList.contains('is-closed');
        tracking.data.domNodes.panels.primary.classList.remove('is-closed');
        tracking.data.domNodes.panels.primary.classList.add('is-opened');
        tracking.data.domNodes.content.base.classList.remove('is-expanded');
        tracking.data.domNodes.content.base.classList.add('is-retracted');
        tracking.data.domNodes.nav.utility.classList.remove('is-closed');
        tracking.data.domNodes.nav.utility.classList.add('is-opened');
        tracking.data.domNodes.nav.primary.classList.add('is-active');
        closePrimaryNavigation();
        var button = document.getElementById('toggle-nav-primary');
        button.classList.add('active');
        button.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#collapse');
        if (!wasOpen || wasClosed) {
            tracking.map.invalidateSize({ pan: false, debouceMoveend: true }); // only if it actually changed
        }
        if (tracking.state.openPanels.primary !== true) {
            tracking.data.domNodes.simpleBars.primary.recalculate();
        }
        tracking.state.openPanels.primary = true;
        //}
    }

    function openPrimaryNavigation() {
        tracking.data.domNodes.nav.toggle.classList.add('is-active');
        tracking.data.domNodes.nav.primary.classList.remove('is-closed'); // expands it to full
    }

    function closePrimaryNavigation() {
        tracking.data.domNodes.nav.toggle.classList.remove('is-active');
        tracking.data.domNodes.nav.primary.classList.add('is-closed');
    }

    function resetParametersForms(node) {
        var forms = node.querySelectorAll('.parameters-form');
        _.each(forms, function (form) {
            $('.parameter-toggle input:checkbox', form).prop('checked', false);
            $('.parameter-toggle input:checkbox', form).parents('.parameter-toggle').next('.parameter-options').find('input,select,label').prop('disabled', true).addClass('disabled');
        });
    }

    function clearStatusMessage(elem) {
        elem.textContent = '';
        elem.classList.remove('alert-danger');
        elem.classList.remove('alert-success');
        elem.classList.remove('is-visible');
    }

    function formShowSuccessMessage(elem, message, noScroll) {
        elem.textContent = message;
        elem.classList.add('alert-success');
        elem.classList.add('is-visible');
        elem.classList.remove('alert-danger');
        if (noScroll === undefined) {
            elem.scrollIntoView(false);
        }
        //elem.text(message).addClass('alert-success is-visible').removeClass('alert-danger');
    }

    function formShowErrorMessage(elem, message, noScroll) {
        elem.textContent = message;
        elem.classList.add('alert-danger');
        elem.classList.add('is-visible');
        elem.classList.remove('alert-success');
        if (noScroll === undefined) {
            elem.scrollIntoView(false);
        }
        //elem.text(message).addClass('alert-danger is-visible').removeClass('alert-success');
    }

    function filterSharedViews(search) {
        // TODO make this generic across fences/places/shared views
        var sharedViews = tracking.data.search.sharedViews.search(search);
        var sharedViewIds = _.map(sharedViews, 'Id');

        if (search === '') {
            sharedViewIds = _.map(tracking.data.sharedViews, 'Id');
            tracking.data.domNodes.filter.sharedViewResults.classList.remove('is-visible');
        }

        // remove or hide nodes and groups that don't match the search
        // we'll attempt to simply hide the nodes to start with
        // but we may need to completely remove them instead for performance
        _.each(tracking.data.domNodes.sharedViews, function (sharedViewNode, id, nodes) {
            if (_.indexOf(sharedViewIds, parseInt(id)) !== -1) {
                // this shared-view is visible
                sharedViewNode.classList.remove('is-filtered');
            } else {
                // shared-view not included in search results
                sharedViewNode.classList.add('is-filtered');
            }
        });

        if (sharedViewIds.length === 0) {
            tracking.data.domNodes.groups['all-shared-views'].classList.add('is-filtered');
        } else {
            tracking.data.domNodes.groups['all-shared-views'].classList.remove('is-filtered');
        }

        if (search !== '') {
            showFilterResults(sharedViews.length, 'shared-views');
        }
    }

    function filterFences(search) {
        var fences = tracking.data.search.fences.search(search);
        var fenceIds = _.map(fences, 'Id');

        if (search === '') {
            fenceIds = _.map(tracking.data.fences, 'Id');
            tracking.data.domNodes.filter.fenceResults.classList.remove('is-visible');
        }

        // remove or hide nodes and groups that don't match the search
        // we'll attempt to simply hide the nodes to start with
        // but we may need to completely remove them instead for performance
        _.each(tracking.data.domNodes.fences, function (fenceNode, id, nodes) {
            if (_.indexOf(fenceIds, id) !== -1) {
                // this fence is visible
                fenceNode.classList.remove('is-filtered');
            } else {
                // fence not included in search results
                fenceNode.classList.add('is-filtered');
            }
        });

        if (fenceIds.length === 0) {
            tracking.data.domNodes.groups['all-fences'].classList.add('is-filtered');
        } else {
            tracking.data.domNodes.groups['all-fences'].classList.remove('is-filtered');
        }

        if (search !== '') {
            showFilterResults(fences.length, 'fences');
        }
    }

    function filterPlaces(search) {
        var places = tracking.data.search.places.search(search);
        var placeIds = _.map(places, 'Id');

        if (search === '') {
            placeIds = _.map(tracking.data.places, 'Id');
            tracking.data.domNodes.filter.placeResults.classList.remove('is-visible');
        }

        // remove or hide nodes and groups that don't match the search
        // we'll attempt to simply hide the nodes to start with
        // but we may need to completely remove them instead for performance
        _.each(tracking.data.domNodes.places, function (placeNode, id, nodes) {
            if (_.indexOf(placeIds, parseInt(id)) !== -1) {
                // this place is visible
                placeNode.classList.remove('is-filtered');
            } else {
                // place not included in search results
                placeNode.classList.add('is-filtered');
            }
        });

        if (placeIds.length === 0) {
            tracking.data.domNodes.groups['all-places'].classList.add('is-filtered');
        } else {
            tracking.data.domNodes.groups['all-places'].classList.remove('is-filtered');
        }

        if (search !== '') {
            showFilterResults(places.length, 'places');
        }
    }

    function unfilterGroupAndChildren(group) {
        // group content nodes are removed from the DOM and must be iterated
        var groupContents = tracking.data.domNodes.groupContents[group.Id];

        var groupAssets = groupContents.querySelectorAll('.group-item');
        _.each(groupAssets, function (groupItemNode) {
            groupItemNode.classList.remove('is-filtered');
        });
        var groupGroups = groupContents.querySelectorAll('.group');
        _.each(groupGroups, function (groupItemNode) {
            groupItemNode.classList.remove('is-filtered');
            var subGroup = findGroupById(groupItemNode.getAttribute('data-group-id'));
            unfilterGroupAndChildren(subGroup);
        });
    }

    function filterAssets(search) {
        var assetGroups = tracking.data.search.assetGroups.search(search);
        var assetGroupIds = _.map(assetGroups, 'Id');

        var assets = tracking.data.search.assets.search(search);
        var assetIds = _.map(assets, 'Id');

        //console.log('filter matches: ' + assetGroupIds.length +', ' + assetIds.length);
        //console.log(assetGroupIds);
        //console.log(assetIds);

        if (search === '') {
            assetIds = _.map(tracking.data.assets, 'Id');
            assetGroupIds = _.map(tracking.data.groups, 'Id');
            tracking.data.domNodes.filter.assetResults.classList.remove('is-visible');
            toggleItemSorting('assets', tracking.user.displayPreferences.sortMode.assets === tracking.sortModes.CUSTOM);
        } else {
            toggleItemSorting('assets', false);
        }

        if (assets.length === 0 && assetGroups.length === 0) {
            // hide all assets or show all assets?
        }

        // remove or hide nodes and groups that don't match the search
        // we'll attempt to simply hide the nodes to start with
        // but we may need to completely remove them instead for performance

        // if an asset matches the search, show it and any parent groups above it
        _.each(tracking.data.domNodes.assets, function (assetNodes, id, nodes) {
            if (_.indexOf(assetIds, parseInt(id)) !== -1) {
                // this asset matches
                _.each(assetNodes, function (assetNode) {
                    assetNode.classList.remove('is-filtered');
                });
            } else {
                // asset not included in search results
                _.each(assetNodes, function (assetNode) {
                    assetNode.classList.add('is-filtered');
                });
            }
        });

        // filter groups with no matching assets
        _.each(tracking.data.groups, function (group) {
            var groupNode = tracking.data.domNodes.groups[group.Id];
            if (groupNode.getAttribute('data-group-for') !== 'assets') {
                return;
            }
            if (search !== '' && _.intersection(findAssetIdsUnderGroup(group), assetIds).length === 0) {
                groupNode.classList.add('is-filtered');
            } else {
                groupNode.classList.remove('is-filtered');
            }
        });

        // groups that did not have a matching asset are hidden at this point, re-show any groups that also match
        // it's cleaner to perform this after the asset filtering due to subgroups
        var assetIdsUnderGroups = [];
        _.each(tracking.data.groups, function (group) {
            if (_.indexOf(assetGroupIds, group.Id) !== -1) {
                assetIdsUnderGroups = _.union(assetIdsUnderGroups, findAssetIdsUnderGroup(group));
                // show this group and any assets or groups under it
                var groupNode = tracking.data.domNodes.groups[group.Id];
                if (groupNode.getAttribute('data-group-for') !== 'assets') {
                    return;
                }
                groupNode.classList.remove('is-filtered');

                unfilterGroupAndChildren(group);

                // if this is a subgroup, its parent groups must be included up to root
                var parentGroupId = group.ParentGroupId;
                while (parentGroupId !== undefined && parentGroupId !== null) {
                    var parentGroup = findGroupById(parentGroupId);
                    var parentGroupNode = tracking.data.domNodes.groups[parentGroup.Id];
                    parentGroupNode.classList.remove('is-filtered');
                    parentGroupId = parentGroup.ParentGroupId;
                }
            } else {
                // leave this group and its contents hidden
            }
        });

        if (assetIds.length === 0) {
            tracking.data.domNodes.groups['all-assets'].classList.add('is-filtered');
        } else {
            tracking.data.domNodes.groups['all-assets'].classList.remove('is-filtered');
        }

        // filter results is assets.length + assets under visible groups
        var allIncludedAssetIds = _.uniq(_.union(assetIds, assetIdsUnderGroups)).length;
        if (search !== '') {
            showFilterResults(allIncludedAssetIds, 'assets');
        }
    }

    function updateSecondaryPanelNotificationContentForNewMode() {
        if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') !== 'dialog') {
            return;
        }

        var notifyForType = tracking.data.domNodes.panels.secondary.getAttribute('data-item-type');

        var secondaryDialog = document.getElementById('dialog-functions').querySelector('.dialog');

        switch (notifyForType) {
            case 'assets':
                var assetId = parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id'));
                var asset = findAssetById(assetId);
                if (asset === undefined || asset === null) {
                    return;
                }
                if (secondaryDialog === tracking.data.domNodes.dialogs.assetPositions) {
                    openPositionsForAsset(asset);
                } else if (secondaryDialog === tracking.data.domNodes.dialogs.assetEvents) {
                    openEventsForAsset(asset);
                } else if (secondaryDialog === tracking.data.domNodes.dialogs.assetMessages) {
                    openMessagesForAsset(asset);
                } else if (secondaryDialog === tracking.data.domNodes.dialogs.assetChat) {
                    openChatForAsset(asset);
                } else if (secondaryDialog === tracking.data.domNodes.dialogs.assetStatus) {
                    openStatusForAsset(asset);
                } else if (secondaryDialog === tracking.data.domNodes.dialogs.assetAlerts) {
                    openAlertsForAsset(asset);
                } else if (secondaryDialog === tracking.data.domNodes.dialogs.assetActivity) {
                    openActivityForAsset(asset);
                }
                break;
            case 'groups':
                var groupId = tracking.data.domNodes.panels.secondary.getAttribute('data-item-id');
                var group = findGroupById(groupId);
                if (secondaryDialog === tracking.data.domNodes.dialogs.assetPositions) {
                    openPositionsForGroup(group);
                } else if (secondaryDialog === tracking.data.domNodes.dialogs.assetEvents) {
                    openEventsForGroup(group);
                } else if (secondaryDialog === tracking.data.domNodes.dialogs.assetMessages) {
                    openMessagesForGroup(group);
                } else if (secondaryDialog === tracking.data.domNodes.dialogs.assetStatus) {
                    openStatusForGroup(group);
                } else if (secondaryDialog === tracking.data.domNodes.dialogs.assetAlerts) {
                    openAlertsForGroup(group);
                } else if (secondaryDialog === tracking.data.domNodes.dialogs.assetChat) {
                    openChatForGroup(group);
                } else if (secondaryDialog === tracking.data.domNodes.dialogs.assetActivity) {
                    openActivityForGroup(group);
                }
                break;
        }
    }

    function setUrlHistoryForActivePanel(panelAction) {
        var action = '';
        var defaultAction = ' ';
        if (panelAction !== undefined) {
            action = '/' + panelAction;
            defaultAction = '#' + panelAction;
        }
        var modeText = tracking.state.activeMapMode === null ? tracking.options.defaultMode : tracking.state.activeMapMode === tracking.mapModes.HISTORY ? 'history' : 'live';
        switch (tracking.state.activePanel) {
            case tracking.panels.ASSETS:
                history.replaceState('panel-active', '', '#' + modeText + '/assets' + action);
                break;
            case tracking.panels.GEOFENCES:
                history.replaceState('panel-active', '', '#' + modeText + '/fences' + action);
                break;
            case tracking.panels.PLACES:
                history.replaceState('panel-active', '', '#' + modeText + '/places' + action);
                break;
            case tracking.panels.JOURNEYS:
                history.replaceState('panel-active', '', '#' + modeText + '/journeys' + action);
                break;
            case tracking.panels.SHARED_VIEWS:
                history.replaceState('panel-active', '', '#' + modeText + '/shared-views' + action);
                break;
            default:
                history.replaceState('panel-active', '', defaultAction);
                break;
        }
    }

    function changeActivePanel(toPanel, keepHidden) {
        var doCloseSecondaryPanel = false;
        console.log('changeActivePanel', tracking.state.activePanel, toPanel, tracking.state.activeMapMode);
        if (tracking.state.activePanel !== toPanel) {
            // when switching between panels, close the secondary panel if it does not apply to the
            // newly active panel
            closePrimaryNavigation();
            console.log('active panel change: ' + tracking.state.activePanel + '-' + toPanel);
            doCloseSecondaryPanel = true;

            // not convinced this is the best time/place to swap view modes
            if (toPanel === tracking.panels.SHARED_VIEWS) {
                switchViewMode(tracking.viewModes.SHARED_VIEW);
            } else if (tracking.state.activePanel === tracking.panels.SHARED_VIEWS) {
                switchViewMode(tracking.viewModes.NORMAL);
            }
        }
        tracking.state.activePanel = toPanel;
        var primaryPanel = tracking.data.domNodes.panels.primary;
        $('.nav-panel-content', primaryPanel).removeClass('is-visible');
        // content panels are shared except for ADD/GEOFENCES/PLACES
        var contentMode = toPanel;
        var panelFilters = [
            { panel: tracking.panels.ASSETS, filter: document.getElementById('filter-assets'), options: document.getElementById('panel-options-assets') },
            { panel: tracking.panels.GEOFENCES, filter: document.getElementById('filter-fences'), options: document.getElementById('panel-options-fences') },
            { panel: tracking.panels.PLACES, filter: document.getElementById('filter-places'), options: document.getElementById('panel-options-places') },
            { panel: tracking.panels.JOURNEYS, filter: document.getElementById('filter-journeys'), options: document.getElementById('panel-options-journeys') },
            { panel: tracking.panels.SHARED_VIEWS, filter: document.getElementById('filter-shared-views'), options: document.getElementById('panel-options-shared-views') }
        ];
        var itemsForType = _.keyBy(panelFilters, 'panel');

        _.each(primaryPanel.querySelectorAll('.filter-container'), function (item) {
            if (itemsForType[toPanel] !== undefined && itemsForType[toPanel].filter === item) {
                // only show filter box if there's contents
                item.classList.add('is-visible');
            } else {
                item.classList.remove('is-visible');
            }
        });
        _.each(primaryPanel.querySelectorAll('.panel-options-container'), function (item) {
            if (itemsForType[toPanel] !== undefined && itemsForType[toPanel].options === item) {
                item.classList.add('is-visible');
            } else {
                item.classList.remove('is-visible');
            }
        });
        
        if (contentMode === undefined) {
            return;
        }
        setUrlHistoryForActivePanel();
        $('.nav-panel-content[data-panel=' + contentMode + ']', primaryPanel).addClass('is-visible');
        var navPanel = $('ul.nav-panel')[0];
        $('li', navPanel).removeClass('nav-active');
        $('li[data-panel=' + toPanel + ']', navPanel).addClass('nav-active');
        var panelHeader = $('.panel-header', primaryPanel)[0];
        $('.panel-title', panelHeader).removeClass('is-visible');
        $('.panel-title[data-panel=' + toPanel + ']', panelHeader).addClass('is-visible');
        tracking.data.domNodes.simpleBars.primary.recalculate();

        if (doCloseSecondaryPanel && tracking.state.openPanels.secondary) {
            closeSecondaryPanel();
            if (keepHidden) {
                hidePrimaryPanel();
            }
        } else {
            // if a notification panel is open, its content must be refreshed
            updateSecondaryPanelNotificationContentForNewMode();
        }
    }

    function filterDateClick(btn) {
        // TODO this is a mess and should be refactored
        var range = btn.id.substring(8);
        var fromDate = null;
        var historyCustom = document.getElementById('history-custom');
        var historyDateForm = tracking.data.domNodes.mapMode.dateRange;
        var doQuery = true;
        var historyButtons = document.getElementById('filter-history-range').querySelectorAll('button');
        // clicking custom button should not change any others
        if (range !== 'custom') {
            _.each(historyButtons, function (button) {
                if (button.id === 'history-live') {
                    return;
                }
                button.classList.remove('active');
            });
        }
        switch (range) {
            case 'live':
                historyDateForm.classList.remove('is-visible');
                switchMapMode(true);
                return;
            case '1h':
                fromDate = moment().subtract(tracking.user.tickOffset, 'ms').subtract(1, 'hours');
                historyDateForm.classList.remove('is-visible');
                historyCustom.classList.remove('btn-primary');
                break;
            case '24h':
                fromDate = moment().subtract(tracking.user.tickOffset, 'ms').subtract(24, 'hours');
                historyDateForm.classList.remove('is-visible');
                historyCustom.classList.remove('btn-primary');
                break;
            case '7d':
                fromDate = moment().subtract(tracking.user.tickOffset, 'ms').subtract(7, 'days');
                historyDateForm.classList.remove('is-visible');
                historyCustom.classList.remove('btn-primary');
                break;
            case '1m':
                fromDate = moment().subtract(tracking.user.tickOffset, 'ms').subtract(1, 'months');
                historyDateForm.classList.remove('is-visible');
                historyCustom.classList.remove('btn-primary');
                break;
            case 'custom':
                if (historyDateForm.classList.contains('is-visible')) {
                    historyDateForm.classList.remove('is-visible');
                    if (!btn.classList.contains('btn-primary')) {
                        btn.classList.remove('active');
                    }
                } else {
                    if (!(tracking.state.isFirstLoad && tracking.options.isCompact)) {
                        historyDateForm.classList.add('is-visible');
                    }
                }
                doQuery = false;
                break;
        }
        if (fromDate !== null) {
            var historyDateFrom = document.getElementById('txtDateFrom');
            historyDateFrom.value = fromDate.format(tracking.user.dateWithStandardTimeFormat);
            var historyDateTo = document.getElementById('txtDateTo');
            historyDateTo.value = '';
        }

        if (doQuery) {
            if(tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
                if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                    switchMapMode(false, null, true, true);
                } else {
                    queryActiveAssets(null, true);
                }
            } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
                // requery shared view with dates entered in panel, limits apply
                var dateFilter = {
                    from: document.getElementById('txtDateFrom').value,
                    to: document.getElementById('txtDateTo').value
                };
                if (tracking.data.sharedView.temp !== null) {
                    querySharedViewData(tracking.data.sharedView.temp, dateFilter, false);
                } else if (tracking.data.sharedView.current !== null) {
                    querySharedViewData(tracking.data.sharedView.current, dateFilter, false);
                }
            }
        }

        //if (range !== 'custom') {
        btn.classList.add('active');
        //}
    }

    function switchViewMode(toViewMode) {
        if (tracking.state.activeViewMode === toViewMode) {
            return;
        }
        console.log('switchViewMode', toViewMode);

        var switchModePromises = [];
        var switchModePromise = $.Deferred();

        closeSecondaryPanel();

        // change activeViewMode
        hideMapMarkersForViewMode(tracking.state.activeViewMode);

        // set attribute on root node which will handle hiding/showing map control elements
        document.getElementById('track-root').setAttribute('data-view-mode', toViewMode);
        tracking.state.activeViewMode = toViewMode;
        showMapMarkersForViewMode(tracking.state.activeViewMode);

        // TODO disable/enable Live button on map mode... 

        if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
            tracking.data.sharedView.priorMapMode = tracking.state.activeMapMode;
            tracking.data.sharedView.priorMapType = tracking.state.currentMapType;
            tracking.data.sharedView.priorIsSatelliteLabelOverlayEnabled = tracking.data.isSatelliteLabelOverlayEnabled;
            var $dialog = $(tracking.data.domNodes.infoDialogs.sharedViewInformation);
            moveOrOpenDialogRelativeTo($dialog, tracking.map._container, 'center center');
            $dialog.unbind('dialogclose');
        } else {
            document.getElementById('txtDateFrom').value = tracking.data.history.fromDate;
            document.getElementById('txtDateTo').value = tracking.data.history.toDate;
            if (tracking.data.sharedView.priorMapType !== tracking.state.currentMapType) {
                tracking.data.isSatelliteLabelOverlayEnabled = tracking.data.sharedView.priorIsSatelliteLabelOverlayEnabled;
                changeMapType(tracking.data.sharedView.priorMapType);
            }
        }

        updateMapModePanel();
        updateActiveAssetInformation(tracking.state.activeViewMode);
        $.when.apply($, switchModePromises).done(function (promise) {
            switchModePromise.resolve(true);
        });
        return switchModePromise;
    }

    function updateMapModePanel() {
        var historyCustom = document.getElementById('history-custom');
        if (tracking.state.activeViewMode === tracking.viewModes.NORMAL && tracking.state.activeMapMode === tracking.mapModes.LIVE) {
            tracking.data.domNodes.moduleName.textContent = tracking.strings.MODE_LIVE;
            tracking.data.domNodes.assetsModeLive.classList.add('is-visible');
            tracking.data.domNodes.assetsModeHistory.classList.remove('is-visible');
            tracking.data.domNodes.mapMode.container.classList.add('mode-live');
            tracking.data.domNodes.mapMode.container.classList.remove('mode-history');
            var historyButtons = document.getElementById('filter-history-range').querySelectorAll('button');
            _.each(historyButtons, function (button) {
                button.classList.remove('active');
            });
            tracking.data.domNodes.mapMode.dateRange.classList.remove('is-visible');
            tracking.data.domNodes.mapMode.modeLiveButton.classList.add('active');
            historyCustom.classList.remove('btn-primary');
            tracking.data.domNodes.mapMode.container.classList.remove('is-limited');
            tracking.data.domNodes.mapMode.container.classList.remove('has-excess');
            tracking.data.domNodes.mapMode.container.classList.remove('is-filtered');

            // hide calendar
        } else {
            tracking.data.domNodes.moduleName.textContent = tracking.strings.MODE_HISTORY;
            tracking.data.domNodes.assetsModeLive.classList.remove('is-visible');
            tracking.data.domNodes.assetsModeHistory.classList.add('is-visible');
            tracking.data.domNodes.mapMode.container.classList.add('mode-history');
            tracking.data.domNodes.mapMode.container.classList.remove('mode-live');
            tracking.data.domNodes.mapMode.modeLiveButton.classList.remove('active');

            tracking.data.domNodes.mapMode.container.classList.remove('is-filtered');
            tracking.data.domNodes.mapMode.container.classList.remove('has-excess');
            tracking.data.domNodes.mapMode.container.classList.remove('is-limited');
            
            if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
                // if any pre-defined date range buttons are active then hide inactivate custom and hide datepickers
                
                var historyButtons = document.getElementById('filter-history-range').querySelectorAll('button');
                var isPredefinedRange = false;
                _.each(historyButtons, function (button) {
                    if (button.classList.contains('active') && button.id !== 'history-live' && button.id !== 'history-custom') {
                        isPredefinedRange = true;
                    }
                });
                if (isPredefinedRange) {
                    tracking.data.domNodes.mapMode.dateRange.classList.remove('is-visible');
                    historyCustom.classList.remove('btn-primary');
                    historyCustom.classList.remove('active');
                }

                if (tracking.data.history.isLimited || tracking.data.history.limitedData !== null) {
                    tracking.data.domNodes.mapMode.container.classList.add('has-excess');
                    if (tracking.data.history.isLoadedLimitedData || tracking.data.history.limitedData !== null) {
                        tracking.data.domNodes.mapMode.container.classList.add('is-limited');
                        if (tracking.data.history.limitedData !== null) {
                            tracking.data.domNodes.mapMode.container.classList.add('is-filtered');
                        }
                    }
                }
            } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
                if (tracking.data.sharedView.isLimited || tracking.data.sharedView.limitedData !== null) {
                    tracking.data.domNodes.mapMode.container.classList.add('has-excess');
                    if (tracking.data.sharedView.isLoadedLimitedData || tracking.data.sharedView.limitedData !== null) {
                        tracking.data.domNodes.mapMode.container.classList.add('is-limited');
                        if (tracking.data.sharedView.limitedData !== null) {
                            tracking.data.domNodes.mapMode.container.classList.add('is-filtered');
                        }
                    }
                }
            }
            updateLimitedDataResults(tracking.state.activeViewMode);
        }
        updateMapModeDateRange();
    }

    function updateMapModeDateRange() {
        var from = null;
        var to = null;
        if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
            if (tracking.data.history.limitedData !== null) {
                from = tracking.data.history.limitedData.fromDate;
                to = tracking.data.history.limitedData.toDate;
            } else {
                from = tracking.data.history.fromDateFull;
                to = tracking.data.history.toDate;
            }
        } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
            if (tracking.data.sharedView.limitedData !== null) {
                from = tracking.data.sharedView.limitedData.fromDate;
                to = tracking.data.sharedView.limitedData.toDate;
            } else {
                from = tracking.data.sharedView.fromDate;
                to = tracking.data.sharedView.toDate;
            }
        }

        if (to === '' || to === undefined) {
            to = null;
        }
        if (from === '' || from === undefined) {
            from = null;
        }

        if (from === null && to === null) {
            tracking.data.domNodes.mapMode.from.textContent = tracking.strings.ALL_TIME;
            tracking.data.domNodes.mapMode.to.textContent = '';
        } else {
            if (from !== null) {
                tracking.data.domNodes.mapMode.from.textContent = from;
            } else {
                tracking.data.domNodes.mapMode.from.textContent = '';
            }
            if (to === null) {
                tracking.data.domNodes.mapMode.to.textContent = moment().subtract(tracking.user.tickOffset, 'ms').format(tracking.user.dateFormat);//.substring(0, tracking.user.dateFormat.indexOf(' ')) + ' HH:mm');
            } else {
                tracking.data.domNodes.mapMode.to.textContent = to;
            }
        }        
    }

    function switchMapMode(toLive, sensitivity, doHistoryQuery, toPredefinedDateRange, forViewMode) {
        // switching map view modes (i.e. from Live to History)
        sensitivity = sensitivity !== undefined ? sensitivity : null;
        toPredefinedDateRange = toPredefinedDateRange !== undefined ? toPredefinedDateRange : false;
        doHistoryQuery = doHistoryQuery !== undefined ? doHistoryQuery : false;
        forViewMode = forViewMode !== undefined ? forViewMode : tracking.viewModes.NORMAL;
        toLive = toLive !== undefined ? toLive : false;

        if (toLive === null) {
            toLive = false;
        }

        console.log('switchMapMode', toLive, sensitivity, doHistoryQuery, toPredefinedDateRange, forViewMode);

        var switchMapModePromises = [];
        var switchMapModePromise = $.Deferred();

        // do whatever changes are necessary for the passed viewMode (defaulting to current)
        if (forViewMode === tracking.viewModes.NORMAL) {
            // check whether it's already in the selected mode
            if (((toLive && tracking.state.activeMapMode === tracking.mapModes.LIVE)
                || (!toLive && tracking.state.activeMapMode !== tracking.mapModes.LIVE))
                && tracking.state.activeMapMode !== null) {
                return true;
            }

            hideMapMarkersForDataGroup(getAssetDataGroupForViewAndMapModes(forViewMode, tracking.state.activeMapMode));
            tracking.state.activeMapMode = toLive ? tracking.mapModes.LIVE : tracking.mapModes.HISTORY;
            // change the panel
            if (toLive) {
                // query for live positions
                if (!tracking.data.live.isInitialized) {
                    switchMapModePromises.push(queryLiveAssets());
                } else {
                    switchMapModePromises.push(updateLiveAssets());
                }
            } else {
                if (tracking.intervals.positionStatus != null) {
                    clearInterval(tracking.intervals.positionStatus);
                }
                // query for historic positions
                if (doHistoryQuery || !tracking.state.hasQueriedHistory) {
                    if (!toPredefinedDateRange) {
                        // todo: move this crap out of here
                        var historyCustom = document.getElementById('history-custom');
                        filterDateClick(historyCustom);                        
                        historyCustom.classList.add('btn-primary');
                    }
                    switchMapModePromises.push(queryActiveAssets(sensitivity, toPredefinedDateRange));
                }
            }

            showMapMarkersForDataGroup(getAssetDataGroupForViewAndMapModes(forViewMode, tracking.state.activeMapMode));
            _.each(tracking.data.assets, function (asset) {
                updateAssetState(asset, asset.State);
                updateAssetFunctionBadges(getAssetDataGroupForViewAndMapModes(forViewMode, tracking.state.activeMapMode), asset.Id);
                updateTimeBasedNotificationIndicatorsForAsset(asset);
            });
            updateGroupFunctionBadges(getAssetDataGroupForViewAndMapModes(forViewMode, tracking.state.activeMapMode), null, 'asset');
            var groupIds = _.map(tracking.data.groups, 'Id');
            groupIds.push('all-assets');
            _.each(groupIds, function (groupId) {
                updateTimeBasedNotificationIndicatorsForGroup(groupId);
            });
            updateActiveAssetInformation(forViewMode);

            if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                updateLiveFollowStatus();
            }
        } else if (forViewMode === tracking.viewModes.SHARED_VIEW) {
            
        }
        updateMapModePanel();
        
        setUrlHistoryForActivePanel(); // TODO why is this here
        resizeApp(true);

        $.when.apply($, switchMapModePromises).done(function (promise) {
            switchMapModePromise.resolve(true);
        });
        return switchMapModePromise;
    }

    function notificationClick(evt) {
        var rootNode = this.parentNode.parentNode.parentNode.parentNode;
        var assetId = rootNode.getAttribute('data-asset-id');
        var tripId = rootNode.getAttribute('data-trip-id');
        var groupId = rootNode.getAttribute('data-group-id');
        var asset = null;
        var trip = null;
        var group = null;
        var journey = null;
        if (assetId !== null) {
            asset = findAssetById(assetId);
            if (asset === undefined || asset === null) {
                return;
            }
        }
        if (tripId !== null) {
            trip = findTripById(tripId);
            if (trip === undefined || trip === null) {
                return;
            }
        }
        if (groupId !== null && groupId.indexOf('journey-') === 0) {
            journey = findJourneyById(groupId.substring(8));
            if (journey === undefined || journey === null) {
                return;
            }
        } else if (groupId !== null) {
            group = findGroupById(groupId);
            if (group === undefined || group === null) {
                return;
            }
        }

        var actions = ['positions'];
        if (!tracking.user.isAnonymous) {
            actions.push('alerts');
        }
        actions.push('events');
        actions.push('status');
        if (!tracking.user.isAnonymous || tracking.options.allowAnonymousMessaging) {
            actions.push('messages');
        }

        var action = 'positions';

        if (evt.originalEvent !== undefined && evt.originalEvent.offsetX !== undefined) {
            var offsetX = evt.originalEvent.offsetX;
            if (offsetX < 15 && actions.length > 0) {
                action = actions[0];
            } else if (offsetX < 30 && actions.length > 1) {
                action = actions[1];
            } else if (offsetX < 45 && actions.length > 2) {
                action = actions[2];
            } else if (offsetX < 60 && actions.length > 3) {
                action = actions[3];
            } else if(actions.length > 4) {
                action = actions[4];
            }

            switch (action) {
                case 'positions':
                    if (asset !== null) {
                        openPositionsForAsset(asset);
                    } else if (trip !== null) {
                        openPositionsForTrip(trip);
                    } else if (journey !== null) {
                        openPositionsForJourney(journey);
                    } else if (group !== null) {
                        openPositionsForGroup(group);
                    }
                    break;
                case 'events':
                    if (asset !== null) {
                        openEventsForAsset(asset);
                    } else if (trip !== null) {
                        openEventsForTrip(trip);
                    } else if (journey !== null) {
                        openEventsForJourney(journey);
                    } else if (group !== null) {
                        openEventsForGroup(group);
                    }
                    break;
                case 'alerts':
                    if (asset !== null) {
                        openAlertsForAsset(asset);
                    } else if (trip !== null) {
                        openAlertsForTrip(trip);
                    } else if (journey !== null) {
                        openAlertsForJourney(journey);
                    } else if (group !== null) {
                        openAlertsForGroup(group);
                    }
                    break;
                case 'status':
                    if (asset !== null) {
                        openStatusForAsset(asset);
                    } else if (trip !== null) {
                        openStatusForTrip(trip);
                    } else if (journey !== null) {
                        openStatusForJourney(journey);
                    } else if (group !== null) {
                        openStatusForGroup(group);
                    }
                    break;
                case 'activity':
                    if (asset !== null) {
                        openActivityForAsset(asset);
                    } else if (trip !== null) {
                        openActivityForTrip(trip);
                    } else if (journey !== null) {
                        openActivityForJourney(journey);
                    } else if (group !== null) {
                        openActivityForGroup(group);
                    }
                    break;
                case 'messages':
                    // determine whether to open chat or messages
                    var counts;
                    if (asset !== null) {
                        counts = getNotificationCounts(getAssetDataGroupForCurrentViewMode(), assetId);
                    } else if (trip !== null) {
                        counts = getNotificationCounts(tracking.dataGroups.JOURNEY_HISTORY, tripId);
                    } else if (journey !== null) {
                        counts = getNotificationCountsForGroup(tracking.dataGroups.JOURNEY_HISTORY, journey.Id);
                    } else {
                        counts = getNotificationCountsForGroup(getAssetDataGroupForCurrentViewMode(), groupId);
                    }

                    if (tracking.user.isAnonymous || counts.messages === 0 || counts.messagesChat > 0) {
                        if (asset !== null) {
                            openChatForAsset(asset);
                        } else if (trip !== null) {
                            openChatForTrip(trip);
                        } else if (journey !== null) {
                            openChatForJourney(journey);
                        } else if (group !== null) {
                            openChatForGroup(group);
                        }
                    } else {
                        if (asset !== null) {
                            openMessagesForAsset(asset);
                        } else if (trip !== null) {
                            openMessagesForTrip(trip);
                        } else if (journey !== null) {
                            openMessagesForJourney(journey);
                        } else if (group !== null) {
                            openMessagesForGroup(group);
                        }
                    }
                    break;
            }
        }
        evt.preventDefault();
        evt.stopPropagation();
    }

    function getAssetDataGroupForViewAndMapModes(viewMode, mapMode) {
        if (viewMode === tracking.viewModes.NORMAL) {
            if (mapMode === tracking.mapModes.LIVE) {
                return tracking.dataGroups.NORMAL_LIVE;
            } else {
                return tracking.dataGroups.NORMAL_HISTORY;
            }
        } else if (viewMode === tracking.viewModes.SHARED_VIEW) {
            return tracking.dataGroups.SHARED_VIEW_HISTORY;
        }
    }

    function getAssetDataGroupForCurrentViewMode() {
        // TODO store this in tracking.state instead whenever it would change?
        return getAssetDataGroupForViewAndMapModes(tracking.state.activeViewMode, tracking.state.activeMapMode);
    }

    function notificationChangeTitle(evt) {
        // because this is throttled, we may have already had a mouseout
        if (this.doUpdateTooltip === undefined) {
            return;
        }
        var rootNode = this.parentNode.parentNode.parentNode.parentNode;
        var assetId = rootNode.getAttribute('data-asset-id');
        var tripId = rootNode.getAttribute('data-trip-id');
        var groupId = rootNode.getAttribute('data-group-id');
        var asset = null;
        var trip = null;
        var group = null;
        var journey = null;
        if (assetId !== null) {
            asset = findAssetById(assetId);
            if (asset === undefined || asset === null) {
                return;
            }
        }
        if (tripId !== null) {
            trip = findTripById(tripId);
            if (trip === undefined || trip === null) {
                return;
            }
        }
        if (groupId !== null && groupId.indexOf('journey-') === 0) {
            journey = findJourneyById(groupId.substring(8));
            if (journey === undefined || journey === null) {
                return;
            }
        } else if (groupId !== null && groupId !== 'all-assets') {
            group = findGroupById(groupId);
            if (group === undefined || group === null) {
                return;
            }
        }

        if (evt.originalEvent !== undefined && evt.originalEvent.offsetX !== undefined) {
            var counts;
            if (asset !== null) {
                counts = getNotificationCounts(getAssetDataGroupForCurrentViewMode(), assetId);
            } else if (trip !== null) {
                counts = getNotificationCounts(tracking.dataGroups.JOURNEY_HISTORY, tripId);
            } else if (journey !== null) {
                counts = getNotificationCountsForGroup(tracking.dataGroups.JOURNEY_HISTORY, journey.Id);
            } else {
                counts = getNotificationCountsForGroup(getAssetDataGroupForCurrentViewMode(), groupId);
            }

            var offsetX = evt.originalEvent.offsetX;
            if (offsetX === 0) {
                // sometimes offsetX and offsetY are presented as 0 in FF
                offsetX = evt.originalEvent.clientX - 117;
            }
            var titles = [tracking.strings.POSITIONS + ' - ' + counts.positions];
            if (!tracking.user.isAnonymous) {
                titles.push(tracking.strings.ALERTS + ' - ' + counts.alerts);
            }
            titles.push(tracking.strings.EVENTS + ' - ' + counts.events);
            titles.push(tracking.strings.STATUS + ' - ' + counts.status);
            if (!tracking.user.isAnonymous || tracking.options.allowAnonymousMessaging) {
                var title = tracking.strings.MESSAGES + ' - ' + counts.messagesChat;
                if (!tracking.user.isAnonymous) {
                    title += ' / ' + counts.messagesDevice;
                }
                titles.push(title);    
            }            
            if (offsetX < 15 && titles.length > 0) {
                this.setAttribute('data-original-title', titles[0]);
            } else if (offsetX < 30 && titles.length > 1) {
                this.setAttribute('data-original-title', titles[1]);
            } else if (offsetX < 45 && titles.length > 2) {
                this.setAttribute('data-original-title', titles[2]);
            } else if (offsetX < 60 && titles.length > 3) {
                this.setAttribute('data-original-title', titles[3]);
            } else if (titles.length > 4) {
                this.setAttribute('data-original-title', titles[4]);
            }
            $(this).bsTooltip('show');
        }
        evt.preventDefault();
        evt.stopPropagation();
    }

    $('#panel-content-assets,#panel-content-journeys').on('click', '.notifications', notificationClick);
    $('#panel-content-assets,#panel-content-journeys').on('mousemove', '.notifications', _.throttle(notificationChangeTitle, 150));
    // mouseover was not consistently handled between Edge, Chrome and FF to use as a box hover event
    //$('#panel-content-assets').on('mouseover', '.notifications', notificationChangeTitle);
    $('#panel-content-assets,#panel-content-journeys').on('mouseover', '.notifications', function(evt) {
        this.doUpdateTooltip = true;
    });
    $('#panel-content-assets,#panel-content-journeys').on('mouseout', '.notifications', function(evt) {
        delete this.doUpdateTooltip;
        var that = this;
        setTimeout(function () {
            if (that.doUpdateTooltip === undefined) {
                $(that).bsTooltip('hide');
            }
        }, 175);
    });

    function getNotificationCountsForGroup(dataGroup, groupId) {
        //console.log('getNotificationCountsForGroup', groupId, dataGroup);
        // get all unique assets under group, including subgroups
        var itemIds = [];
        if (groupId === 'all-assets') {
            itemIds = _.map(tracking.data.assets, 'Id');
        } else {
            if (dataGroup === tracking.dataGroups.JOURNEY_HISTORY) {
                groupId = 'journey-' + groupId;
            } else if (dataGroup === tracking.dataGroups.SHARED_VIEW_HISTORY) {
                groupId = 'shared-view-' + groupId;
            }
            var status = getGroupAssetStatus(groupId);
            if (dataGroup == tracking.dataGroups.JOURNEY_HISTORY) {
                itemIds = status.tripIds;
            } else if (dataGroup === tracking.dataGroups.SHARED_VIEW_HISTORY) {
                itemIds = status.sharedViewIds;
            } else {
                itemIds = status.assetIds;
            }
        }
        var sum = {
            positions: 0,
            events: 0,
            alerts: 0,
            status: 0,
            messages: 0,
            messagesChat: 0,
            messagesDevice: 0,
            hasHistoryResult: false,
            hasEmergency: false
        };

        _.each(itemIds, function (itemId) {
            var counts = getNotificationCounts(dataGroup, itemId);
            sum.positions += counts.positions;
            sum.events += counts.events;
            sum.alerts += counts.alerts;
            sum.status += counts.status;
            sum.messages += counts.messages;
            sum.messagesChat += counts.messagesChat;
            sum.messagesDevice += counts.messagesDevice;
            if (counts.hasHistoryResult) {
                sum.hasHistoryResult = true;
            }
            if (counts.hasEmergency) {
                sum.hasEmergency = true;
            }
        });
        return sum;
        // get notification counts for each asset in this mode
        // sum them up for the group
    }

    function getNotificationCounts(dataGroup, itemId) {
        //console.log('getNotificationCounts', dataGroup, itemId);
        var positions = 0;
        var alerts = 0;
        var events = 0;
        var status = 0;
        var messages = 0;
        var messagesChat = 0;
        var messagesDevice = 0;

        var assetEvents = [];
        var hasHistoryResult = true;
        if (dataGroup === tracking.dataGroups.NORMAL_LIVE) {
            // todo: this should return unread counts only, not the sum of events that are recorded
            if (tracking.data.live.normalizedPositionsByAssetId[itemId] !== undefined) {
                positions = tracking.data.live.normalizedPositionsByAssetId[itemId].length;
            }
            if (tracking.data.live.normalizedEventsByAssetId[itemId] !== undefined) {
                assetEvents = tracking.data.live.normalizedEventsByAssetId[itemId];
            }
            if (tracking.data.live.messageCountsByAssetId[itemId] !== undefined) {
                messages = tracking.data.live.messageCountsByAssetId[itemId].FromMobile + tracking.data.live.messageCountsByAssetId[itemId].ToMobile;
                messagesChat = tracking.data.live.messageCountsByAssetId[itemId].FromMobileChats + tracking.data.live.messageCountsByAssetId[itemId].ToMobileChats;
                messagesDevice = tracking.data.live.messageCountsByAssetId[itemId].FromMobileDevice + tracking.data.live.messageCountsByAssetId[itemId].ToMobileDevice;
            }
        } else if (dataGroup === tracking.dataGroups.NORMAL_HISTORY) {
            if (tracking.data.history.normalizedPositionsByAssetId[itemId] !== undefined) {
                positions = tracking.data.history.normalizedPositionsByAssetId[itemId].length;
                hasHistoryResult = tracking.data.history.assetIdsWithResults[itemId] !== undefined;
            }
            if (tracking.data.history.normalizedEventsByAssetId[itemId] !== undefined) {
                assetEvents = tracking.data.history.normalizedEventsByAssetId[itemId];
            }
            if (tracking.data.history.messageCountsByAssetId[itemId] !== undefined) {
                messages = tracking.data.history.messageCountsByAssetId[itemId].FromMobile + tracking.data.history.messageCountsByAssetId[itemId].ToMobile;
                messagesChat = tracking.data.history.messageCountsByAssetId[itemId].FromMobileChats + tracking.data.history.messageCountsByAssetId[itemId].ToMobileChats;
                messagesDevice = tracking.data.history.messageCountsByAssetId[itemId].FromMobileDevice + tracking.data.history.messageCountsByAssetId[itemId].ToMobileDevice;
            }
        } else if (dataGroup === tracking.dataGroups.JOURNEY_HISTORY) {
            if (tracking.data.trips.normalizedPositionsByTripId[itemId] !== undefined) {
                positions = tracking.data.trips.normalizedPositionsByTripId[itemId].length;
                hasHistoryResult = tracking.data.trips.tripIdsWithResults[itemId] !== undefined;
            }
            if (tracking.data.trips.normalizedEventsByTripId[itemId] !== undefined) {
                assetEvents = tracking.data.trips.normalizedEventsByTripId[itemId];
            }
            if (tracking.data.trips.messageCountsByTripId[itemId] !== undefined) {
                messages = tracking.data.trips.messageCountsByTripId[itemId].FromMobile + tracking.data.trips.messageCountsByTripId[itemId].ToMobile;
                messagesChat = tracking.data.trips.messageCountsByTripId[itemId].FromMobileChats + tracking.data.trips.messageCountsByTripId[itemId].ToMobileChats;
                messagesDevice = tracking.data.trips.messageCountsByTripId[itemId].FromMobileDevice + tracking.data.trips.messageCountsByTripId[itemId].ToMobileDevice;
            }
        } else if (dataGroup === tracking.dataGroups.SHARED_VIEW_HISTORY) {
            positions = tracking.data.sharedView.normalizedPositions.length;
            assetEvents = tracking.data.sharedView.normalizedEvents;
            messages = tracking.data.sharedView.messageCounts.FromMobile + tracking.data.sharedView.messageCounts.ToMobile;
            messagesChat = tracking.data.sharedView.messageCounts.FromMobileChats + tracking.data.sharedView.messageCounts.ToMobileChats;
            messagesDevice = tracking.data.sharedView.messageCounts.FromMobileDevice + tracking.data.sharedView.messageCounts.ToMobileDevice;
        }

        assetEvents = _.filter(assetEvents, function (event) { return !event.Event.Hide; });
        events = assetEvents.length;
        // alerts
        var textEvents = _.filter(assetEvents, function (event) { return !event.Event.Hide && tracking.data.EVENTS_TEXT_MESSAGE.indexOf(event.Event.Type) !== -1; });
        var alertEvents = _.filter(assetEvents, function (event) { return !event.Event.Hide && tracking.data.EVENTS_ALERT.indexOf(event.Event.Type) !== -1; });
        var emergencyEvents = _.filter(assetEvents, function (event) { return !event.Event.Hide && tracking.data.EVENTS_EMERGENCY.indexOf(event.Event.Type) !== -1; });
        var hasEmergency = emergencyEvents.length > 0;
        alerts = alertEvents.length + emergencyEvents.length;
        // status - really a change in position .State
        // but we'll just go with specific event types for now as the event may not have an associated position
        var statusEvents = _.filter(assetEvents, function (event) {
            return !event.Event.Hide && tracking.data.EVENTS_STATUS.indexOf(event.Event.Type) !== -1;
        });
        status = statusEvents.length;

        events = events - alerts - status - textEvents.length;

        var result = {
            positions: positions,
            events: events,
            alerts: alerts,
            status: status,
            messages: messages,
            messagesChat: messagesChat,
            messagesDevice: messagesDevice,
            hasHistoryResult: hasHistoryResult,
            hasEmergency: hasEmergency
        };
        return result;
    }

    function updateNotificationsInSecondaryPanel(type, itemId, counts) {
        var panel = tracking.data.domNodes.panels.secondary;
        if (panel.getAttribute('data-item-type') !== type) {
            return;
        }
        if (panel.getAttribute('data-item-id') !== itemId.toString()) {
            return;
        }

        // update notification counts for tabs in secondary panel
        var tabs = document.getElementById('panel-secondary-nav-tabs');
        var badgePositions = tabs.querySelector('#nav-positions-tab .badge');
        var badgeAlerts = tabs.querySelector('#nav-alerts-tab .badge');
        var badgeEvents = tabs.querySelector('#nav-events-tab .badge');
        var badgeStatus = tabs.querySelector('#nav-status-tab .badge');
        var badgeChat = tabs.querySelector('#nav-chat-tab .badge');
        var badgeMessages = tabs.querySelector('#nav-messages-tab .badge');

        updateBadgesWithCounts(badgePositions, badgeAlerts, badgeEvents, badgeStatus, badgeChat, badgeMessages, counts);
    }

    function updateBadgesWithCounts(badgePositions, badgeAlerts, badgeEvents, badgeStatus, badgeChat, badgeMessages, counts) {
        if (badgePositions !== null) {
            if (counts.positions > 0) {
                badgePositions.classList.add('active');
            } else {
                badgePositions.classList.remove('active');
            }
            badgePositions.textContent = counts.positions;
        }

        if (badgeAlerts !== null) {
            if (counts.alerts > 0) {
                badgeAlerts.classList.add('active');
            } else {
                badgeAlerts.classList.remove('active');
            }
            if (counts.hasEmergency) {
                badgeAlerts.classList.remove('bg-alert');
                badgeAlerts.classList.add('bg-emergency');
            } else {
                badgeAlerts.classList.add('bg-alert');
                badgeAlerts.classList.remove('bg-emergency');
            }
            var parent = badgeAlerts.parentNode;
            if (parent.classList.contains('nav-item')) {
                if (counts.hasEmergency) {
                    parent.classList.add('notify-emergency-tab');
                    parent.classList.remove('notify-alert-tab');
                } else {
                    parent.classList.remove('notify-emergency-tab');
                    parent.classList.add('notify-alert-tab');
                }
            }
            badgeAlerts.textContent = counts.alerts;
        }

        if (badgeEvents !== null) {
            if (counts.events > 0) {
                badgeEvents.classList.add('active');
            } else {
                badgeEvents.classList.remove('active');
            }
            badgeEvents.textContent = counts.events;
        }

        if (badgeStatus !== null) {
            if (counts.status > 0) {
                badgeStatus.classList.add('active');
            } else {
                badgeStatus.classList.remove('active');
            }
            badgeStatus.textContent = counts.status;
        }

        if (badgeMessages !== null) {
            if (counts.messagesDevice > 0) {
                badgeMessages.classList.add('active');
            } else {
                badgeMessages.classList.remove('active');
            }
            badgeMessages.textContent = counts.messagesDevice;
        }

        if (badgeChat !== null) {
            if (counts.messagesChat > 0) {
                badgeChat.classList.add('active');
            } else {
                badgeChat.classList.remove('active');
            }
            badgeChat.textContent = counts.messagesChat;
        }
    }

    function updateGroupFunctionBadges(dataGroup, itemIds, type) {
        //console.log('update group badges ' + type + ': ' + (itemIds !== null ? itemIds.join(',') : 'all'));
        var nodeList = tracking.data.domNodes.groups; // todo: group types
        var groupIds = [];
        switch (type) {
            case 'asset':
                groupIds.push('all-assets');
                // update appropriate asset groups and all-assets
                // get group ids that contain asset
                if (itemIds === null) {
                    itemIds = _.map(tracking.data.assets, 'Id');
                }
                _.each(itemIds, function (itemId) {
                    _.each(tracking.data.groups, function (group) {
                        if (group.AssetIds.indexOf(itemId) !== -1) {
                            groupIds.push(group.Id);
                        }
                    });
                });
                break;
            case 'trip':
                // update related journeys
                if (itemIds === null) {
                }
                dataGroup = tracking.dataGroups.JOURNEY_HISTORY;
                _.each(itemIds, function (tripId) {
                    var trip = findTripById(tripId);
                    groupIds.push(trip.JourneyId);
                });
                break;
        }
        groupIds = _.uniq(groupIds);
        _.each(groupIds, function(groupId) {
            var groupCounts = getNotificationCountsForGroup(dataGroup, groupId);
            if (dataGroup === tracking.dataGroups.JOURNEY_HISTORY) {
                groupId = 'journey-' + groupId;
            }
            updateBadgesForNode(nodeList[groupId].querySelector('.group-info'), dataGroup, groupCounts);
            var notificationType = 'groups';
            if (dataGroup === tracking.dataGroups.JOURNEY_HISTORY) {
                notificationType = 'journeys';
            }
            updateNotificationsInSecondaryPanel(notificationType, groupId, groupCounts);
        });
    }

    function updateBadgesForNode(node, dataGroup, counts) {
        var badgePositions = node.querySelector('[data-action="asset-positions"] .badge');
        var badgeAlerts = node.querySelector('[data-action="asset-alerts"] .badge');
        var badgeEvents = node.querySelector('[data-action="asset-events"] .badge');
        var badgeStatus = node.querySelector('[data-action="asset-status"] .badge');
        var badgeMessages = node.querySelector('[data-action="asset-messages"] .badge');
        var badgeChat = node.querySelector('[data-action="asset-chat"] .badge');
        updateBadgesWithCounts(badgePositions, badgeAlerts, badgeEvents, badgeStatus, badgeChat, badgeMessages, counts);

        var notifications = node.querySelector('.notifications');
        // in history mode there can be a position returned outside of the queried time, so hasHistoryResult is used to differentiate
        if ((dataGroup === tracking.dataGroups.NORMAL_HISTORY || dataGroup === tracking.dataGroups.JOURNEY_HISTORY || dataGroup === tracking.dataGroups.SHARED_VIEW_HISTORY) && counts.positions > 0 && counts.hasHistoryResult) {
            notifications.classList.add('recent-positions');
        } else if ((dataGroup === tracking.dataGroups.NORMAL_LIVE) && counts.positions > 0) {
            notifications.classList.add('recent-positions');
        } else {
            notifications.classList.remove('recent-positions');
        }

        if (counts.alerts > 0) {
            notifications.classList.add('recent-alerts');
        } else {
            notifications.classList.remove('recent-alerts');
        }

        if (counts.hasEmergency) {
            notifications.classList.add('has-emergency');
        } else {
            notifications.classList.remove('has-emergency');
        }

        if (counts.events > 0) {
            notifications.classList.add('recent-events');
        } else {
            notifications.classList.remove('recent-events');
        }

        if (counts.status > 0) {
            notifications.classList.add('recent-status');
        } else {
            notifications.classList.remove('recent-status');
        }

        if (counts.messages > 0) {
            notifications.classList.add('recent-messages');
        } else {
            notifications.classList.remove('recent-messages');
        }
    }

    function updateAssetFunctionBadges(dataGroup, itemId) {
        // todo: performance, these should be stored instead of recalculated every time
        var counts = getNotificationCounts(dataGroup, itemId);

        if (dataGroup !== tracking.dataGroups.SHARED_VIEW_HISTORY) {
            var nodeList = tracking.data.domNodes.assets;
            if (dataGroup === tracking.dataGroups.JOURNEY_HISTORY) {
                nodeList = tracking.data.domNodes.trips;
            }  

            var items = nodeList[itemId];
            if (!_.isArray(items)) {
                items = [items];
            }

            _.each(items, function (node) {
                updateBadgesForNode(node, dataGroup, counts);
            });
        }

        var notificationType = 'assets';
        if (dataGroup === tracking.dataGroups.JOURNEY_HISTORY) {
            notificationType = 'trips';
        } else if (dataGroup === tracking.dataGroups.SHARED_VIEW_HISTORY) { 
            notificationType = 'shared-views';
        }
        updateNotificationsInSecondaryPanel(notificationType, itemId, counts);
    }

    function updateLatestPositionTimeForSharedView() {
        var latest = _.maxBy([tracking.data.live.latestPosition, tracking.data.history.latestPosition], function(item) { return item == null ? 0 : item.Epoch; });
        if (latest != null) {
            $('#shared-view-last-event').text(latest.Time);
        } else {
            $('#shared-view-last-event').text(tracking.strings.NEVER);
        }
    }

    function updateAssetEventNotificationTime(event) {
        if (tracking.data.EVENTS_ALERT.indexOf(event.Type) !== -1 || tracking.data.EVENTS_EMERGENCY.indexOf(event.Type) !== -1) {
            updateAssetNotificationTime(event.AssetId, 'alerts', event.Epoch);
        } else if (tracking.data.EVENTS_STATUS.indexOf(event.Type) !== -1) {
            updateAssetNotificationTime(event.AssetId, 'status', event.Epoch);
        } else if (tracking.data.EVENTS_TEXT_MESSAGE.indexOf(event.Type) !== -1) {
            // messages handled by .messagesByAssetId
        } else {
            updateAssetNotificationTime(event.AssetId, 'events', event.Epoch);
        }
    }

    function updateAssetNotificationTime(assetId, type, value) {
        if (tracking.data.live.notificationTimesByAssetId[assetId] === undefined) {
            tracking.data.live.notificationTimesByAssetId[assetId] = {
                positions: null,
                events: null,
                alerts: null,
                status: null,
                messages: null
            };
        }

        if (type !== 'messages') {
            value *= 1000; // epoch seconds to epoch millis for consistency
        }

        var currentValue = tracking.data.live.notificationTimesByAssetId[assetId][type];
        if (currentValue === null || (value > currentValue)) {
            tracking.data.live.notificationTimesByAssetId[assetId][type] = value;
            // todo: update groups here as well, but make sure the above is not in a loop
        }
    }

    function getDbg() {
        return {
            u: tracking.user.id,
            d: tracking.options.isDemo,
            a: tracking.user.isAnonymous,
            i: tracking.user.isImpersonated,
            m: tracking.state.isMobile,
            s: tracking.state.isSafari
        };
    }

    function updateLiveAssets() {
        if (tracking.data.pending.live) {
            return;
        }

        tracking.data.pending.live = true;
        // if previous query hasn't returned, don't send another
        tracking.log('Updating Live Assets');
        var data = {
            request: {
                lang: tracking.user.dateCulture,
                dbg: getDbg()
            }            
        };
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetLatestPositionsForAssetsReq'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                tracking.data.pending.live = false;
                if (msg.d) {
                    var newPositions = msg.d;
                    var updatePositions = false;
                    var isMarkerActive = false;
                    var isNewFollowPosition = false;
                    for (var i = 0; i < newPositions.length; i++) {
                        // asset/position
                        var latestPosition = newPositions[i];
                        var assetId = latestPosition.AssetId;
                        var asset = findAssetById(assetId);
                        if (asset == null) { // unknown asset update
                            continue;
                        }
                        var newPosition = latestPosition.Position;
                        var isActive = tracking.state.activeMapMode === tracking.mapModes.LIVE
                            && !isItemIncluded(tracking.user.displayPreferences.hiddenAssets, assetId);

                        if (tracking.data.live.latestPosition == null
                            || tracking.data.live.latestPosition.Epoch < newPosition.Epoch) {
                            tracking.data.live.latestPosition = newPosition;
                        }

                        // if the position has changed (newer), update it
                        var isNewPosition = false;
                        var priorPosition = tracking.data.live.latestPositionsByAssetId[assetId];
                        if (priorPosition !== undefined) {
                            priorPosition = priorPosition.Position;
                            if (priorPosition.Id !== newPosition.Id) {
                                // replace old position with new
                                tracking.log('New position for ' + asset.Name + ' at ' + newPosition.Time + '. Updating.');

                                var priorPositionMarker = tracking.data.live.markersByPositionId[priorPosition.Id];

                                isMarkerActive = priorPositionMarker !== undefined && priorPositionMarker.data.selected;

                                // push old position into a history list to support persisted event position information
                                if (asset.Positions == null) {
                                    asset.Positions = [priorPosition];
                                } else {
                                    asset.Positions.push(priorPosition);
                                    // this needs to be limited otherwise we will have issues with memory
                                    // for long-running portal users
                                    if (asset.Positions.length > 50) {
                                        asset.Positions.splice(0, 1);
                                    }
                                }

                                // push old marker into live history list for asset (max 10 spots)
                                // and update the history line -- same color as asset
                                if (!newPosition.IsHidden) {
                                    if (asset.DrawLinesBetweenPositions) {
                                        if (tracking.data.live.mapLinesByAssetId[asset.Id] === undefined) {
                                            // init line
                                            var livePath = [];
                                            livePath.push(L.latLng(priorPosition.Lat, priorPosition.Lng));
                                            livePath.push(L.latLng(newPosition.Lat, newPosition.Lng));
                                            var color = convertNamedColorToHex(asset.Color);

                                            tracking.data.live.mapLinesByAssetId[asset.Id] = L.polyline(livePath, {
                                                weight: 4,
                                                color: color,
                                                opacity: 0.75
                                            });
                                            if (isActive) {
                                                addItemToMap(tracking.data.live.mapLinesByAssetId[asset.Id]);
                                            }
                                        } else {
                                            // add *current* position to live path
                                            // pop oldest position off if more than 10
                                            var currentLivePath = tracking.data.live.mapLinesByAssetId[asset.Id].getLatLngs();
                                            if (currentLivePath.length >= tracking.options.maxLiveTrailPositions) {
                                                currentLivePath.splice(0, 1);
                                            }
                                            currentLivePath.push(L.latLng(newPosition.Lat, newPosition.Lng));
                                            tracking.data.live.mapLinesByAssetId[asset.Id].setLatLngs(currentLivePath);
                                        }
                                    }
                                }

                                // remove old marker
                                if (priorPositionMarker !== undefined) {
                                    $(priorPositionMarker.getElement()).bsTooltip('dispose');
                                    removeItemFromMap(priorPositionMarker);
                                    priorPositionMarker.data.hide = true;
                                }

                                isNewPosition = true;
                            }
                        } else {
                            // new position for asset which doens't have a previous position
                            tracking.log("New position for asset #" + assetId + "... No previous.");
                            isNewPosition = true;
                        }

                        if (isNewPosition) {
                            updatePositions = true;
                            // add new marker
                            var point = L.latLng(newPosition.Lat, newPosition.Lng);
                            var newPositionMarker = addPositionMarkerToPoint(point, false, newPosition, asset, 255, null, null, null, tracking.mapModes.LIVE);

                            // toggle visibility
                            if (isActive && !newPosition.IsHidden) {
                                addItemToMap(newPositionMarker);
                            } else {
                                removeItemFromMap(newPositionMarker);
                            }

                            if (priorPosition !== undefined) {
                                var existingIndex = _.findIndex(tracking.data.live.latestPositions, function (item) { return item.Position.Id === priorPosition.Id; });
                                if (existingIndex !== -1) {
                                    tracking.data.live.latestPositions.splice(existingIndex, 1);
                                }
                            }
                            // normalized position
                            if (tracking.data.positionsById[newPosition.Id] === undefined) {
                                tracking.data.positionsById[newPosition.Id] = normalizeAssetData(asset.Id, 'position', latestPosition);
                            }

                            tracking.data.live.latestPositions.push(tracking.data.positionsById[newPosition.Id]);
                            tracking.data.live.latestPositionsByAssetId[assetId] = tracking.data.positionsById[newPosition.Id];
                            updateAssetNotificationTime(assetId, 'positions', latestPosition.Position.Epoch);
                            tracking.data.live.positions.push(latestPosition);
                            
                            tracking.data.live.normalizedPositions.push(tracking.data.positionsById[newPosition.Id]);
                            if (tracking.data.live.normalizedPositionsByAssetId[assetId] === undefined) {
                                tracking.data.live.normalizedPositionsByAssetId[assetId] = [];
                            }
                            tracking.data.live.normalizedPositionsByAssetId[assetId].push(tracking.data.positionsById[newPosition.Id]);

                            if (tracking.data.live.positionsByAssetId[assetId] === undefined) {
                                tracking.data.live.positionsByAssetId[assetId] = [];
                            }
                            tracking.data.live.positionsByAssetId[assetId].push(latestPosition);
                            if (tracking.data.live.markersByAssetId[assetId] === undefined) {
                                tracking.data.live.markersByAssetId[assetId] = [];
                            }
                            tracking.data.live.markersByAssetId[assetId].push(newPositionMarker);
                            tracking.data.live.markersByPositionId[newPosition.Id] = newPositionMarker;

                            if (isMarkerActive) {
                                // this position was open with live already
                                // act like it was just clicked
                                markerClick(newPositionMarker, 'position', null, false);
                            }

                            // update position history for this asset
                            updateLiveDataForAsset(asset, false, newPosition);

                            // if tracking this asset, automatically highlight this new position
                            if (!isNewFollowPosition && tracking.state.liveFollow.isActive && ($.inArray(asset, tracking.state.liveFollow.assets) !== -1)) {
                                isNewFollowPosition = true;
                            }

                            // check for bouncy event
                            checkMarkerBounce(newPositionMarker);
                        }
                    }

                    if (isNewFollowPosition) {
                        openMostRecentLiveFollowPosition();
                    }

                    if (updatePositions) {
                        tracking.throttles.updatePositionStatus();
                    }
                }
            },
            error: function (xhr, status, error) {
                tracking.data.pending.live = false;
                utility.handleWebServiceError('Error querying live assets.');
            }
        });
    }

    function checkMarkerBounce(marker) {
        if (marker == null) {
            return;
        }
        var location = marker.data.location;
        if (location == null) {
            return;
        }
        if (location.Events == null) {
            return;
        }
        var bouncyEvents = tracking.options.bounceOnEvents;
        for (var i = 0; i < location.Events.length; i++) {
            var evt = location.Events[i];
            if (bouncyEvents.indexOf(evt.Type) !== -1) {
                if (!marker.isBouncing()) {
                    marker.setBouncingOptions({
                        bounceHeight: 15,
                        bounceSpeed: 150,
                        exclusive: false
                    }).toggleBouncing();
                }
                if (marker.bounceTimeout != null) {
                    clearTimeout(marker.bounceTimeout);
                }
                marker.bounceTimeout = setTimeout(function () { if (marker.isBouncing()) { marker.stopBouncing(); } }, tracking.options.bounceForDuration);
                return;
            }
        }
    }

    function showDefaultAsset() {
        if (tracking.state.activeMapMode !== tracking.mapModes.LIVE) {
            return;
        }

        if (!tracking.state.isFirstLoad) {
            return false;
        }

        if ((tracking.options.showAssetId == '') && (tracking.options.showAssetUniqueId == '')) {
            return false;
        }

        var asset = null;
        if (tracking.options.showAssetUniqueId != '') {
            asset = findAssetByUniqueId(tracking.options.showAssetUniqueId);
        }
        if (tracking.options.showAssetId != '') {
            asset = findAssetById(tracking.options.showAssetId);
        }

        if (asset == null) {
            return false;
        }

        openAssetLatestPosition(asset);

        if (tracking.user.isAnonymous) {
            liveFollowAsset(asset);
        }
        return true;
    }

    function queryGroupsAndAssets() {
        toggleLoadingMessage(true, 'assetGroups');
        var data = {
            request: {
                dbg: getDbg()
            }
        };
        return $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetGroupsAndAssetsReq'),
            data: JSON.stringify(data),
            contentType: 'application/json',
            dataType: 'json'
         })
         .done(function (msg) {
            if (!msg.d) {
                return;
            }
            toggleLoadingMessage(false, 'assetGroups');
            var result = msg.d;
            tracking.data.devices = result.Devices;
            tracking.data.devicesById = _.keyBy(tracking.data.devices, 'Id');
            tracking.data.assets = result.Assets;
            _.each(tracking.data.assets, function (asset) {
                asset.ColorSorted = convertHexToSortable(convertNamedColorToHex(asset.Color));
            });
            tracking.data.assetsById = _.keyBy(tracking.data.assets, 'Id');
            tracking.data.fences = result.Fences;
            _.each(tracking.data.fences, function (fence) {
                fence.ColorSorted = convertHexToSortable(fence.Color);
            });
            tracking.data.fencesById = _.keyBy(tracking.data.fences, 'Id');
            tracking.data.sharedViews = result.SharedViews;
            _.each(tracking.data.sharedViews, function (sharedView) {
                sharedView.ColorSorted = convertHexToSortable(sharedView.Color);
                sharedView.Link = getSharedViewLink(sharedView); // TODO remove this
            });
            tracking.data.sharedViewsById = _.keyBy(tracking.data.sharedViews, 'Id');
            tracking.data.places = result.Places;
            _.each(tracking.data.places, function (place) {
                place.ColorSorted = convertHexToSortable(convertNamedColorToHex(place.Color));
            });
            tracking.data.placesById = _.keyBy(tracking.data.places, 'Id');
            tracking.data.journeys = result.Journeys;
            _.each(tracking.data.journeys, function (journey) {
                _.each(journey.Trips, function (trip) {
                    trip.JourneyId = journey.Id;
                });
            });
            tracking.data.journeysById = _.keyBy(tracking.data.journeys, 'Id');
            tracking.data.waypoints = result.Waypoints;
            tracking.data.driverStatuses = result.DriverStatuses;
            tracking.data.attributes = result.Attributes;
            tracking.data.attributeGroups = result.AttributeGroups;
            _.each(result.Groups, function (group) { group.Groups = []; group.GroupIds = []; group.Type = 'assets'; });
            tracking.data.groups = result.Groups;
            var allAssetsGroup = {
                Id: 'all-assets',
                Name: tracking.strings.ALL_ASSETS,
                Color: getBackgroundColorAsHex(document.getElementById('nav-toggle')), // todo: pass default colors in here, or extract from CSS value? window.getComputedStyle(document.getElementById('group-all-assets)).borderLeftColor // RBG
                ParentGroupId: null,
                GroupIds: [],
                Groups: [],
                AssetIds: [],
                DestinationId: "",
                IsDefault: true,
                Type: 'assets'
            };
            tracking.data.groups.splice(0, 0, allAssetsGroup);
            _.each(tracking.data.groups, function (group) {
                group.ColorSorted = convertHexToSortable(group.Color);
            });
            tracking.data.groupsById = _.keyBy(tracking.data.groups, 'Id');

            // load preferences for asset/group/fence UI show/hide
            loadDisplayPreferences();
        })
        .fail(function (xhr, status, error) {
            utility.handleWebServiceError(tracking.strings.MSG_QUERY_GROUPS_ERROR);
            toggleLoadingMessage(false, 'assetGroups');
        });
    }

    function queryUsersForAssets() {
        toggleLoadingMessage(true, 'assetUsers');
        var data = {
            request: {
                dbg: getDbg()
            }
        };
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetUsersForAssetsReq'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json'
        })
        .done(function (msg) {
            if (!msg.d) {
                return;
            }

            toggleLoadingMessage(false, 'assetUsers');
            var result = msg.d;
            tracking.data.users = result.Users;
            tracking.data.assetUsers = result.AssetUsers;
            tracking.data.assetForms = result.AssetForms;
            tracking.data.drivers = result.Drivers;
            tracking.data.assetDrivers = result.AssetDrivers;
            tracking.data.driverGroups = _.each(result.DriverGroups, function (driverGroup) {
                driverGroup.Groups = [];
                driverGroup.GroupIds = [];
            });
            tracking.data.driverGroupsById = _.keyBy(tracking.data.driverGroups, 'Id');
            _.each(tracking.data.driverGroups, function (driverGroup) {
                if (driverGroup.ParentGroupId !== null && tracking.data.driverGroupsById[driverGroup.ParentGroupId] !== undefined) {
                    tracking.data.driverGroupsById[driverGroup.ParentGroupId].GroupIds.push(driverGroup.Id);
                }
            });
            sortItemGroups(tracking.data.driverGroups);
            tracking.data.assetDriverGroups = result.AssetDriverGroups;
            tracking.data.fenceUsers = result.FenceUsers;
            tracking.data.placeUsers = result.PlaceUsers;
            tracking.data.assetGroupUsers = result.AssetGroupUsers;
        })
        .fail(function (xhr, status, error) {
            utility.handleWebServiceError(tracking.strings.MSG_QUERY_USERS_ERROR);
            toggleLoadingMessage(false, 'assetUsers');
        });
    }

    function testGetGroupsAndAssets() {
        return $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetGroupsAndAssets'),
            data: "{}",
            contentType: 'application/json; charset=utf-8',
            dataType: 'json'
        });
    }

    function testGetAssetUsers() {
        return $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetUsersForAssets'),
            data: "{}",
            contentType: 'application/json; charset=utf-8',
            dataType: 'json'
        });
    }

    function testGetLivePositions() {
        return $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetLatestPositionsForAssets'),
            data: "{}",
            contentType: 'application/json; charset=utf-8',
            dataType: 'json'
        });
    }

    function testGetLatestNotifications() {
        return $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetLatestNotificationsForUser'),
            data: JSON.stringify({ lastMessageId: tracking.data.lastNotificationId }),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json'
        });
    }

    function testGetAlertsRequiringAcknowledgement() {
        var eventId;
        if (tracking.data.lastAlertId == null) {
            eventId = null;
        } else {
            eventId = tracking.data.lastAlertId;
        }
        return $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetAlertsRequiringAcknowledgment'),
            data: JSON.stringify({ eventId: eventId }),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json'
        });
    }

    function testLoadup() {
        tracking.log('Async loading test.');

        var getAssetUsers = testGetAssetUsers();
        var getLivePositions = testGetLivePositions();
        var getLatestNotifications = testGetLatestNotifications();
        var getAlertsRequiringAcknowledgement = testGetAlertsRequiringAcknowledgement();
        var getGroupsAndAssets = testGetGroupsAndAssets();

        var preload = $.when(getGroupsAndAssets, getAssetUsers, getLivePositions).done(function (groupsAndAssets, assetUsers, livePositions) {
            tracking.log('Loaded Groups, users and positions');
        });

        $.when(preload, getLatestNotifications, getAlertsRequiringAcknowledgement).done(function (preload, latestNotifications, alertsRequiringAcknowledgement) {
            tracking.log('All loaded (notifications/alerts/required)')
        });
    }

    function testSemiSequential() {
        tracking.log('Semi Sequential started');
        var first = $.when(testGetGroupsAndAssets(), testGetLivePositions()).then(function () {
            tracking.log("Groups, live done");
            var second = $.when(testGetAssetUsers()).then(function () {
                tracking.log("Users done");
                var third = $.when(testGetLatestNotifications(), testGetAlertsRequiringAcknowledgement()).done(function (x, y) {
                    tracking.log("semi sequential complete");
                });
            });
        });
    }

    function testSequential() {
        tracking.log('Sequential started');
        var sequence = testGetGroupsAndAssets()
            .then(testGetLivePositions)
            .then(testGetAssetUsers)
            .then(testGetLatestNotifications)
            .then(testGetAlertsRequiringAcknowledgement)
            .done(function (x) {
                tracking.log('Sequential completed');
            });
        tracking.log("Sequence moving on.");
        $.when(sequence).done(function (s) {
            tracking.log("Sequence done.");
        });
    }

    function queryLiveAssets() {
        tracking.log('Querying live assets.');
        var data = {
            request: {
                lang: tracking.user.dateCulture,
                dbg: getDbg()
            }
        };
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetLatestPositionsForAssetsReq'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                if (msg.d) {
                    tracking.state.hasQueriedLive = true;
                    tracking.log('Live assets results received.');
                    tracking.data.live.positions = msg.d;
                    tracking.data.live.normalizedPositions = [];
                    tracking.data.live.positionsByAssetId = _.groupBy(tracking.data.live.positions, 'AssetId');
                    
                    tracking.data.live.messageCounts = [];
                    tracking.data.live.messageCountsByAssetId = {};
                    // only returns assets with at least one position -- if an asset has no positions it will not be included
                    var isNewFollowPosition = false;
                    for (var i = 0; i < tracking.data.live.positions.length; i++) {
                        // asset/position
                        var assetId = tracking.data.live.positions[i].AssetId;
                        var asset = findAssetById(assetId);
                        if (asset === undefined || asset === null) {
                            continue;
                        }
                        var position = tracking.data.live.positions[i].Position;

                        if (tracking.data.positionsById[position.Id] === undefined) {
                            tracking.data.positionsById[position.Id] = normalizeAssetData(asset.Id, 'position', tracking.data.live.positions[i].Position);
                        }
                        tracking.data.live.normalizedPositions.push(tracking.data.positionsById[position.Id]);

                        var isActive = tracking.state.activeMapMode === tracking.mapModes.LIVE
                            && !isItemIncluded(tracking.user.displayPreferences.hiddenAssets, asset.Id);

                        // add position to map
                        var point = L.latLng(position.Lat, position.Lng);
                        var positionMarker = addPositionMarkerToPoint(point, false, position, asset, 255, null, null, null, tracking.mapModes.LIVE);

                        // toggle visibility
                        if (isActive && !position.IsHidden) {
                            addItemToMap(positionMarker);
                        } else {
                            removeItemFromMap(positionMarker);
                        }

                        if (!isNewFollowPosition && tracking.state.liveFollow.isActive && _.indexOf(tracking.state.liveFollow.assets, asset) !== -1) {
                            isNewFollowPosition = true;
                        }
                    }

                    tracking.data.live.latestPositions = tracking.data.live.normalizedPositions.slice(); // copy array
                    tracking.data.live.latestPositionsByAssetId = _.keyBy(tracking.data.live.latestPositions, 'AssetId');
                    _.each(tracking.data.live.latestPositionsByAssetId, function (latestPosition) {
                        updateAssetNotificationTime(latestPosition.AssetId, 'positions', latestPosition.Epoch);
                    });

                    tracking.data.live.normalizedPositionsByAssetId = _.groupBy(tracking.data.live.normalizedPositions, 'AssetId');
                    tracking.data.live.markersByAssetId = _.groupBy(tracking.data.live.markers, function (marker) { return marker.data.assetId; });
                    tracking.data.live.markersByPositionId = _.keyBy(tracking.data.live.markers, function(marker) { return marker.data.location.Id; });

                    // show active locations in history area
                    _.each(tracking.data.assets, function (asset) {
                        updateLiveDataForAsset(asset, true, tracking.data.live.latestPositionsByAssetId[asset.Id]);
                    });
                    //createLivePositionResults();

                    getLiveAssetsShown();

                    if (isNewFollowPosition) {
                        openMostRecentLiveFollowPosition();
                    }

                    // check for default asset highlight
                    if (!showDefaultAsset()) {
                        // set bounds
                        setMapBounds();
                    }

                    tracking.throttles.updatePositionStatus();
                    tracking.data.live.isInitialized = true;
                    tracking.data.domNodes.mapMode.liveLoaded.textContent = moment().format(tracking.user.dateFormat.substring(0, tracking.user.dateFormat.indexOf(' ')) + ' HH:mm');
                }
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError('Error querying live assets.');
            }
        });
    }

    function isItemIncluded(grouping, id) {
        if (grouping === undefined)
            return false;
        return grouping.indexOf(id.toString()) !== -1;
    }

    function getVisibleAssetIds(viewMode) {
        viewMode = viewMode !== undefined ? viewMode : tracking.viewModes.NORMAL;
        if (viewMode === tracking.viewModes.NORMAL) {
            return tracking.data.visible.assets;
        } else if (viewMode === tracking.viewModes.SHARED_VIEW) {
            if (tracking.data.sharedView.temp !== null) {
                return tracking.data.sharedView.temp.AssetIds;
            } else if (tracking.data.sharedView.current !== null) {
                return tracking.data.sharedView.current.AssetIds;
            } else {
                return [];
            }
        }        
    }

    function showResultLimitsIfApplicable(result, isDateFiltered) {
        console.log('showResultLimitsIfApplicable', isDateFiltered);
        // first page may not be limited, need additional property to know that?
        if (result.IsLimited === undefined || result.IncludesLimitedData === undefined) {
            return;
        }
        // only applies to history mode and shared views
        var excess = tracking.data.domNodes.mapMode.excessData;
        var cont = tracking.data.domNodes.mapMode.container;
        if (result.IsLimited || isDateFiltered) {
            if (isDateFiltered) {
                cont.classList.add('is-filtered');
            } else {
                cont.classList.remove('is-filtered');
            }
            //if (result.IsLimited) {
                excess.querySelector('p').textContent = tracking.strings.ERROR_TOO_MANY_RESULTS.replace('{0}', result.Limit);
            //}                

            cont.classList.add('has-excess');
            if (isDateFiltered || (result.IsLimited && result.IncludesLimitedData)) {
                // user agreed to view the limited data
                cont.classList.add('is-limited');
                document.getElementById('map-mode-details-limited').querySelector('div').textContent = tracking.strings.RESULTS_LIMITED.replace('{0}', result.Limit);
            } else {
                cont.classList.remove('is-limited');
                // show history form with notices
                tracking.data.domNodes.mapMode.dateRange.classList.add('is-visible');
                document.getElementById('history-custom').classList.add('active');
            }
        } else {
            // also when mode is changed
            cont.classList.remove('is-limited');
            cont.classList.remove('has-excess');
            cont.classList.remove('is-filtered');
        }

        if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
            
        } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
            
        }
    }

    function queryActiveAssets(sensitivity, isPredefinedDateRange, loadLimitedData, dateFilter) {
        sensitivity = typeof sensitivity !== 'undefined' ? sensitivity : null;
        isPredefinedDateRange = typeof isPredefinedDateRange !== 'undefined' ? isPredefinedDateRange : false;

        // reset bounds
        tracking.clearBounds();
        
        var fromDate = null;
        var toDate = null;
        var fromDateUtc = null;
        var toDateUtc = null;
        var fromDateEpoch = null;
        var toDateEpoch = null;

        // override shared view's date ranges to show data that otherwise would exceed display limits
        var isDateFiltered = false;
        if (dateFilter !== undefined && dateFilter !== null) {
            fromDateEpoch = dateFilter.fromEpoch;
            toDateEpoch = dateFilter.toEpoch;
            isDateFiltered = true;
        } else {
            tracking.data.history.limitedData = null;
            fromDateEpoch = moment(document.getElementById('txtDateFrom').value, tracking.user.dateWithStandardTimeFormat);
            if (fromDateEpoch.isValid()) {
                fromDateEpoch = moment.utc(fromDateEpoch.valueOf() + tracking.user.tickOffset).valueOf(); // TODO proper timezone conversion support
            } else {
                fromDateEpoch = null;
            }
            toDateEpoch = moment(document.getElementById('txtDateTo').value, tracking.user.dateWithStandardTimeFormat);
            if (toDateEpoch.isValid()) {
                toDateEpoch = moment.utc(toDateEpoch.valueOf() + tracking.user.tickOffset).valueOf(); // TODO proper timezone conversion support
            } else {
                toDateEpoch = null;
            }
        }

        var fromDateFull = null;
        if (fromDateEpoch !== undefined && fromDateEpoch !== null) {
            fromDate = moment(fromDateEpoch - tracking.user.tickOffset).format(tracking.user.dateWithStandardTimeFormat);
            fromDateUtc = moment.utc(fromDateEpoch).format(tracking.user.dateWithStandardTimeFormat);
            fromDateFull = moment(fromDateEpoch - tracking.user.tickOffset).format(tracking.user.dateFormat);
        }
        if (toDateEpoch !== undefined && toDateEpoch !== null) {
            toDate = moment(toDateEpoch - tracking.user.tickOffset).format(tracking.user.dateWithStandardTimeFormat);
            toDateUtc = moment.utc(toDateEpoch).format(tracking.user.dateWithStandardTimeFormat);
        }

        tracking.data.history.fromDate = fromDate;
        tracking.data.history.fromDateFull = fromDateFull;
        tracking.data.history.toDate = toDate;
        tracking.data.history.fromDateUtc = fromDateUtc;
        tracking.data.history.toDateUtc = toDateUtc;
        tracking.data.history.fromDateEpoch = fromDateEpoch;
        tracking.data.history.toDateEpoch = toDateEpoch;

        var assetIds = getVisibleAssetIds().join(',');
        if (assetIds == '') {
            console.log("No assets selected!");
            //populateEvents([]);
            createHistoryPositionResults();
            return;
        }

        if (loadLimitedData === undefined || loadLimitedData === null) {
            loadLimitedData = false;
        }

        // disable show button and show loading message
        tracking.data.domNodes.mapMode.show.disabled = true;
        toggleLoadingMessage(true, 'assetPositions');
        // todo: start data loading message here
        var dataPost = {
            fromDate: fromDate,
            toDate: toDate,
            assetIds: assetIds,
            zoomLevel: null,
            sensitivity: sensitivity,
            format: tracking.user.dateFormat,
            lang: tracking.user.dateCulture,
            loadLimitedData: loadLimitedData,
            isMobile: tracking.state.isMobile
        };

        // return the promise
        return $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetAssetPositionsForDateRange'),
            data: JSON.stringify(dataPost),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                tracking.state.hasQueriedHistory = true;
                tracking.data.domNodes.mapMode.show.disabled = false;
                toggleLoadingMessage(false, 'assetPositions');

                // if we've switched out of history mode before the query completed, discard the results
                if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                    return;
                }
                if (msg.d) {
                    var result = msg.d;

                    // if it's a limited result that has errored out, don't clear the current view, discard the results
                    if (result.IsLimited === true && result.IncludesLimitedData === false) {
                        showResultLimitsIfApplicable(result, false);
                        return;
                    }

                    clearHistoryFromMap();
                    tracking.data.history.isLimited = result.IsLimited;
                    tracking.data.history.isLoadedLimitedData = result.IncludesLimitedData;
                    tracking.data.history.isDateFiltered = isDateFiltered;
                    
                    tracking.data.history.positions = [];
                    tracking.data.history.normalizedPositions = [];
                    tracking.data.history.events = [];
                    tracking.data.history.normalizedEvents = [];
                    tracking.data.history.normalizedEventIds = {};
                    tracking.data.history.normalizedEventsByAssetId = {};
                    tracking.data.history.positionsByAssetId = {};
                    tracking.data.history.normalizedPositionsByAssetId = {};
                    tracking.data.history.messageCounts = [];
                    tracking.data.history.messageCountsByAssetId = {};
                    tracking.data.history.messages = [];
                    tracking.data.history.messagesByAssetId = {};
                    tracking.data.history.normalizedMessages = [];
                    tracking.data.history.normalizedMessagesByAssetId = {};
                    tracking.data.history.assetIdsWithResults = {};
                    _.each(result.Assets, function (assetResult) {
                        // also handles message counts for some reason
                        processAssetHistoryPositionsResult(assetResult);
                    });

                    if (result.Events !== null) {
                        addAssetEvents(result.Events, tracking.mapModes.HISTORY);
                    }

                    createHistoryPositionResults();
                    // handle result.Events - populate events data table #event-panel, .panel-content
                    //populateEvents(tracking.data.history.events); // todo: this will be dupe called
                    //populateEvents(result.Events);

                    // are we loading a specific position
                    // hack: using setTimeout always feels like a hack
                    // as we are waiting for the map to render or resize?
                    // it should be determined what specifically prevents the highlight position
                    // so we can add an event trigger on that specific event instead
                    // of having a general timeout which may be too short or too long
                    setTimeout(highlightInitialPosition, 500);
                    resizeApp(true);
                    setMapBounds();
                    updateLatestPositionTimeForSharedView();
                    updateSecondaryPanelNotificationContentForNewMode();

                    showResultLimitsIfApplicable(result, isDateFiltered);
                    //tracking.data.domNodes.mapMode.from.textContent = fromDate;
                    //if (toDate === '') {
                    //    tracking.data.domNodes.mapMode.to.textContent = moment().subtract(tracking.user.tickOffset, 'ms').format(tracking.user.dateFormat.substring(0, tracking.user.dateFormat.indexOf(' ')) + ' HH:mm');
                    //} else {
                    //    tracking.data.domNodes.mapMode.to.textContent = toDate;
                    //}

                    if (!isPredefinedDateRange) {
                        // TODO this is a mess - move to proper spot
                        var historyCustom = document.getElementById('history-custom');
                        var historyButtons = document.getElementById('filter-history-range').querySelectorAll('button');
                        _.each(historyButtons, function (button) {
                            button.classList.remove('active');
                        });
                        historyCustom.classList.add('active');
                        historyCustom.classList.add('btn-primary');
                    }

                    if (tracking.data.history.isLimited && tracking.data.history.isLoadedLimitedData || (tracking.data.history.limitedData !== null)) {
                        handleLimitedDataResult(tracking.viewModes.NORMAL, fromDateUtc, toDateUtc, result.Counts, result.Limit);
                    } else {
                        tracking.data.history.limitedData = null;
                    }
                    updateActiveAssetInformation(tracking.viewModes.NORMAL);
                    updateMapModeDateRange();
                }
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_ASSET_QUERY_ERROR);
                // re-enable show button and clear loading message
                tracking.data.domNodes.mapMode.show.disabled = false;
                toggleLoadingMessage(false, 'assetPositions');
            }
        });
    }

    function getValueIfCheckboxSelected(element) {
        var elem = $j(element);
        var include = elem.closest('.parameter-options').prev('.parameter-toggle').find('input:checkbox').prop('checked')
        //var include = elem.prevAll('input:checkbox').prop('checked');
        if (include)
            return elem.val();
        else
            return null;
    }

    function toggleLoadingMessage(show, key) {
        // if key is supplied, tie the show/hide to that key
        if (key != null) {
            if (show) {
                loading.push(key);
            } else {
                for (var i = 0; i < loading.length; i++) {
                    if (loading[i] == key) {
                        loading.splice(i, 1);
                        break;
                    }
                }
            }
        }
        if (show) {
            $j('#loading').show();
        } else {
            // don't hide until all keys are done loading
            if (loading.length == 0)
                $j('#loading').hide();
        }
    }

    var loadingInterval = null;
    function toggleNewLoadingMessage(show, key) {
        // if key is supplied, tie the show/hide to that key
        if (key != null) {
            if (show) {
                loading.push(key);
            } else {
                for (var i = 0; i < loading.length; i++) {
                    if (loading[i] == key) {
                        loading.splice(i, 1);
                        break;
                    }
                }
            }
        }
        if (show) {
            $j('#loading-new').show();
            // animate loading
            loadingInterval = setInterval(updateLoading, 500);
        } else {
            // don't hide until all keys are done loading
            if (loading.length == 0) {
                $j('#loading-new').hide();
                // clear animation
                clearInterval(loadingInterval);
            }
        }
    }

    function updateLoading() {
        var anim = $j('#loading-anim').text();
        anim = anim + '.';
        if (anim.length > 3)
            anim = '.';
        $j('#loading-anim').text(anim);
    }

    tracking.testClear = clearPositionResults;

    function clearPositionResults() {
        _.each(tracking.data.domNodes.assets, function (assetNodes) {
            _.each(assetNodes, function (assetNode) {
                //var toggle = assetNode.querySelector('.item-toggle');
                var details = assetNode.querySelector('.item-details');
                var location = assetNode.querySelector('.locations');
                var status = assetNode.querySelectorAll('.status');
                //toggle.classList.remove('active');
                //toggle.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#expand');
                details.classList.remove('is-visible');
                if (location !== null) {
                    location.parentNode.removeChild(location);
                }
                _.each(status, function (item) {
                    item.parentNode.removeChild(item);
                });
            });
        });
    }

    function hideMapMarkersForViewMode(viewMode) {
        if (viewMode === tracking.viewModes.NORMAL) {
            tracking.map.removeLayer(tracking.data.mapLayers.normal);

            $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('close');
            $(tracking.data.domNodes.infoDialogs.positionHistory).dialog('close');
        } else {
            deselectSharedView();
            tracking.map.removeLayer(tracking.data.mapLayers.sharedView);
            $(tracking.data.domNodes.infoDialogs.sharedViewInformation).dialog('close');
            $(tracking.data.domNodes.infoDialogs.mapItemInformation).dialog('close');
            $(tracking.data.domNodes.infoDialogs.positionHistory).dialog('close');
        }
    }

    function showMapMarkersForViewMode(viewMode) {
        if (viewMode === tracking.viewModes.NORMAL) {
            tracking.map.addLayer(tracking.data.mapLayers.normal);
        } else {
            tracking.map.addLayer(tracking.data.mapLayers.sharedView);
        }
    }

    function hideMapMarkersForDataGroup(dataGroup) {
        if (dataGroup === undefined || dataGroup === null) {
            return;
        }
        var markers = getMapMarkersForDataGroup(dataGroup);
        if (markers === undefined) {
            return;
        }

        var $dialog = $(tracking.data.domNodes.infoDialogs.mapItemInformation);
        _.each(markers, function (marker) {
            if (tracking.state.selectedMarker !== null && tracking.state.selectedMarker == marker && $dialog.dialog('isOpen')) {
                $dialog.dialog('close');
            }
            removeItemFromMap(marker);
        });

        switch (dataGroup) {
            case tracking.dataGroups.NORMAL_LIVE:
                _.each(tracking.data.live.mapLinesByAssetId, function (item) {
                    removeItemFromMap(item, null, tracking.viewModes.NORMAL);
                });
                break;
            case tracking.dataGroups.NORMAL_HISTORY:
                _.each(tracking.data.history.mapLinesByAssetId, function (item) {
                    removeItemFromMap(item, null, tracking.viewModes.NORMAL);
                });
                _.each(tracking.data.history.markerClustersByAssetId, function (item) {
                    removeItemFromMap(item, null, tracking.viewModes.NORMAL);
                });
                break;
            case tracking.dataGroups.JOURNEY_HISTORY:
                _.each(tracking.data.trips.mapLinesByTripId, function (item) {
                    removeItemFromMap(item, null, tracking.viewModes.NORMAL);
                });
                _.each(tracking.data.trips.markerClustersByTripId, function (item) {
                    removeItemFromMap(item, null, tracking.viewModes.NORMAL);
                });
                break;
            case tracking.dataGroups.SHARED_VIEW_HISTORY:
                _.each(tracking.data.sharedView.mapLinesByAssetId, function (item) {
                    removeItemFromMap(item, null, tracking.viewModes.SHARED_VIEW);
                });
                _.each(tracking.data.sharedView.markerClustersByAssetId, function (item) {
                    removeItemFromMap(item, null, tracking.viewModes.SHARED_VIEW);
                });
                break;
        }
    }

    function showMapMarkersForDataGroup(dataGroup) {
        var markers = getMapMarkersForDataGroup(dataGroup);
        if (markers === undefined) {
            return;
        }

        // in sharedViews mode, no items are hidden based on visibility
        var visibleAssets = _.filter(tracking.data.assets, function (asset) {
            return _.indexOf(tracking.data.visible.assets, asset.Id) !== -1;
        });
        
        switch (dataGroup) {
            case tracking.dataGroups.NORMAL_LIVE:
                _.each(visibleAssets, function (asset) {
                    if (tracking.data.live.mapLinesByAssetId[asset.Id] !== undefined) {
                        addItemToMap(tracking.data.live.mapLinesByAssetId[asset.Id], null, tracking.viewModes.NORMAL);
                    }
                    // only show the latest
                    var latestMarker = getCurrentMarkerForAsset(asset, tracking.mapModes.LIVE);
                    if (latestMarker !== undefined && !latestMarker.data.location.IsHidden) {
                        addItemToMap(latestMarker, null, tracking.viewModes.NORMAL);
                    }
                });
                break;
            case tracking.dataGroups.NORMAL_HISTORY:
                _.each(visibleAssets, function (asset) {
                    if (tracking.data.history.mapLinesByAssetId[asset.Id] !== undefined) {
                        addItemToMap(tracking.data.history.mapLinesByAssetId[asset.Id], null, tracking.viewModes.NORMAL);
                    }
                    if (tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined) {
                        addItemToMap(tracking.data.history.markerClustersByAssetId[asset.Id], null, tracking.viewModes.NORMAL);
                    } else {
                        _.each(tracking.data.history.markersByAssetId[asset.Id], function (marker) {
                            if (!marker.data.location.IsHidden) {
                                addItemToMap(marker, null, tracking.viewModes.NORMAL);
                            }
                        });
                    }
                });
                break;
            case tracking.dataGroups.JOURNEY_HISTORY:
                _.each(tracking.data.visible.trips, function (tripId) {
                    if (tracking.data.trips.mapLinesByTripId[tripId] !== undefined) {
                        addItemToMap(tracking.data.trips.mapLinesByTripId[tripId], null, tracking.viewModes.NORMAL);
                    }
                    if (tracking.data.trips.markerClustersByTripId[tripId] !== undefined) {
                        addItemToMap(tracking.data.trips.markerClustersByTripId[tripId]), null, tracking.viewModes.NORMAL;
                    } else {
                        _.each(tracking.data.trips.markersByTripId[tripId], function (marker) {
                            if (!marker.data.location.IsHidden) {
                                addItemToMap(marker, null, tracking.viewModes.NORMAL);
                            }
                        });
                    }
                });
                break;
            case tracking.dataGroups.SHARED_VIEW_HISTORY:
                _.each(tracking.data.sharedView.mapLinesByAssetId, function(item) {
                    addItemToMap(item, null, tracking.viewModes.SHARED_VIEW);
                });
                
                _.each(tracking.data.sharedView.markerClustersByAssetId, function (item, itemId) {
                    addItemToMap(item, null, tracking.viewModes.SHARED_VIEW);
                });
                // add individual markers when asset isn't clustered
                _.each(tracking.data.sharedView.markersByAssetId, function (markers, itemId) {
                    if (tracking.data.sharedView.markerClustersByAssetId[itemId] === undefined) {
                        _.each(markers, function(item) {
                            addItemToMap(item, null, tracking.viewModes.SHARED_VIEW);
                        });
                    }
                });
                break;
        }
    }

    function clearHistoryFromMap() {
        // clear the prior history before running a new one
        //var markers = _.union(tracking.data.live.markers, tracking.data.history.markers);
        //var markers = tracking.data.live.markers.concat(tracking.data.history.markers);
        var markers = [];
        Array.prototype.push.apply(markers, tracking.data.live.markers);
        Array.prototype.push.apply(markers, tracking.data.history.markers);

        _.each(markers, function (marker) {
            removeItemFromMap(marker);
        });

        _.each(tracking.data.history.markers, function (marker) {
            removeItemFromMap(marker);
        });
        tracking.data.history.markers = [];
        tracking.data.history.markersByAssetId = {};
        tracking.data.history.markersByPositionId = {};

        // clear lines connecting positions
        _.each(tracking.data.history.positions, function (position) {
            var prAsset = findAssetById(position.Id);
            if (tracking.data.history.mapLinesByAssetId[prAsset.Id] !== undefined) {
                // remove polyline
                removeItemFromMap(tracking.data.history.mapLinesByAssetId[prAsset.Id]);
                delete tracking.data.history.mapLinesByAssetId[prAsset.Id];
            }
        });
        tracking.data.history.mapLinesByAssetId = {};

        // clear marker clusters
        _.each(tracking.data.assets, function (asset) {
            if (tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined) {
                tracking.data.history.markerClustersByAssetId[asset.Id].clearLayers();
                // TODO delete them as well?
            }
        });
        //tracking.data.history.markerClustersByAssetId = {};

        if (tracking.state.openWindow !== null) {
            tracking.state.openWindow.remove();
        }

        clearPositionResults();
    }

    function updateWaypoint(waypointId) {
        // query waypoint Id
        toggleLoadingMessage(true, 'waypoint-update');
        var data = { id: waypointId };
        $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetWaypointById'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    if (result.Success == true) {
                        addOrUpdateWaypoint(result.Waypoint);
                        updateWaypointListing();
                    }
                }
                toggleLoadingMessage(false, 'waypoint-update');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_LOAD_WAYPOINT_ERROR);
                toggleLoadingMessage(false, 'waypoint-update');
            }
        });
    }

    function addOrUpdateWaypoint(updatedWaypoint) {
        var index = null;
        var existingWaypoint = null;
        for (var i = 0; i < tracking.data.waypoints.length; i++) {
            if (tracking.data.waypoints[i].Id == updatedWaypoint.Id) {
                index = i;
                existingWaypoint = tracking.data.waypoints[i];
                break;
            }
        }

        if (existingWaypoint !== null) {
            if (existingWaypoint.Status === "Active") {
                if (updatedWaypoint.Status !== "Active") {
                    // remove it from the list of waypoints we're tracking
                    tracking.data.waypoints.splice(index, 1);

                    // remove marker from map
                    if (waypointMarkers != null) {
                        for (var i = 0; i < waypointMarkers.length; i++) {
                            // var waypointId = $j.data(waypointMarkers[i], 'waypointId');
                            var waypointId = waypointMarkers[i].data.waypointId;
                            if (waypointId === updatedWaypoint.Id) {
                                removeItemFromMap(waypointMarkers[i]);
                                waypointMarkers.splice(i, 1);
                                break;
                            }
                        }
                    }

                    // clear route
                    if (existingWaypoint.route != null) {
                        existingWaypoint.routingService.remove();
                        existingWaypoint.route = null;
                    }

                    if (tracking.state.openWindow !== null) {
                        if ($j(tracking.state.openWindow).data('waypointId') === updatedWaypoint.Id) {
                            tracking.state.openWindow.remove();
                        }
                    }
                } else {
                    // replace with updated waypoint, update WP information, if shown
                    var route = existingWaypoint.route;
                    var routingService = existingWaypoint.routingService;
                    existingWaypoint = updatedWaypoint;
                    existingWaypoint.routingService = routingService;
                    existingWaypoint.route = route;
                }
            }
        } else {
            // brand new waypoint, only bother with it if it's active
            if (updatedWaypoint.Status !== "Active") {
                return;
            }

            tracking.data.waypoints.push(updatedWaypoint);
            // add waypoint to map
            var point = L.latLng(updatedWaypoint.Location.Lat, updatedWaypoint.Location.Lng);
            addWaypointMarker(point, updatedWaypoint.Location, updatedWaypoint);
        }

        if (updatedWaypoint.DistanceTo !== null) {
            $j('#WaypointDistanceTo[data-waypoint-id=' + updatedWaypoint.Id + ']')
                .show().find('div.item').text(updatedWaypoint.DistanceTo);
        }
        if (updatedWaypoint.ETA !== null) {
            $j('#WaypointETA[data-waypoint-id=' + updatedWaypoint.Id + ']')
                .show().find('div.item').text(updatedWaypoint.ETA);
        }
    }

    function addWaypointMarker(latlng, location, waypoint) {
        if (latlng == null) {
            return null;
        }
        tracking.extendBounds(latlng);

        var asset = findAssetById(waypoint.AssetId);

        var imagePath = createMarkerPath('Waypoint', asset.Color, null, null, null, false);
        var icon = L.icon({
            iconUrl: imagePath,
            iconSize: [36, 36],
            iconAnchor: [18, 18]
        });
        var marker = L.marker(latlng, { icon: icon });
        marker.data = {
            location: null,
            waypointId: null
        };
        addItemToMap(marker);
        location.marker = marker;
        if (location != null) {
            marker.data.location = location;
        }
        if (waypoint != null) {
            marker.data.waypointId = waypoint.Id;
        }

        marker.on('click', function () {
            markerClick(marker, 'waypoint', null, true);
        });
        marker.on('mouseover', function () {
            markerHover(marker);
        });
        marker.on('mouseout', function () {
            markerUnhover(marker);
        });
        waypointMarkers.push(marker);
        return marker;
    }

    function updateWaypointListing() {
        _.each(tracking.data.domNodes.assets, function (assetNodes, index, list) {
            _.each(assetNodes, function (assetNode, nodeIndex, nodeList) {
                var waypointIndicators = assetNode.querySelectorAll('.waypoint');
                _.each(waypointIndicators, function(waypointIndicator) {
                    if (waypointIndicator !== null) {
                        waypointIndicator.parentNode.removeChild(waypointIndicator);
                    }
                });
            });
        });
        console.log(tracking.data.waypoints.length);
        _.each(tracking.data.waypoints, function (waypoint, index, list) {
            var asset = findAssetById(waypoint.AssetId);

            var assetWaypoint = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            assetWaypoint.classList.add('waypoint');
            assetWaypoint.classList.add('notify-waypoint');
            assetWaypoint.style.fill = asset.Color;
            assetWaypoint.setAttribute('data-waypoint-id', waypoint.Id);
            var assetWaypointTitle = document.createElementNS('http://www.w3.org/2000/svg', 'title');
            assetWaypointTitle.textContent = waypoint.Name;
            assetWaypoint.appendChild(assetWaypointTitle);
            var assetWaypointType = document.createElementNS('http://www.w3.org/2000/svg', 'use');
            assetWaypointType.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#notify-waypoint');
            assetWaypoint.appendChild(assetWaypointType);

            //var assetWaypoint = document.createElement('div');
            //assetWaypoint.className = 'waypoint';
            //assetWaypoint.setAttribute('data-waypoint-id', waypoint.Id);
            //var assetWaypointLink = document.createElement('a');
            //assetWaypointLink.setAttribute('href', '#');
            //var assetWaypointIcon = document.createElement('span');
            //assetWaypointIcon.className = 't-icon t-icon-waypoint';
            //assetWaypointIcon.style.backgroundImage = 'url(' + createMarkerPath('Waypoint', asset.Color, null, null, null, false) + ')';
            //assetWaypointIcon.title = waypoint.Name;
            //assetWaypointLink.appendChild(assetWaypointIcon);
            //assetWaypoint.appendChild(assetWaypointLink);
            _.each(tracking.data.domNodes.assets[asset.Id], function (assetNode) {
                assetNode.querySelector('.asset-indicators').appendChild(assetWaypoint.cloneNode(true));
            });
            assetWaypoint = null;
        });
    }

    function changeSharedViewStatus(sharedView) {
        var data = {
            request: {
                id: sharedView.Id,
                isEnabled: !sharedView.IsEnabled
            }
        };

        handleAjaxFormSubmission('ChangeSharedViewStatus', data, null, null, tracking.strings.MSG_EDIT_SHARED_VIEW_SUCCESS, tracking.strings.MSG_EDIT_SHARED_VIEW_ERROR, function (result) {
            updateSharedView(result.SharedView);
            showSharedViewInformationOnMap(result.SharedView, !tracking.state.openPanels.secondary);
        });
    }

    function getSharedViewLink(sharedView) {
        // TODO remove this method
        return window.location.protocol + '//' + window.location.host + '/s/' + sharedView.UniqueKey;
    }

    function updateSharedView(sharedView) {
        var oldSharedView = findSharedViewById(sharedView.Id);
        for (var i = 0; i < tracking.data.sharedViews.length; i++) {
            if (tracking.data.sharedViews[i].Id == sharedView.Id) {
                tracking.data.sharedViews.splice(i, 1, sharedView);
            }
        }

        var li = tracking.data.domNodes.sharedViews[sharedView.Id];
        if (li !== undefined) {
            var name = li.querySelector('.shared-view-name');
            name.textContent = sharedView.Name;
            var description = li.querySelector('.shared-view-description');
            description.textContent = getSharedViewNodeDescription(sharedView);
        }

        sharedView.ColorSorted = convertHexToSortable(convertNamedColorToHex(sharedView.Color));
        sharedView.Link = getSharedViewLink(sharedView); // TODO remove this
        if (oldSharedView.Color !== sharedView.Color || oldSharedView.IsEnabled !== sharedView.IsEnabled) {
            updateSharedViewListingIcon(sharedView);
        }
        // TODO title and icon of secondary panel, if open, applies to all item types too
        
        // update options panel if opened
        if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'shared-views'
            && parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id')) === sharedView.Id) {
            openSharedViewSettingsPanel(sharedView);
        }

        resortItemGroup('all-shared-views');
        indexSharedViewsForSearch();
    }

    function updatePlace(place) {
        if (tracking.data.places === null) {
            return;
        }

        var oldPlace = findPlaceById(place.Id);
        // replace place in tracking.data.places
        for (var i = 0; i < tracking.data.places.length; i++) {
            if (tracking.data.places[i].Id == place.Id) {
                tracking.data.places.splice(i, 1, place);
            }
        }

        tracking.data.places = tracking.data.places.sort(sortByName);

        var li = tracking.data.domNodes.places[place.Id];
        if (li !== undefined) {
            var name = li.querySelector('.place-name');
            name.textContent = place.Name;
            var description = li.querySelector('.place-description');
            description.textContent = place.Description;
        }

        place.ColorSorted = convertHexToSortable(convertNamedColorToHex(place.Color));
        if (oldPlace.Color != place.Color) {
            // update color and paths
            var isActive = !isItemIncluded(tracking.user.displayPreferences.hiddenPlaces, place.Id);
            updatePlaceListingIcon(place.Id, place.Color, isActive);
        }

        // update icon on the map as the location may have moved
        // remove old marker, add new marker
        var wasDraggable = false;
        if (tracking.data.placeMarkers != null) {
            for (var i = tracking.data.placeMarkers.length - 1; i >= 0; i -= 1) {
                var placeMarker = tracking.data.placeMarkers[i];
                var placeId = placeMarker.data.placeId;
                if (placeId == place.Id) {
                    if (placeMarker.dragging !== undefined && placeMarker.dragging !== null) {
                        wasDraggable = placeMarker.dragging.enabled();
                    }
                    removeItemFromMap(placeMarker);
                    tracking.data.placeMarkers.splice(i, 1);
                }
            }
        }
        var point = L.latLng(place.Location.Lat, place.Location.Lng);
        addPlaceMarker(point, place.Location, place);
        if (wasDraggable) {
            enablePlaceMarkerDraggable(place.Location.marker);
        }

        resortItemGroup('all-places');
        indexPlacesForSearch();
    }

    function updateFence(fence) {

        if (tracking.data.fences == null) {
            return;
        }

        var oldFence = findFenceById(fence.Id);
        removeFencePaths(fence.Id);
        // replace fence in tracking.data.fences
        for (var i = 0; i < tracking.data.fences.length; i++) {
            if (tracking.data.fences[i].Id == fence.Id) {
                tracking.data.fences.splice(i, 1, fence);
            }
        }

        tracking.data.fences = tracking.data.fences.sort(sortByName);

        var li = tracking.data.domNodes.fences[fence.Id];
        if (li !== undefined) {
            var name = li.querySelector('.fence-name');
            name.textContent = fence.Name;
            var description = li.querySelector('.fence-description');
            description.textContent = fence.Description;
        }

        if (getFenceColor(oldFence) != getFenceColor(fence)) {
            // update color and paths
            fence.ColorSorted = convertHexToSortable(fence.Color);
            var isActive = !isItemIncluded(tracking.user.displayPreferences.hiddenFences, fence.Id);
            updateFenceListingIcon(fence, isActive);
        }
        resortItemGroup('all-fences');
        indexFencesForSearch();
    }

    function processNewFence(fence) {
        if (tracking.data.fences == null) {
            return;
        }

        fence.ColorSorted = convertHexToSortable(fence.Color);
        tracking.data.fences.push(fence);
        // add fence to UI
        addFence(fence);
        addFencePath(fence);
        //addFenceContextMenus(fence.Id);
        toggleFenceActive(fence.Id, true, true);
        updateGroupVisibilityStatus('all-fences');

        $j('#no-all-fences').removeClass('is-visible');
        document.getElementById('filter-fences').querySelector('.filter-box').classList.add('is-visible');
    }

    function processNewPlace(place) {
        if (tracking.data.places == null) {
            return;
        }

        place.ColorSorted = convertHexToSortable(convertNamedColorToHex(place.Color));
        tracking.data.places.push(place);
        addPlace(place);
        togglePlaceActive(place.Id, true, true);

        $j('#no-all-places').removeClass('is-visible');
        document.getElementById('filter-places').querySelector('.filter-box').classList.add('is-visible');
    }

    function processNewAsset(asset) {
        if (tracking.data.assets == null) {
            return;
        }

        asset.ColorSorted = convertHexToSortable(convertNamedColorToHex(asset.Color));
        tracking.data.assets.push(asset);
        tracking.data.assetsById = _.keyBy(tracking.data.assets, 'Id');
        addAssetToGroup(asset, null);
        for (var i = 0; i < asset.GroupIds.length; i++) {
            addAssetToGroup(asset, asset.GroupIds[i]);
        }
        toggleItemSorting('assets', tracking.user.displayPreferences.sortMode.assets === tracking.sortModes.CUSTOM);

        // make active
        //addAssetContextMenus(asset.Id);
        toggleAssetActive(asset.Id, true, true);

        indexAssetsForSearch();
    }

    function populateCustomAttributesEditable(container, attributeType, prefix) {
        if (tracking.data.attributeGroups == null || tracking.data.attributes == null) {
            return;
        }
        for (var i = 0; i < tracking.data.attributeGroups.length; i++) {
            var group = tracking.data.attributeGroups[i];
            if (group.Type != attributeType) {
                continue;
            }
            var attributes = findAttributesForAttributeGroup(group, attributeType); // asset attributes
            if (attributes == null || attributes.length == 0) {
                continue;
            }

            tracking.log('Add Custom Attribute Group: ' + group.Name);

            // add group and its attributes to accordion edit-asset-extra-accordion
            var contents = document.createDocumentFragment();
            for (var j = 0; j < attributes.length; j++) {
                var attribute = attributes[j];
                var item = el('.form-group', el('label', { for: prefix + attribute.Id }, attribute.Name));
                    
                //TODO: Setup Regex, PermittedValues
                if ((attribute.PermittedValues != null) && (attribute.PermittedValues != '')) {
                    var permittedValues = attribute.PermittedValues.split(',');
                    // select box for restricted values, check the current
                    var options = [el('option')];
                    for (var k = 0; k < permittedValues.length; k++) {
                        var permittedValue = permittedValues[k];
                        options.push(el('option', { value: permittedValue }, permittedValue));
                    }
                    var select = el('select#' + prefix + attribute.Id + '.attrib.form-control', {'data-attribute-id': attribute.Id }, options);
                    setChildren(item, select);
                } else {
                    switch (attribute.DataType) {
                        case 0: // string
                            //style = ' style="width: 20em;"';
                        case 1: // number
                            var itemOptions = { type: 'text', 'data-attribute-id': attribute.Id };
                            if (attribute.MaxLength != null) {
                                itemOptions.maxlength = attribute.MaxLength;
                            }
                            setChildren(item, el('input#' + prefix + attribute.Id + '.attrib.form-control', itemOptions));
                            break;
                        case 2: // file, TODO: File upload capability (mimic photo?)
                            break;
                    }
                }
                contents.appendChild(item);
            }

            // create accordion and add it
            container.appendChild(createAccordionCard('attribute-' + group.Id + '-edit', container.id, group.Name, contents));
        }
    }

    function setOutput(asset, pin, value, time) {

        // TM3000/Calamp/IDP/Queclink
        // ajax post
        if(time == null)
            time = '';
        var data = {
            assetId: asset.Id,
            pin: pin,
            value: value,
            time: time
        };
        toggleLoadingMessage(true, 'output');
        $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/SendOutputPinRequest'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    if (result.Success !== true) {
                        // message failure, keep text field to allow retry
                        var error = tracking.strings.MSG_SET_OUTPUT_ERROR;
                        if ((result.ErrorMessage != null) && (result.ErrorMessage != '')) {
                            error += ' ' + result.ErrorMessage;
                        }
                        utility.handleWebServiceError(error);
                    }
                }
                toggleLoadingMessage(false, 'output');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_SET_OUTPUT_ERROR);
                toggleLoadingMessage(false, 'output');
            }
        });
    }

    function addTimes(enabled, name) {
        if (!enabled)
            return null;
        var times = {};
        times[name + '-0'] = { name: tracking.strings.PERSISTENT};
        times[name + '-1'] = { name: '1 ' + tracking.strings.MINUTE };
        times[name + '-5'] = { name: '5 ' + tracking.strings.MINUTES };
        times[name + '-15'] = { name: '15 ' + tracking.strings.MINUTES };
        times[name + '-30'] = { name: '30 ' + tracking.strings.MINUTES };
        times[name + '-60'] = { name: '60 ' + tracking.strings.MINUTES };
        times[name + '-custom'] = { name: tracking.strings.CUSTOM };
        return times;
    }

    function isSendCommandDisabledForDevice(device) {
        if (tracking.user.isAnonymous) {
            return true;
        }
        if (tracking.options.enabledFeatures.indexOf('REMOTE_MANAGEMENT') === -1) {
            return true;
        }
        if ((tracking.user.canSendCommands === undefined) || !tracking.user.canSendCommands) {
            return true;
        }
        // send command - this is dumb hardcoded
        // TODO property on the device
        var groupedDeviceIds = [
            tracking.devices.NAL,
            tracking.devices.QUAKE_AIC,
            tracking.devices.QUECLINK,
            tracking.devices.FLIGHTCELL,
            tracking.devices.GTTS,
            tracking.devices.HUGHES,
            tracking.devices.INMARSAT_C,
            tracking.devices.SKYWAVE_IDP,
            tracking.devices.SKYWAVE_IDP_DUAL_MODE,
            tracking.devices.CALAMP,
            tracking.devices.GSAT_MICROS,
            tracking.devices.SKYWAVE_M2M,
            tracking.devices.INREACH,
            tracking.devices.GSE_FBB
        ];
        for (var i = 0; i < groupedDeviceIds.length; i++) {
            if (_.indexOf(groupedDeviceIds[i], device.Id) !== -1) {
                return false;
            }
        }

        var individualDeviceIds = [
            tracking.devices.IRIDIUM_EDGE,
            tracking.devices.LT100,
            tracking.devices.LT501,
            tracking.devices.EDGE_SOLAR,
            tracking.devices.DAN_TRACKER,
            tracking.devices.GEOPRO,
            tracking.devices.DPLUS,
            tracking.devices.IRIDIUM_EXTREME,
            tracking.devices.EDGE_SOLAR,
            tracking.devices.TM3000
        ];
        
        if (_.indexOf(individualDeviceIds, device.Id) !== -1) {
            return false;
        }

        return true;
    }

    function isSendCommandEnabledForAsset(asset) {
        if (tracking.options.enabledFeatures.indexOf('REMOTE_MANAGEMENT') === -1) {
            return false;
        }

        var device = findDeviceById(asset.DeviceId);
        return !isSendCommandDisabledForDevice(device);
    }

    function populateMetaInformation(type, item) {
        // photo for geofence, place, asset, trip
        var metaPanels = ['meta-asset', 'meta-group', 'meta-trip', 'meta-journey', 'meta-fence', 'meta-place', 'meta-shared-view'];
        var activePanel = 'meta-' + type;
        var metaPanel = document.getElementById('panel-secondary-meta');
        //var metaRight = metaPanel.querySelector('.meta-right');
        if (type === 'asset' || type === 'shared-view') {
            //metaRight.classList.add('is-visible');
            metaPanel.classList.add('show-right');
        } else {
            //metaRight.classList.remove('is-visible');
            metaPanel.classList.remove('show-right');
        }
        switch (type) {
            case 'asset':
                // notes, photo, uniqueid, device type
                var notes = document.getElementById('meta-asset-notes');
                //if (item.PhotoType != '') {
                //    metaRight.classList.add('is-visible');
                //    metaPanel.classList.add('show-right');
                //} else {
                //    metaRight.classList.remove('is-visible');
                //    metaPanel.classList.remove('show-right');
                //}
                if (item.Notes !== null && item.Notes !== '') {
                    notes.querySelector('.meta-item').textContent = item.Notes;
                    //notes.classList.add('is-visible');
                } else {
                    notes.querySelector('.meta-item').textContent = tracking.strings.GROUP_NONE;
                    //notes.classList.remove('is-visible');
                }
                var device = findDeviceById(item.DeviceId);
                var deviceName = document.getElementById('meta-asset-device');
                if (tracking.options.hideDeviceName) {
                    deviceName.classList.remove('is-visible');
                } else {
                    deviceName.querySelector('.meta-item').textContent = device.Name;
                    deviceName.classList.add('is-visible');
                }
                var uniqueId = document.getElementById('meta-asset-uniqueid');
                uniqueId.querySelector('.meta-item').textContent = item.UniqueId;
                uniqueId.querySelector('.meta-header').textContent = $('#ddlEditAssetDeviceTypes option[value=' + device.UniqueIdType + ']').text() + ':';
                break;
            case 'group':
                break;
            case 'fence':
                // description
                var description = document.getElementById('meta-group-description')
                if (item.Description !== null && item.Description !== '') {
                    description.querySelector('.meta-item').textContent = item.Description;
                    description.classList.add('is-visible');
                } else {
                    description.classList.remove('is-visible');
                }
                break;
            case 'place':
                // description, contact
                var description = document.getElementById('meta-place-description')
                if (item.Description !== null && item.Description !== '') {
                    description.querySelector('.meta-item').textContent = item.Description;
                    description.classList.add('is-visible');
                } else {
                    description.classList.remove('is-visible');
                }
                var contact = document.getElementById('meta-place-contact')
                if (item.Contact !== null && item.Contact !== '') {
                    contact.querySelector('.meta-item').textContent = item.Contact;
                    contact.classList.add('is-visible');
                } else {
                    contact.classList.remove('is-visible');
                }
                break;
            case 'shared-view':
                var status = document.getElementById('meta-shared-view-status').querySelector('.meta-item');
                if (item.IsEnabled) {
                    status.textContent = tracking.strings.ENABLED;
                } else {
                    status.textContent = tracking.strings.DISABLED;
                }

                var expires = document.getElementById('meta-shared-view-expiration').querySelector('.meta-item');
                if (item.ExpiresOnEpoch === null) {
                    expires.textContent = tracking.strings.NEVER;
                } else {
                    expires.textContent = moment(item.ExpiresOnEpoch).format(tracking.user.shortDateFormat);
                }

                var availability = document.getElementById('meta-shared-view-availability').querySelector('.meta-item');
                if (item.IsPublic) {
                    availability.textContent = tracking.strings.PUBLIC;
                } else {
                    availability.textContent = tracking.strings.PRIVATE;
                }

                var messaging = document.getElementById('meta-shared-view-messaging').querySelector('.meta-item');
                if (item.IsMessagingEnabled) {
                    messaging.textContent = tracking.strings.ENABLED;
                } else {
                    messaging.textContent = tracking.strings.DISABLED;
                }

                // link
                //var link = document.getElementById('meta-shared-view-link');
                var linkButton = document.getElementById('meta-shared-view-link');
                var linkLink = document.getElementById('meta-shared-view-link-link');
                var linkUrl = item.Link;
                linkLink.textContent = linkUrl;
                linkLink.setAttribute('href', linkUrl);
                linkButton.setAttribute('data-link', linkUrl);

                // share links
                document.getElementById('shared-view-share-email').querySelector('a').setAttribute('data-share-view-id', item.Id);
                document.getElementById('shared-view-share-twitter').querySelector('a').setAttribute('href', 'https://twitter.com/intent/tweet?url=' + encodeURIComponent(item.Link) + '&text=' + encodeURIComponent(item.Name) + '. ' + encodeURIComponent(item.Description));
                document.getElementById('shared-view-share-linkedin').querySelector('a').setAttribute('href', 'https://www.linkedin.com/shareArticle?mini=true&url=' + encodeURIComponent(item.Link) + '&title=' + encodeURIComponent(item.Name) + '&summary=' + encodeURIComponent(item.Description) + '&source=' + encodeURIComponent(tracking.productTitle));
                document.getElementById('shared-view-share-facebook').querySelector('a').setAttribute('href', 'https://www.facebook.com/sharer.php?u=' + encodeURIComponent(item.Link));

                // description
                var description = document.getElementById('meta-shared-view-description');
                if (item.Description !== null && item.Description !== '') {
                    description.querySelector('.meta-item').textContent = item.Description;
                    description.classList.add('is-visible');
                } else {
                    description.querySelector('.meta-item').textContent = '';
                    description.classList.remove('is-visible');
                }

                break;
            case 'journey':
                break;
            case 'trip':
                break;
        }

        _.each(metaPanels, function (panel) {
            var items = metaPanel.querySelectorAll('.' + panel);
            _.each(items, function (item) {
                if (item !== null) {
                    if (activePanel !== null && activePanel === panel) {
                        item.classList.add('is-visible');
                    } else {
                        item.classList.remove('is-visible');
                    }
                }
            });
        });
    }

    function populateItemSelector(action, currentItem, type) {
        var selector = document.getElementById('panel-secondary-header');
        //selector.classList.add('d-flex');
        var $selector = $(selector);
        selector.setAttribute('data-group-for', type);
        var items = [];
        var allItems = [];
        switch (type) {
            case 'assets':
                allItems = tracking.data.assets;
                switch (action) {
                    case 'send-command':
                        items = _.filter(tracking.data.assets, function (asset) { return isSendCommandEnabledForAsset(asset); });
                        break;
                    case 'send-waypoint':
                        items = _.filter(tracking.data.assets, function (asset) { return isWaypointEnabledForAsset(asset); });
                        break;
                    case 'send-message':
                        items = _.filter(tracking.data.assets, function (asset) { return isMessagingEnabledForAsset(asset); });
                        break;
                    case 'view-logs-service-meters':
                        items = _.filter(tracking.data.assets, function (asset) { return isServiceMeterEnabledForAsset(asset); });
                        break;
                    case 'view-logs-garmin-forms':
                        items = _.filter(tracking.data.assets, function (asset) { return isGarminFormsEnabledForAsset(asset); });
                        break;
                    case 'set-output':
                        items = _.filter(tracking.data.assets, function (asset) { return isOutputEnabledForAsset(asset); });
                        break;
                    case 'set-driver':
                        items = tracking.data.assets;
                        break;
                    case 'route-asset':
                    case 'set-waypoint':
                    case 'add-position':
                    case 'view-logs-message':
                    case 'view-logs-io':
                    case 'view-logs-waypoint': // todo: does this apply to set waypoint? if not, it should be same restriction as send-waypoint
                    case 'view-logs-driver':
                    case 'edit-asset':
                    case 'asset-positions':
                    case 'asset-events':
                    case 'asset-status':
                    case 'asset-messages':
                    case 'asset-chat':
                    case 'asset-options':
                    case 'asset-alerts':
                    case 'asset-activity':
                        items = tracking.data.assets;
                        break;
                    case 'acknowledge-alert':
                        items = _.filter(tracking.data.assets, function (asset) {
                            return _.filter(tracking.data.alerts, function (alert) { return alert.AssetId === asset.Id; }).length > 0;
                        });
                        break;
                    default:
                        console.warn('No asset action selector filter defined for:' + action);
                        break;

                }
                break;
            case 'groups':
                allItems = tracking.data.groups;
                switch (action) {
                    case 'send-message':
                        items = _.filter(tracking.data.groups, function (group) { return isMessagingEnabledForAssetGroup(group); });
                        break;
                    case 'group-events':
                    case 'group-status':
                    case 'group-messages':
                    case 'group-positions':
                    case 'group-options':
                    case 'group-alerts':
                    case 'group-chat':
                    case 'group-activity':
                        items = tracking.data.groups;
                        break;
                    case 'edit-group':
                        items = _.filter(tracking.data.groups, function (group) { return !group.IsDefault; });
                        break;
                    default:
                        console.warn('No group action selector filter defined for:' + action);
                        break;
                }
                break;
            case 'fences':
                allItems = tracking.data.fences;
                switch (action) {
                    case 'edit-fence':
                    case 'fence-options':
                        items = tracking.data.fences;
                        break;
                    default:
                        console.warn('No fence action selector filter defined for:' + action);
                        break;
                }
                break;
            case 'places':
                allItems = tracking.data.places;
                switch (action) {
                    case 'route-asset':
                    case 'edit-place':
                    case 'place-options':
                        items = tracking.data.places;
                        break;
                    default:
                        console.warn('No place action selector filter defined for:' + action);
                        break;
                }
                break;
            case 'trips':
                switch (action) {
                    case 'trip-positions':
                    case 'trip-events':
                    case 'trip-status':
                    case 'trip-messages':
                    case 'trip-options':
                    case 'trip-alerts':
                    case 'trip-chat':
                    case 'trip-activity':
                        var allTrips = [];
                        _.each(tracking.data.journeys, function (journey) {
                            allTrips = allTrips.concat(journey.Trips);
                        })
                        items = allTrips;
                        allItems = allTrips;
                }
                break;
            case 'journeys':
                items = tracking.data.journeys;
                allItems = tracking.data.journeys;
                break;
            case 'shared-views':
                items = tracking.data.sharedViews;
                allItems = tracking.data.sharedViews;
                break;
            default:
                console.warn('Unknown item action selector type:' + type);
                break;
        }

        var options = selector.querySelector('#panel-secondary-item-options');
        options.innerHTML = '';

        var isNext = isPrev = true;
        var selectorOptions = document.createDocumentFragment();
        var indexFirstEnabled = null;
        var indexLastEnabled = 0;
        var selectedIndex = 0;
        _.each(allItems, function (val, ind, list) {
            var opt = document.createElement('option');
            opt.value = val.Id;
            opt.textContent = (ind + 1).toString() + ': ' + val.Name;
            opt.disabled = _.indexOf(items, val) === -1;
            if (currentItem.Id === val.Id) {
                selectedIndex = ind;
                opt.setAttribute('selected', 'selected');
            }
            var visibleOption = opt.cloneNode(true); // why?
            visibleOption.textContent = val.Name;
            options.appendChild(visibleOption);
            selectorOptions.appendChild(opt);
            if (!opt.disabled) {
                if (indexFirstEnabled === null) {
                    indexFirstEnabled = ind;
                }
                indexLastEnabled = ind;
            }
        });

        isNext = selectedIndex < indexLastEnabled;
        isPrev = selectedIndex > indexFirstEnabled;

        //select.appendChild(selectorOptions);
        $selector.find('a').removeClass('disabled');
        $selector.find('button').removeClass('disabled').prop('disabled', false);
        if (!isNext) {
            $selector.find('a.item-next').addClass('disabled');
            $selector.find('button.item-next').addClass('disabled').prop('disabled', true);
        }
        if (!isPrev) {
            $selector.find('a.item-prev').addClass('disabled');
            $selector.find('button.item-prev').addClass('disabled').prop('disabled', true);
        }
    }

    function addEventsToActiveNotificationList(addedEvents) {
        // check dialog type and filter based on that?
        if (tracking.state.activeMapMode !== tracking.mapModes.LIVE) {
            return;
        }

        var filteredEvents = [];

        // filter for dialog type
        var openDialog = document.getElementById('dialog-functions').querySelector('.dialog');
        if (openDialog === tracking.data.domNodes.dialogs.assetEvents) {
            filteredEvents = _.filter(addedEvents, function (status) { return !status.Hide && (tracking.data.EVENTS_STATUS.indexOf(status.Type) === -1 && tracking.data.EVENTS_ALERT.indexOf(status.Type) === -1 && tracking.data.EVENTS_EMERGENCY.indexOf(status.Type) === -1 && tracking.data.EVENTS_TEXT_MESSAGE.indexOf(status.Type) === -1); });
        } else if (openDialog === tracking.data.domNodes.dialogs.assetStatus) {
            filteredEvents = _.filter(addedEvents, function (status) { return !status.Hide && (tracking.data.EVENTS_STATUS.indexOf(status.Type) !== -1); });
        } else if (openDialog === tracking.data.domNodes.dialogs.assetAlerts) {
            filteredEvents = _.filter(addedEvents, function (status) { return !status.Hide && (tracking.data.EVENTS_ALERT.indexOf(status.Type) !== -1 || tracking.data.EVENTS_EMERGENCY.indexOf(status.Type) !== -1); });
        } else if (openDialog === tracking.data.domNodes.dialogs.assetActivity) {

        } else {
            return;
        }

        if (filteredEvents.length === 0) {
            return;
        }

        // filter for assets/groups
        var assetId = null;
        var groupId = null;
        if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
            && tracking.data.domNodes.panels.secondary.getAttribute('data-item-type') === 'assets') {
            assetId = parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id'));
        } else if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
            && tracking.data.domNodes.panels.secondary.getAttribute('data-item-type') === 'groups') {
            groupId = tracking.data.domNodes.panels.secondary.getAttribute('data-item-id');
        }
        if (assetId !== null) {
            filteredEvents = _.filter(addedEvents, function (item) {
                return item.AssetId === assetId;
            });
        } else {
            var status = getGroupAssetStatus(groupId);
            _.each(addedEvents, function (item) {
                if (status.assetIds.indexOf(item.AssetId) !== -1) {
                    filteredEvents.push(item);
                }
            });
        }

        if (filteredEvents.length === 0) {
            return;
        }

        // include asset info for groups
        var eventData = [];
        _.each(filteredEvents, function (event) {
            var eventListing = mapAssetEventToListing(event);
            if (eventListing !== null) {
                eventData.push(eventListing);
            }
        });

        // add filtered events to active list
        //console.log('events to add');
        //console.log(filteredEvents);
    }

    function addAssetMessages(assetResult, forDataGroup, trip) {
        var fromMobileMessages = _.map(assetResult.Inbox, function (item) { return mapMessageToCommonFormat(item, true); });
        var toMobileMessages = _.map(assetResult.Outbox, function (item) { return mapMessageToCommonFormat(item, false); });
        var allAssetMessages = fromMobileMessages.concat(toMobileMessages);
        var normalizedMessages = [];

        var dataSource = tracking.data;
        if (forDataGroup === tracking.dataGroups.SHARED_VIEW_HISTORY) {
            dataSource = tracking.data.sharedView;
        }

        _.each(allAssetMessages, function (message) {
            if (dataSource.messagesById[message.Id] === undefined) {
                dataSource.messagesById[message.Id] = normalizeAssetData(message.AssetId, 'message', message);
            }
            normalizedMessages.push(dataSource.messagesById[message.Id]);
        });

        if (forDataGroup === tracking.dataGroups.NORMAL_LIVE) {
            tracking.data.live.messages.push(assetResult);
            tracking.data.live.messagesByAssetId[assetResult.AssetId] = assetResult;
            if (normalizedMessages.length > 0) {
                _.each(normalizedMessages, function (item) {
                    tracking.data.live.normalizedMessages.push(item);
                });
                tracking.data.live.normalizedMessagesByAssetId[assetResult.AssetId] = normalizedMessages;
            }
        } else if (forDataGroup === tracking.dataGroups.NORMAL_HISTORY) {
            tracking.data.history.messages.push(assetResult);
            tracking.data.history.messagesByAssetId[assetResult.AssetId] = assetResult;
            if (normalizedMessages.length > 0) {
                _.each(normalizedMessages, function (item) {
                    tracking.data.history.normalizedMessages.push(item);
                });
                tracking.data.history.normalizedMessagesByAssetId[assetResult.AssetId] = normalizedMessages;
            }
        } else if (forDataGroup === tracking.dataGroups.JOURNEY_HISTORY && trip !== undefined) {
            tracking.data.trips.messages.push(assetResult);
            tracking.data.trips.messagesByTripId[trip.Id] = assetResult;
            if (normalizedMessages.length > 0) {
                _.each(normalizedMessages, function (item) {
                    tracking.data.trips.normalizedMessages.push(item);
                });
                tracking.data.trips.normalizedMessagesByTripId[trip.Id] = normalizedMessages;
            }
        } else if (forDataGroup === tracking.dataGroups.SHARED_VIEW_HISTORY) {
            //tracking.data.sharedView.messages.push(assetResult);
            tracking.data.sharedView.normalizedMessages = tracking.data.sharedView.normalizedMessages.concat(normalizedMessages);
        }
    }

    function normalizeAssetData(assetId, type, item, parent) {
        // TODO normalize upon parsing of the data originally
        // and pass/store this version OR have the DTO match this better
        var normalized = {
            AssetId: assetId,
            Epoch: null
        };
        switch (type) {
            case 'position':
                normalized.Position = item;
                break;
            case 'event':
                normalized.Event = item;
                break;
            //case 'chat':
            //    normalized.Chat = item;
            //    break;
            //case 'message':
            //    normalized.Message = item;
            //    break;
            case 'message':
                if (tracking.data.MESSAGES_TEXT.indexOf(item.TypeId) !== -1) {
                    normalized.Chat = item;
                } else {
                    normalized.Message = item;
                }
                break;
        }

        if (item.Epoch !== undefined) {
            normalized.Epoch = item.Epoch;
        } else if (item.CreatedEpoch !== undefined) {
            normalized.Epoch = item.CreatedEpoch;
        }

        var dataSource = tracking.data;
        if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
            dataSource = tracking.data.sharedView;
        }

        if (item.Position !== undefined && item.Position !== null) {
            if (dataSource.positionsById[item.Position.Id] === undefined) {
                dataSource.positionsById[item.Position.Id] = normalizeAssetData(assetId, 'position', item.Position);
            }
            normalized.Position = dataSource.positionsById[item.Position.Id].Position;
        } else if (item.PositionId !== undefined && item.PositionId !== null) {
            if (dataSource.positionsById[item.PositionId] !== undefined) {
                normalized.Position = dataSource.positionsById[item.PositionId].Position;
            } else if (parent !== undefined && parent.Position !== undefined) {
                normalized.Position = parent.Position;
            }
        }

        if (item.Events !== undefined && item.Events !== null) {
            _.each(item.Events, function (itemEvent) {
                if (dataSource.eventsById[itemEvent.Id] === undefined) {
                    dataSource.eventsById[itemEvent.Id] = normalizeAssetData(assetId, 'event', itemEvent, normalized);
                }
            });
        }

        return normalized;
    }

    function getFilteredEventsForGroup(groupId, filter) {
        var status = getGroupAssetStatus(groupId);
        var events = [];
        var eventsSource = tracking.state.activeMapMode === tracking.mapModes.HISTORY ? tracking.data.history.normalizedEventsByAssetId : tracking.data.live.normalizedEventsByAssetId;
        _.each(status.assetIds, function (assetId) {
            events = events.concat(_.filter(eventsSource[assetId], filter));
        });
        events = _.sortBy(events, defaultListItemSort).reverse();
        return events;
    }

    function getFilteredEventsForJourney(journeyId, filter) {
        var journey = findJourneyById(journeyId);
        var events = [];
        _.each(journey.Trips, function (trip) {
            events = events.concat(_.filter(tracking.data.trips.normalizedEventsByTripId[trip.Id], filter));
        });
        events = _.sortBy(events, function (item) { return item.Epoch; }).reverse();
        return events;
    }

    function defaultListItemSort() {
        return function(item) { return item.Epoch; };
    }

    function openActivityForSharedView(sharedView) {
        var includeChatMessages = sharedView.IsMessagingEnabled;
        var deferreds = [];

        var positions = tracking.data.sharedView.normalizedPositions;
        var events = tracking.data.sharedView.normalizedEvents;
        var messages = [];
        if (includeChatMessages) {
            deferreds.push(loadHistoryMessagesForSharedView(sharedView.Id));
        }

        $.when.apply($, deferreds).done(function () {
            messages = tracking.data.sharedView.normalizedMessages;
            var others = events.concat(messages);
            positions = filterPositionsAlreadyIncluded(positions, others);
            items = others.concat(positions);
            items = _.sortBy(_.filter(items, getDisplayFilterForEventType('activity')), defaultListItemSort).reverse();
            createListing(items, 'activity');
            openDialogPanel(tracking.data.domNodes.dialogs.assetActivity, tracking.strings.ACTIVITY_LOG, sharedView, false, checkForShareViewChange(sharedView), 'shared-view', 'shared-view-activity', openActivityForSharedView);
        });
    }

    function openActivityForAsset(asset) {
        var includeChatMessages = !(tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging);
        var includeDeviceMessages = !tracking.user.isAnonymous;
        var items = [];
        var deferreds = [];
        var dataSource = tracking.state.activeMapMode === tracking.mapModes.LIVE ? tracking.data.live : tracking.data.history;
        
        var positions = [];
        if (dataSource.normalizedPositionsByAssetId[asset.Id] !== undefined) {
            positions = dataSource.normalizedPositionsByAssetId[asset.Id];
        }

        var events = [];
        if (dataSource.normalizedEventsByAssetId[asset.Id] !== undefined) {
            events = dataSource.normalizedEventsByAssetId[asset.Id];
        }

        var messages = [];
        if (includeChatMessages || includeDeviceMessages) {
            var loadMessages = tracking.state.activeMapMode === tracking.mapModes.LIVE ? loadLiveMessagesForAssets : loadHistoryMessagesForAssets;
            deferreds.push(loadMessages([asset.Id]));
        }

        $.when.apply($, deferreds).done(function () {
            if (dataSource.normalizedMessagesByAssetId[asset.Id] !== undefined) {
                messages = dataSource.normalizedMessagesByAssetId[asset.Id];
            }
            var others = events.concat(messages);
            positions = filterPositionsAlreadyIncluded(positions, others);
            items = others.concat(positions);
            var filteredItems = _.filter(items, getDisplayFilterForEventType('activity'));
            console.log(filteredItems);
            items = _.sortBy(filteredItems, defaultListItemSort).reverse();
            createListing(items, 'activity');
            console.log(_.sortBy(filteredItems, function(item) { return item.Epoch; }).reverse());
            openDialogPanel(tracking.data.domNodes.dialogs.assetActivity, tracking.strings.ACTIVITY_LOG, asset, false, null, 'asset', 'asset-activity', openActivityForAsset);
        });
    }

    function openActivityForGroup(group) {
        var status = getGroupAssetStatus(group.Id);
        var includeChatMessages = !(tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging);
        var includeDeviceMessages = !tracking.user.isAnonymous;
        var items = [];
        var deferreds = [];
        var dataSource = tracking.state.activeMapMode === tracking.mapModes.LIVE ? tracking.data.live : tracking.data.history;

        var positions = [];
        var events = [];
        _.each(status.assetIds, function (assetId) {
            if (dataSource.normalizedPositionsByAssetId[assetId] !== undefined) {
                positions = positions.concat(dataSource.normalizedPositionsByAssetId[assetId]);
            }

            if (dataSource.normalizedEventsByAssetId[assetId] !== undefined) {
                events = events.concat(dataSource.normalizedEventsByAssetId[assetId]);
            }
        });

        var messages = [];
        if (includeChatMessages || includeDeviceMessages) {
            var loadMessages = tracking.state.activeMapMode === tracking.mapModes.LIVE ? loadLiveMessagesForAssets : loadHistoryMessagesForAssets;
            deferreds.push(loadMessages(status.assetIds));
        }

        $.when.apply($, deferreds).done(function () {
            _.each(status.assetIds, function(assetId) {
                if (dataSource.normalizedMessagesByAssetId[assetId] !== undefined) {
                    messages = messages.concat(dataSource.normalizedMessagesByAssetId[assetId]);
                }
            });
            var others = events.concat(messages);
            positions = filterPositionsAlreadyIncluded(positions, others);
            items = others.concat(positions);
            items = _.sortBy(_.filter(items, getDisplayFilterForEventType('activity')), defaultListItemSort).reverse();
            createListing(items, 'activity');
            openDialogPanel(tracking.data.domNodes.dialogs.assetActivity, tracking.strings.ACTIVITY_LOG, group, false, null, 'group', 'group-activity', openActivityForGroup);
        });
    }

    function openActivityForTrip(trip) {
        var includeChatMessages = !(tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging);
        var includeDeviceMessages = !tracking.user.isAnonymous;
        var items = [];
        var deferreds = [];

        var positions = [];
        if (tracking.data.trips.normalizedPositionsByTripId[trip.Id] !== undefined) {
            positions = tracking.data.trips.normalizedPositionsByTripId[trip.Id];
        }

        var events = [];
        if (tracking.data.trips.normalizedEventsByTripId[trip.Id] !== undefined) {
            events = tracking.data.trips.normalizedEventsByTripId[trip.Id];
        }

        var messages = [];
        if (includeChatMessages || includeDeviceMessages) {
            deferreds.push(loadHistoryMessagesForTrip(trip.Id));
        }

        $.when.apply($, deferreds).done(function () {
            if (tracking.data.trips.normalizedMessagesByTripId[trip.Id] !== undefined) {
                messages = tracking.data.trips.normalizedMessagesByTripId[trip.Id];
            }
            var others = events.concat(messages);
            positions = filterPositionsAlreadyIncluded(positions, others);
            items = others.concat(positions);
            items = _.sortBy(_.filter(items, getDisplayFilterForEventType('activity')), defaultListItemSort).reverse();
            createListing(items, 'activity');
            openDialogPanel(tracking.data.domNodes.dialogs.assetActivity, tracking.strings.ACTIVITY_LOG, trip, false, null, 'trip', 'trip-activity', openActivityForTrip);
        });
    }

    function openActivityForJourney(journey) {
        var includeChatMessages = !(tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging);
        var includeDeviceMessages = !tracking.user.isAnonymous;
        var items = [];
        var deferreds = [];

        var positions = [];
        var events = [];
        var messages = [];

        _.each(journey.Trips, function (trip) {
            if (tracking.data.trips.normalizedPositionsByTripId[trip.Id] !== undefined) {
                positions = positions.concat(tracking.data.trips.normalizedPositionsByTripId[trip.Id]);
            }
            if (tracking.data.trips.normalizedEventsByTripId[trip.Id] !== undefined) {
                events = events.concat(tracking.data.trips.normalizedEventsByTripId[trip.Id]);
            }
            if (includeChatMessages || includeDeviceMessages) {
                deferreds.push(loadHistoryMessagesForTrip(trip.Id));
            }
        });

        $.when.apply($, deferreds).done(function () {
            _.each(journey.Trips, function (trip) {
                if (tracking.data.trips.normalizedMessagesByTripId[trip.Id] !== undefined) {
                    messages = tracking.data.trips.normalizedMessagesByTripId[trip.Id];
                }
            });
            var others = events.concat(messages);
            positions = filterPositionsAlreadyIncluded(positions, others);
            items = others.concat(positions);
            items = _.sortBy(_.filter(items, getDisplayFilterForEventType('activity')), defaultListItemSort).reverse();
            createListing(items, 'activity');
            openDialogPanel(tracking.data.domNodes.dialogs.assetActivity, tracking.strings.ACTIVITY_LOG, journey, false, null, 'journey', 'journey-activity', openActivityForJourney);
        });
    }

    function openPositionsForAsset(asset) {
        var dataSource = tracking.state.activeMapMode === tracking.mapModes.LIVE ? tracking.data.live.normalizedPositionsByAssetId : tracking.data.history.normalizedPositionsByAssetId;
        var positions = [];
        if (dataSource[asset.Id] !== undefined) {
            positions = _.sortBy(dataSource[asset.Id], defaultListItemSort).reverse();
        }
        createListing(positions, 'positions');
        openDialogPanel(tracking.data.domNodes.dialogs.assetPositions, tracking.strings.POSITIONS, asset, false, null, 'asset', 'asset-positions', openPositionsForAsset);

        if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
            // TODO clear unread badges, but probably easier on render with storing unreadPosition/Message/Event IDs
        }
    }

    function openPositionsForGroup(group) {
        var status = getGroupAssetStatus(group.Id);
        var dataSource = tracking.state.activeMapMode === tracking.mapModes.HISTORY ? tracking.data.history.normalizedPositionsByAssetId : tracking.data.live.normalizedPositionsByAssetId;
        var positions = [];
        _.each(status.assetIds, function (assetId) {
            if (dataSource[assetId] !== undefined) {
                positions = positions.concat(dataSource[assetId]);
            }
        });
        positions = _.sortBy(positions, defaultListItemSort).reverse();
        createListing(positions, 'positions');
        openDialogPanel(tracking.data.domNodes.dialogs.assetPositions, tracking.strings.POSITIONS, group, false, undefined, 'group', 'group-positions', openPositionsForGroup);
    }

    function openPositionsForSharedView(sharedView) {
        var positions = _.sortBy(tracking.data.sharedView.normalizedPositions, defaultListItemSort).reverse();
        createListing(positions, 'positions');
        openDialogPanel(tracking.data.domNodes.dialogs.assetPositions, tracking.strings.POSITIONS, sharedView, false, checkForShareViewChange(sharedView), 'shared-view', 'shared-view-positions', openPositionsForSharedView);
    }

    function openPositionsForJourney(journey) {
        var positions = [];
        _.each(journey.Trips, function (trip) {
            if (tracking.data.trips.normalizedPositionsByTripId[trip.Id] !== undefined) {
                positions = positions.concat(tracking.data.trips.normalizedPositionsByTripId[trip.Id]);
            }
        });
        positions = _.sortBy(positions, defaultListItemSort).reverse();
        createListing(positions, 'positions');
        openDialogPanel(tracking.data.domNodes.dialogs.assetPositions, tracking.strings.POSITIONS, journey, false, undefined, 'journey', 'journey-positions', openPositionsForJourney);
    }

    function openPositionsForTrip(trip) {
        var positions = [];
        if (tracking.data.trips.normalizedPositionsByTripId[trip.Id] !== undefined) {
            positions = tracking.data.trips.normalizedPositionsByTripId[trip.Id];
        }
        positions = _.sortBy(positions, defaultListItemSort).reverse();
        createListing(positions, 'positions');
        openDialogPanel(tracking.data.domNodes.dialogs.assetPositions, tracking.strings.POSITIONS, trip, false, undefined, 'trip', 'trip-positions', openPositionsForTrip);
    }

    function openEventsForSharedView(sharedView) {
        var events = _.sortBy(_.filter(tracking.data.sharedView.normalizedEvents, getDisplayFilterForEventType('events')), defaultListItemSort).reverse();
        createListing(events, 'events');
        openDialogPanel(tracking.data.domNodes.dialogs.assetEvents, tracking.strings.EVENTS, sharedView, false, checkForShareViewChange(sharedView), 'shared-view', 'shared-view-events', openEventsForSharedView);
    }

    function openEventsForAsset(asset) {
        var dataSource = tracking.state.activeMapMode === tracking.mapModes.LIVE ? tracking.data.live : tracking.data.history;
        var events = _.sortBy(_.filter(dataSource.normalizedEventsByAssetId[asset.Id], getDisplayFilterForEventType('events')), defaultListItemSort).reverse();
        createListing(events, 'events');
        openDialogPanel(tracking.data.domNodes.dialogs.assetEvents, tracking.strings.EVENTS, asset, false, null, 'asset', 'asset-events', openEventsForAsset);
    }

    function openEventsForGroup(group) {
        var events = getFilteredEventsForGroup(group.Id, getDisplayFilterForEventType('events'));
        createListing(events, 'events');
        openDialogPanel(tracking.data.domNodes.dialogs.assetEvents, tracking.strings.EVENTS, group, false, null, 'group', 'group-events', openEventsForGroup);
    }

    function openEventsForTrip(trip) {
        var tripEvents = _.filter(tracking.data.trips.normalizedEventsByTripId[trip.Id], getDisplayFilterForEventType('events'));
        createListing(tripEvents, 'events');
        openDialogPanel(tracking.data.domNodes.dialogs.assetEvents, tracking.strings.EVENTS, trip, false, undefined, 'trip', 'trip-events', openEventsForTrip);
    }

    function openEventsForJourney(journey) {
        var events = getFilteredEventsForJourney(journey.Id, getDisplayFilterForEventType('events'));
        createListing(events, 'events');
        openDialogPanel(tracking.data.domNodes.dialogs.assetEvents, tracking.strings.EVENTS, journey, false, null, 'journey', 'journey-events', openEventsForJourney);
    }

    function openAlertsForAsset(asset) {
        if (tracking.user.isAnonymous) {
            return;
        }
        // TODO in live mode we should include any alerts still requiring acknowledgement that aren't already included in the live listing
        var dataSource = tracking.state.activeMapMode === tracking.mapModes.LIVE ? tracking.data.live.normalizedEventsByAssetId : tracking.data.history.normalizedEventsByAssetId;
        var alerts = _.sortBy(_.filter(dataSource[asset.Id], getDisplayFilterForEventType('alerts')), defaultListItemSort).reverse();
        createListing(alerts, 'alerts');
        openDialogPanel(tracking.data.domNodes.dialogs.assetAlerts, tracking.strings.ALERTS, asset, false, null, 'asset', 'asset-alerts', openAlertsForAsset);
    }

    function openAlertsForGroup(group) {
        if (tracking.user.isAnonymous) {
            return;
        }
        var events = getFilteredEventsForGroup(group.Id, getDisplayFilterForEventType('alerts'));
        createListing(events, 'alerts');
        openDialogPanel(tracking.data.domNodes.dialogs.assetAlerts, tracking.strings.ALERTS, group, false, null, 'group', 'group-alerts', openAlertsForGroup);
    }

    function openAlertsForTrip(trip) {
        if (tracking.user.isAnonymous) {
            return;
        }
        var alerts = _.filter(tracking.data.trips.normalizedEventsByTripId[trip.Id], getDisplayFilterForEventType('alerts'));
        createListing(alerts, 'alerts');
        openDialogPanel(tracking.data.domNodes.dialogs.assetAlerts, tracking.strings.ALERTS, trip, false, undefined, 'trip', 'trip-alerts', openAlertsForTrip);
    }

    function openAlertsForJourney(journey) {
        if (tracking.user.isAnonymous) {
            return;
        }
        var events = getFilteredEventsForJourney(journey.Id, getDisplayFilterForEventType('alerts'));
        createListing(events, 'alerts');
        openDialogPanel(tracking.data.domNodes.dialogs.assetAlerts, tracking.strings.ALERTS, journey, false, null, 'journey', 'journey-alerts', openAlertsForJourney);
    }

    function openStatusForSharedView(sharedView) {
        var events = _.sortBy(_.filter(tracking.data.sharedView.normalizedEvents, getDisplayFilterForEventType('status')), defaultListItemSort).reverse();
        createListing(events, 'status');
        openDialogPanel(tracking.data.domNodes.dialogs.assetStatus, tracking.strings.STATUS, sharedView, false, checkForShareViewChange(sharedView), 'shared-view', 'shared-view-status', openStatusForSharedView);
    }

    function openStatusForAsset(asset) {
        var dataSource = tracking.state.activeMapMode === tracking.mapModes.LIVE ? tracking.data.live.normalizedEventsByAssetId : tracking.data.history.normalizedEventsByAssetId;
        var events = _.sortBy(_.filter(dataSource[asset.Id], getDisplayFilterForEventType('status')), defaultListItemSort).reverse();
        createListing(events, 'status');
        openDialogPanel(tracking.data.domNodes.dialogs.assetStatus, tracking.strings.STATUS, asset, false, null, 'asset', 'asset-status', openStatusForAsset);
    }

    function openStatusForGroup(group) {
        var events = getFilteredEventsForGroup(group.Id, getDisplayFilterForEventType('status'));
        createListing(events, 'status');
        openDialogPanel(tracking.data.domNodes.dialogs.assetStatus, tracking.strings.STATUS, group, false, null, 'group', 'group-status', openStatusForGroup);
    }

    function openStatusForTrip(trip) {
        var events = _.filter(tracking.data.trips.normalizedEventsByTripId[trip.Id], getDisplayFilterForEventType('status'));
        createListing(events, 'status');
        openDialogPanel(tracking.data.domNodes.dialogs.assetStatus, tracking.strings.STATUS, trip, false, undefined, 'trip', 'trip-status', openStatusForTrip);
    }

    function openStatusForJourney(journey) {
        var events = getFilteredEventsForJourney(journey.Id, getDisplayFilterForEventType('status'));
        createListing(events, 'status');
        openDialogPanel(tracking.data.domNodes.dialogs.assetStatus, tracking.strings.STATUS, journey, false, null, 'journey', 'journey-status', openStatusForJourney);
    }

    function openMessagesForAsset(asset) {
        if (tracking.user.isAnonymous) {
            return;
        }

        var loadMessageCallback = function (dataSource) {
            return function() {
                var messages = _.filter(dataSource[asset.Id], getDisplayFilterForEventType('messages'));
                createListing(messages, 'messages');
            };
        };

        var dataSource = tracking.data.history.normalizedMessagesByAssetId;
        if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
            if (tracking.state.hasQueriedHistory) {
                $.when(loadHistoryMessagesForAssets([asset.Id])).done(loadMessageCallback(dataSource));
            } else {
                createListing([], 'messages');
            }
        } else if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
            dataSource = tracking.data.live.normalizedMessagesByAssetId;
            $.when(loadLiveMessagesForAssets([asset.Id])).done(loadMessageCallback(dataSource));
        }

        openDialogPanel(tracking.data.domNodes.dialogs.assetMessages, tracking.strings.MESSAGES, asset, false, null, 'asset', 'asset-messages', openMessagesForAsset);
    }

    function openMessagesForGroup(group) {
        if (tracking.user.isAnonymous) {
            return;
        }

        var status = getGroupAssetStatus(group.Id);

        var isLive = tracking.state.activeMapMode === tracking.mapModes.LIVE;
        var messages = [];
        var isDataLoaded = $.Deferred();
        if (!isLive && !tracking.state.hasQueriedHistory) {
            // can't populate history when it hasn't been queried
            isDataLoaded.resolve(true);
        } else {
            // TODO only load visible assets...?
            var dataLoader = isLive ? loadLiveMessagesForAssets(status.assetIds) : loadHistoryMessagesForAssets(status.assetIds);
            var dataSource = isLive ? tracking.data.live.normalizedMessagesByAssetId : tracking.data.history.normalizedMessagesByAssetId;
            $.when(dataLoader).done(function () {
                _.each(status.assetIds, function (assetId) {
                    var deviceMessages = _.filter(dataSource[assetId], getDisplayFilterForEventType('messages'));
                    messages = messages.concat(deviceMessages);
                });
                isDataLoaded.resolve(true);
            });
        }

        $.when(isDataLoaded).done(function () {            
            createListing(messages, 'messages');
            openDialogPanel(tracking.data.domNodes.dialogs.assetMessages, tracking.strings.MESSAGES, group, false, null, 'group', 'group-messages', openMessagesForGroup);
        });
    }

    function openMessagesForTrip(trip) {
        if (tracking.user.isAnonymous) {
            return;
        }

        var loadMessagesCallback = function () {
            var messages = _.filter(tracking.data.trips.normalizedMessagesByTripId[trip.Id], getDisplayFilterForEventType('messages'));
            createListing(messages, 'messages');
            openDialogPanel(tracking.data.domNodes.dialogs.assetMessages, tracking.strings.MESSAGES, trip, false, undefined, 'trip', 'trip-messages', openMessagesForTrip);
        };

        $.when(loadHistoryMessagesForTrip(trip.Id)).done(loadMessagesCallback);
    }

    function openMessagesForJourney(journey) {
        if (tracking.user.isAnonymous) {
            return;
        }

        var messages = [];
        var deferreds = [];
        var tripIds = [];

        _.each(journey.Trips, function (trip) {
            tripIds.push(trip.Id);
        });
        _.each(tripIds, function (tripId) {
            deferreds.push(loadHistoryMessagesForTrip(tripId));
        });

        $.when.apply($, deferreds).done(function () {
            _.each(tripIds, function (tripId) {
                var deviceMessages = _.filter(tracking.data.trips.normalizedMessagesByTripId[tripId], getDisplayFilterForEventType('messages'));
                messages = messages.concat(deviceMessages);
            });
            createListing(messages, 'messages');
            openDialogPanel(tracking.data.domNodes.dialogs.assetMessages, tracking.strings.MESSAGES, journey, false, null, 'journey', 'journey-messages', openMessagesForJourney);
        });
    }

    function openChatForAsset(asset) {
        if (tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging) {
            return;
        }

        var dataSource = tracking.data.history.normalizedMessagesByAssetId;
        var loadMessageCallback = function (dataSource) {
            return function() {
                var chatMessages = _.filter(dataSource[asset.Id], getDisplayFilterForEventType('chat'));
                createListing(chatMessages, 'chat');
                openDialogPanel(tracking.data.domNodes.dialogs.assetChat, tracking.strings.CHAT, asset, false, null, 'asset', 'asset-chat', openChatForAsset);
            };
        };

        if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
            if (tracking.state.hasQueriedHistory) {
                $.when(loadHistoryMessagesForAssets([asset.Id])).done(loadMessageCallback(dataSource));
            } else {
                createListing([], 'chat');
            }
        } else if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
            dataSource = tracking.data.live.normalizedMessagesByAssetId;
            $.when(loadLiveMessagesForAssets([asset.Id])).done(loadMessageCallback(dataSource));
        }
    }

    function openChatForGroup(group) {
        if (tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging) {
            return;
        }
        var status = getGroupAssetStatus(group.Id);

        var isLive = tracking.state.activeMapMode === tracking.mapModes.LIVE;
        var messages = [];
        var isDataLoaded = $.Deferred();
        if (!isLive && !tracking.state.hasQueriedHistory) {
            // can't populate history when it hasn't been queried
            isDataLoaded.resolve(true);
        } else {
            var dataLoader = isLive ? loadLiveMessagesForAssets(status.assetIds) : loadHistoryMessagesForAssets(status.assetIds);
            var dataSource = isLive ? tracking.data.live.normalizedMessagesByAssetId : tracking.data.history.normalizedMessagesByAssetId;
            $.when(dataLoader).done(function () {
                _.each(status.assetIds, function (assetId) {
                    var chatMessages = _.filter(dataSource[assetId], getDisplayFilterForEventType('chat'));
                    messages = messages.concat(chatMessages);
                })
                isDataLoaded.resolve(true);
            });
        }

        $.when(isDataLoaded).done(function () {
            createListing(messages, 'chat');
            openDialogPanel(tracking.data.domNodes.dialogs.assetChat, tracking.strings.CHAT, group, false, null, 'group', 'group-chat', openChatForGroup);
        });
    }

    function openChatForSharedView(sharedView) {
        var loadMessagesCallback = function () {
            var messages = _.filter(tracking.data.sharedView.normalizedMessages, getDisplayFilterForEventType('chat'));
            createListing(messages, 'chat');
            openDialogPanel(tracking.data.domNodes.dialogs.assetChat, tracking.strings.CHAT, sharedView, false, checkForShareViewChange(sharedView), 'shared-view', 'shared-view-chat', openChatForSharedView);
        };

        if (!sharedView.IsMessagingEnabled) {
            // TODO show notice
            loadMessagesCallback();
            return;
        }

        $.when(loadHistoryMessagesForSharedView(sharedView.Id)).done(loadMessagesCallback);
    }

    function openChatForTrip(trip) {
        if (tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging) {
            return;
        }

        var loadMessagesCallback = function () {
            var messages = _.filter(tracking.data.trips.normalizedMessagesByTripId[trip.Id], getDisplayFilterForEventType('chat'));
            createListing(messages, 'chat');
            openDialogPanel(tracking.data.domNodes.dialogs.assetChat, tracking.strings.CHAT, trip, false, undefined, 'trip', 'trip-chat', openChatForTrip);
        };

        $.when(loadHistoryMessagesForTrip(trip.Id)).done(loadMessagesCallback);
    }

    function openChatForJourney(journey) {
        if (tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging) {
            return;
        }
        var messages = [];
        var deferreds = [];
        var tripIds = [];

        _.each(journey.Trips, function (trip) {
            tripIds.push(trip.Id);
        });
        _.each(tripIds, function (tripId) {
            deferreds.push(loadHistoryMessagesForTrip(tripId));
        });

        $.when.apply($, deferreds).done(function () {
            _.each(tripIds, function (tripId) {
                var chat = _.filter(tracking.data.trips.normalizedMessagesByTripId[tripId], getDisplayFilterForEventType('chat'));
                messages = messages.concat(chat);
            });
            createListing(messages, 'chat');
            openDialogPanel(tracking.data.domNodes.dialogs.assetChat, tracking.strings.CHAT, journey, false, null, 'journey', 'journey-chat', openChatForJourney);
        });
    }

    function getOSRMLanguage(language) {
        if (language === 'pt') {
            return 'pt-BR';
        }
        return language;
    }

    function openAcknowledgeAlertDialog(asset, event) {
        var alert = null;
        if (event === undefined || event === null) {
            // find first alert triggered event to acknowledge for this asset
            assetEvents = _.filter(tracking.data.alerts, function (evt) {
                return evt.AssetId === asset.Id;
            });
            if (assetEvents !== undefined && assetEvents.length > 0) {
                event = assetEvents[0];
            }
        } else {
            if (asset === undefined || asset === null) {
                asset = findAssetById(event.AssetId);
            }
        }

        if (event.Alert === undefined || event.Alert === null) {
            return;
        }

        tracking.data.validation.acknowledgeAlert.resetForm();
        tracking.data.validation.acknowledgeAlert.currentForm.reset();
        $('#hfAcknowledgeEventId').val(event.Id);
        $('#hfAcknowledgeAssetId').val(asset.Id);

        console.log(event);
        var index = populateAssetAlertIndex(asset, event);

        var form = document.getElementById('acknowledge-form');
        form.classList.add('is-visible');
        form.setAttribute('data-asset-id', asset.Id);
        form.setAttribute('data-event-id', event.Id);
        form.setAttribute('data-index', index);

        var none = document.getElementById('asset-acknowledge-alerts-none');
        none.classList.remove('is-visible');

        var type = event.Alert.Type;
        if (event.Alert.Name != null) {
            type = event.Alert.Name + ': ' + type;
        }

        $('#AcknowledgeTime').text(event.Time);
        var position = getPositionLinkForEvent(event);
        $('#AcknowledgePosition').empty().append(position);
        if (position != '') {
            $('#AcknowledgePositionContainer').show();
            $('#AcknowledgeDetails').show();
        } else {
            $('#AcknowledgePositionContainer').hide();
            $('#AcknowledgeDetails').hide();
        }

        $('#AcknowledgeType').text(type).parent().parent().css('border-color', '').css('border-color', event.Alert.Color);

        if (event.Alert.Description != null) {
            $('#AcknowledgeDescriptionContainer').show();
        } else {
            $('#AcknowledgeDescriptionContainer').hide();
        }
        if (event.Alert.PhotoType != null && event.Alert.PhotoType != '') {
            $('#alert-photo').prop('src', '/uploads/images/alerts/' + event.Alert.Id + '_thumb.' + event.Alert.PhotoType + '?rnd=' + new Date().getTime()).show();
        } else {
            $('#alert-photo').hide();
        }
        $('#AcknowledgeDescription').text(event.Alert.Description);
        if (event.Alert.ResolutionProcedure != null) {
            $('#AcknowledgeProcedureContainer').show();
        } else {
            $('#AcknowledgeProcedureContainer').hide();
        }

        if (event.Alert.LabelAcknowledge != null) {
            $('#AcknowledgeDialog')[0].textContent = event.Alert.LabelAcknowledge;
            // $('#AcknowledgeDialog').button('option', 'label', event.Alert.LabelAcknowledge);
        } else {
            $('#AcknowledgeDialog')[0].textContent = tracking.strings.ACKNOWLEDGE;
            //$('#AcknowledgeDialog').button('option', 'label', tracking.strings.ACKNOWLEDGE);
        }
        if (event.Alert.LabelAcknowledgeAlt != null) {
            $('#AcknowledgeAltDialog')[0].textContent = event.Alert.LabelAcknowledgeAlt;
            //$('#AcknowledgeAltDialog').button('option', 'label', event.Alert.LabelAcknowledgeAlt);
        } else {
            $('#AcknowledgeAltDialog')[0].textContent = tracking.strings.ACKNOWLEDGE_ALT;
            //$('#AcknowledgeAltDialog').button('option', 'label', tracking.strings.ACKNOWLEDGE_ALT);
        }

        $('#AcknowledgeResolutionProcedure').text(event.Alert.ResolutionProcedure);

        openDialogPanel(tracking.data.domNodes.dialogs.acknowledgeAlert, tracking.strings.ACKNOWLEDGE_ALERT, asset, false, null, 'asset', 'acknowledge-alert', openAcknowledgeAlertDialog);
    }

    function openAddPositionDialog(asset) {
        tracking.data.validation.addPosition.resetForm();
        tracking.data.validation.addPosition.currentForm.reset();
        $('#txtAddPositionAsset').val(asset.Name + ' (' + asset.UniqueId + ')');
        $('#hfAddPositionAssetId').val(asset.Id);
        $('#txtAddPositionDate').val(moment(new Date().getTime() - tracking.user.tickOffset).format(tracking.user.dateFormat));
        $('#txtAddPositionSpeedType').text(speedText());
        document.getElementById('txtAddPositionLat').focus();
        openDialogPanel(tracking.data.domNodes.dialogs.addPosition, tracking.strings.ADD_POSITION, asset, false, function () {
            tracking.state.isAddingPosition = false;
            stopChoosingMapLocation(tracking.state.mapClickHandlers.POSITION_ADD);
        }, 'asset', 'add-position', openAddPositionDialog);

        tracking.state.isAddingPosition = true;
        startChoosingMapLocation(tracking.state.mapClickHandlers.POSITION_ADD);
    }

    function openSendWaypointDialog(asset) {
        // send asset a position update (garmin)
        var dialog = $(tracking.data.domNodes.dialogs.sendPosition);
        dialog.data('isServerSide', false);

        // populate places
        var sel = $('#ddlSendPositionPlace').empty();
        sel.append($('<option>'));
        for (var i = 0; i < tracking.data.places.length; i++) {
            var place = tracking.data.places[i];
            sel.append($('<option>').val(place.Id).text(place.Name));
        }

        $('#txtPositionSearch').val('');
        $('#hfPositionAssetId').val(asset.Id);
        $('#hfPositionAssetName').val(asset.Name + ' (' + asset.UniqueId + ')');

        openDialogPanel(tracking.data.domNodes.dialogs.sendPosition, tracking.strings.SEND_POSITION, asset, false, function () {
            tracking.state.isChoosingPosition = false;
            stopChoosingMapLocation(tracking.state.mapClickHandlers.POSITION);
            if (tracking.state.openWindow != null) {
                tracking.state.openWindow.remove();
            }
        }, 'asset', 'send-waypoint', openSendWaypointDialog);

        tracking.state.isChoosingPosition = true;
        startChoosingMapLocation(tracking.state.mapClickHandlers.POSITION);
    }

    function openSetOutputDialog(asset) {
        $('#hfOutputAssetId').val(asset.Id);
        var activeContainerId = '';

        if ($.inArray(asset.DeviceId, tracking.devices.NAL) != -1) {
            activeContainerId = 'nal-output-fields';

            var device = findDeviceById(asset.DeviceId);
            for (var i = 0; i < device.OutputPinCount; i++) {
                var pinNum = i + 1;
                var label = getOutputLabelByIndex(asset, pinNum);
                var output = tracking.strings.OUTPUT_NUMBER.replace('{0}', pinNum);
                var labelElem = document.getElementById('nal-output-' + pinNum + '-label');
                if (label == null) {
                    labelElem.textContent = output;
                } else {
                    labelElem.textContent = labelOrDefault(label.Label, output);
                }
            }
        } else {        
            activeContainerId = 'idp-output-fields';

            // different options depending on asset device type
            var outputTime = document.getElementById('set-output-time');
            outputTime.classList.remove('is-visible');

            tracking.data.validation.idpOutput.resetForm();
            tracking.data.validation.idpOutput.currentForm.reset();

            var device = findDeviceById(asset.DeviceId);
            var list = $('#ddlIDPOutputPin');
            list.empty();

            var includeTimes = (($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1) || ($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) != -1) || (asset.DeviceId == tracking.devices.QUECLINK_GV200) || (asset.DeviceId == tracking.devices.QUECLINK_GV300) || (asset.DeviceId == tracking.devices.QUECLINK_GV600W));
            var pulseEnabled = !includeTimes && asset.DeviceId !== tracking.devices.QUECLINK_GL200;

            for (var i = 0; i < device.OutputPinCount; i++) {
                var pinNum = i + 1;
                var label = getOutputLabelByIndex(asset, pinNum);
                // 3 output types: on, off, pulse
                var onOption = createLabel(pinNum, 1, label);
                var offOption = createLabel(pinNum, 0, label);
                if (onOption !== null) {
                    list.append(onOption);
                }
                if (offOption !== null) {
                    list.append(offOption);
                }
                if (pulseEnabled) {
                    var pulseOption = createLabel(pinNum, 2, label);
                    if (pulseOption !== null) {
                        list.append(pulseOption);
                    }
                }
            }

            if (includeTimes) {
                outputTime.classList.add('is-visible');
            }
        }

        var outputFieldContainers = document.querySelectorAll('.output-fields');
        _.each(outputFieldContainers, function (item) {
            if(item.id !== activeContainerId) {
                item.classList.remove('is-visible');
            } else {
                item.classList.add('is-visible');
            }
        });
        openDialogPanel(tracking.data.domNodes.dialogs.idpOutput, tracking.strings.SET_OUTPUT, asset, false, null, 'asset', 'set-output', openSetOutputDialog);
    }

    function openSetWaypointDialog(asset) {
        var dialogWaypoint = $(tracking.data.domNodes.dialogs.sendPosition);
        dialogWaypoint.data('isServerSide', true);
        // populate places
        var selWaypoint = $('#ddlSendPositionPlace').empty();
        selWaypoint.append($('<option>'));
        for (var j = 0; j < tracking.data.places.length; j++) {
            var placeWaypoint = tracking.data.places[j];
            selWaypoint.append($('<option>').val(placeWaypoint.Id).text(placeWaypoint.Name));
        }

        $('#txtPositionSearch').val('');
        $('#hfPositionAssetId').val(asset.Id);
        $('#hfPositionAssetName').val(asset.Name + ' (' + asset.UniqueId + ')');
        openDialogPanel(tracking.data.domNodes.dialogs.sendPosition, tracking.strings.SET_WAYPOINT, asset, false, function () {
            tracking.state.isChoosingPosition = false;
            stopChoosingMapLocation(tracking.state.mapClickHandlers.POSITION);
            if (tracking.state.openWindow != null) {
                tracking.state.openWindow.remove();
            }
        }, 'asset', 'set-waypoint', openSetWaypointDialog);

        tracking.state.isChoosingPosition = true;
        startChoosingMapLocation(tracking.state.mapClickHandlers.POSITION);
    }

    function openSendCommandDialog(asset) {
        var id = asset.Id;
        //var dialogTitle = tracking.strings.SEND_COMMAND_TITLE.replace('{0}', asset.Name);
        var dialogTitle = tracking.strings.SEND_COMMAND;
        var dialog = null;

        var device = findDeviceById(asset.DeviceId);
        if (asset.DeviceId === tracking.devices.DPLUS) {
            tracking.data.validation.dPlusInterval.resetForm();
            tracking.data.validation.dPlusInterval.currentForm.reset();
            tracking.data.validation.dPlusQuery.resetForm();
            tracking.data.validation.dPlusQuery.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.dPlus);
        } else if (_.indexOf(tracking.devices.GSE_FBB, asset.DeviceId) !== -1) {
            tracking.data.validation.fbbInterval.resetForm();
            tracking.data.validation.fbbInterval.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.fbb);
        } else if ($j.inArray(device.Id, tracking.devices.INMARSAT_C) !== -1) {
            tracking.data.validation.inmarsatC.resetForm();
            tracking.data.validation.inmarsatC.currentForm.reset();
            tracking.data.validation.inmarsatCSchedule.resetForm();
            tracking.data.validation.inmarsatCSchedule.currentForm.reset();
            tracking.data.validation.inmarsatCDelete.resetForm();
            tracking.data.validation.inmarsatCDelete.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.inmarsatC);
            requestInmarsatCInformation(asset); // lookup ocean region status
            var tenMinutesUtc = moment.utc().add(10, 'minutes');
            document.getElementById('ddlInmarsatCStartTimeHour').value = tenMinutesUtc.hour();
            document.getElementById('ddlInmarsatCStartTimeMinute').value = tenMinutesUtc.format('mm');
            document.getElementById('InmarsatCScheduleOptions').classList.add('is-visible');
            document.getElementById('ddlInmarsatCDeleteDnidRegion').value = '9';
            document.getElementById('ddlInmarsatCRequestLocationRegion').value = '9';
            document.getElementById('ddlInmarsatCPollRegion').value = '9';
        } else if (asset.DeviceId === tracking.devices.IRIDIUM_EDGE) {
            tracking.data.validation.iridiumEdge.resetForm();
            tracking.data.validation.iridiumEdge.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.edge);
        } else if (asset.DeviceId === tracking.devices.TM3000) {
            tracking.data.validation.tm3000.resetForm();
            tracking.data.validation.tm3000.currentForm.reset();

            // set output pin dropdown items based on asset.Device and asset.OutputLabels
            var list = $('#ddlTM3000Pin');
            list.empty();
            for (var i = 0; i < device.OutputPinCount; i++) {
                var pinNum = i + 1;
                var label = getOutputLabelByIndex(asset, pinNum);
                // 3 output types: on, off, pulse
                var onOption = createLabel(pinNum, 1, label);
                var offOption = createLabel(pinNum, 0, label);
                var pulseOption = createLabel(pinNum, 2, label);
                if (onOption != null)
                    list.append(onOption);
                if (offOption != null)
                    list.append(offOption);
                if (pulseOption != null)
                    list.append(pulseOption);
            }

            dialog = $(tracking.data.domNodes.dialogs.tm3000);
        } else if (_.indexOf(tracking.devices.CALAMP, asset.DeviceId) !== -1) {
            tracking.data.validation.calampApn.resetForm();
            tracking.data.validation.calampApn.currentForm.reset();
            tracking.data.validation.calampOutput.resetForm();
            tracking.data.validation.calampOutput.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.calamp);

            // set output pin dropdown items based on asset.Device and asset.OutputLabels
            var list = $j('#ddlCalampPin');
            list.empty();
            for (var i = 0; i < device.OutputPinCount; i++) {
                var pinNum = i + 1;
                var label = getOutputLabelByIndex(asset, pinNum);
                // 3 output types: on, off, pulse
                var onOption = createLabel(pinNum, 1, label);
                var offOption = createLabel(pinNum, 0, label);
                var pulseOption = createLabel(pinNum, 2, label);
                if (onOption != null)
                    list.append(onOption);
                if (offOption != null)
                    list.append(offOption);
                if (pulseOption != null)
                    list.append(pulseOption);
            }
        } else if (asset.DeviceId === tracking.devices.IRIDIUM_EXTREME) {
            tracking.data.validation.extremeInterval.resetForm();
            tracking.data.validation.extremeInterval.currentForm.reset();
            tracking.data.validation.extremeEmergencyDestination.resetForm();
            tracking.data.validation.extremeEmergencyDestination.currentForm.reset();
            tracking.data.validation.extremeEmergencyRecipient.resetForm();
            tracking.data.validation.extremeEmergencyRecipient.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.extreme);
        } else if (_.indexOf(tracking.devices.SKYWAVE_M2M, asset.DeviceId) !== -1) {
            tracking.data.validation.skywaveM2MParameters.resetForm();
            tracking.data.validation.skywaveM2MParameters.currentForm.reset();
            tracking.data.validation.skywaveM2MLocation.resetForm();
            tracking.data.validation.skywaveM2MLocation.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.skywaveM2M);
        } else if (_.indexOf(tracking.devices.NAL, asset.DeviceId) !== -1) {
            tracking.data.validation.nal.resetForm();
            tracking.data.validation.nal.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.nal);
        } else if (_.indexOf(tracking.devices.INREACH, asset.DeviceId) !== -1) {
            tracking.data.validation.inreachInterval.resetForm();
            tracking.data.validation.inreachInterval.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.inreach);
        } else if (asset.DeviceId === tracking.devices.GEOPRO) {
            tracking.data.validation.geoproInterval.resetForm();
            tracking.data.validation.geoproInterval.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.geopro);
        //} else if (asset.DeviceId === tracking.devices.GARMIN_FLEET_590) {
        //    // discontinued
        //    tracking.data.validation.garminGarmin.resetForm();
        //    tracking.data.validation.garminGarmin.currentForm.reset();
        //    tracking.data.validation.garminOdometer.resetForm();
        //    tracking.data.validation.garminOdometer.currentForm.reset();
        //    tracking.data.validation.garminDeleteData.resetForm();
        //    tracking.data.validation.garminDeleteData.currentForm.reset();
        //    tracking.data.validation.garminReset.resetForm();
        //    tracking.data.validation.garminReset.currentForm.reset();
        //    tracking.data.validation.garminResetConfig.resetForm();
        //    tracking.data.validation.garminResetConfig.currentForm.reset();
        //    $j('#TelematicsMapVersion,#TelematicsSoftwareVersion').text('');
        //    $j('#TelematicsGarminParamters input:checkbox').prop('checked', false);
        //    $j('#TelematicsGarminParameters input,#TelematicsGarminParameters select').not(':checkbox').prop('disabled', true);
        //    dialog = $(tracking.data.domNodes.dialogs.garmin);
        //    requestGarminInformation(asset.Id);
        } else if (_.indexOf(tracking.devices.GSAT_MICROS, asset.DeviceId) !== -1) {
            $.when(loadDialog('gsatmicro', true)).done(function () {
                tracking.data.validation.gsatmicroSettings.resetForm();
                tracking.data.validation.gsatmicroSettings.currentForm.reset();
                tracking.data.validation.gsatmicroCommands.resetForm();
                tracking.data.validation.gsatmicroCommands.currentForm.reset();
                openDialogPanel(tracking.dialogs.gsatmicro.domNode, dialogTitle, asset, true, null, 'asset', 'send-command', openSendCommandDialog);
                $(tracking.dialogs.gsatmicro.domNode).data('assetId', asset.Id);
                tracking.dialogs.gsatmicro.requestGSatMicroInformation(asset.Id);
            });
        } else if (_.indexOf(tracking.devices.GTTS, asset.DeviceId) !== -1) {
            $j('.gtts3000,.gtts2000bi', dialog).hide();
            if (asset.DeviceId === tracking.devices.GTTS_2000BI
                || asset.DeviceId === tracking.devices.GTTS_2000B) {
                $j('.gtts2000bi').show();
            } else if (asset.DeviceId === tracking.devices.GTTS_3000) {
                $j('.gtts3000').show();
            }
            dialog = $(tracking.data.domNodes.dialogs.gtts);
        } else if (asset.DeviceId === tracking.devices.DAN_TRACKER) {
            dialog = $(tracking.data.domNodes.dialogs.dantracker);
        } else if (_.indexOf(tracking.devices.FLIGHTCELL, asset.DeviceId) !== -1) {
            tracking.data.validation.flightcellPayload.resetForm();
            tracking.data.validation.flightcellPayload.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.flightcell);
        } else if (_.indexOf(tracking.devices.QUECLINK, asset.DeviceId) !== -1) {
            tracking.data.validation.queclinkApn.resetForm();
            tracking.data.validation.queclinkApn.currentForm.reset();
            tracking.data.validation.queclinkSettings.resetForm();
            tracking.data.validation.queclinkSettings.currentForm.reset();
            tracking.data.validation.queclinkCommands.resetForm();
            tracking.data.validation.queclinkCommands.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.queclink);
            //$('#edit-queclink-tab-apn').tab('show');
            queclinkCreateAPNCommand(asset.Id);
            requestQueclinkInformation(asset.Id);
        } else if (_.indexOf(tracking.devices.HUGHES, asset.DeviceId) !== -1) {
            tracking.data.validation.hughesCommands.resetForm();
            tracking.data.validation.hughesCommands.currentForm.reset();
            tracking.data.validation.hughesDownloadFile.resetForm();
            tracking.data.validation.hughesDownloadFile.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.hughes);
        } else if (asset.DeviceId === tracking.devices.EDGE_SOLAR) {
            tracking.data.validation.edgeSolarSettings.resetForm();
            tracking.data.validation.edgeSolarSettings.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.edgeSolar);
            requestEdgeSolarInformation(asset.Id);
        } else if (asset.DeviceId === tracking.devices.LT100) {
            tracking.data.validation.lt100Periodic.resetForm();
            tracking.data.validation.lt100Periodic.currentForm.reset();
            tracking.data.validation.lt100Motion.resetForm();
            tracking.data.validation.lt100Motion.currentForm.reset();
            tracking.data.validation.lt100Vibrate.resetForm();
            tracking.data.validation.lt100Vibrate.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.lt100);
            //$('#edit-lt100-tab-diagnostics').tab('show');
        } else if (asset.DeviceId === tracking.devices.LT501) {
            tracking.data.validation.lt501Periodic.resetForm();
            tracking.data.validation.lt501Periodic.currentForm.reset();
            tracking.data.validation.lt501Motion.resetForm();
            tracking.data.validation.lt501Motion.currentForm.reset();
            tracking.data.validation.lt501Location.resetForm();
            tracking.data.validation.lt501Location.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.lt501);
            //$('#edit-lt501-tab-diagnostics').tab('show');
        } else if (_.indexOf(tracking.devices.QUAKE_AIC, asset.DeviceId) !== -1) {
            tracking.data.validation.quake.resetForm();
            tracking.data.validation.quake.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.quake);
           // $('#edit-quake-settings').tab('show');
            //requestQuakeInformation(asset.Id); // todo
        } else if (_.indexOf(tracking.devices.SKYWAVE_IDP_DUAL_MODE, asset.DeviceId) !== -1
            || _.indexOf(tracking.devices.SKYWAVE_IDP, asset.DeviceId) !== -1) {

            tracking.data.validation.idpAvlGeofence.resetForm();
            tracking.data.validation.idpAvlGeofence.currentForm.reset();
            tracking.data.validation.idpAvlParameters.resetForm();
            tracking.data.validation.idpAvlParameters.currentForm.reset();
            tracking.data.validation.idpCoreParameters.resetForm();
            tracking.data.validation.idpCoreParameters.currentForm.reset();
            tracking.data.validation.idpIoParameters.resetForm();
            tracking.data.validation.idpIoParameters.currentForm.reset();
            tracking.data.validation.idpMetersSet.resetForm();
            tracking.data.validation.idpMetersSet.currentForm.reset();
            dialog = $(tracking.data.domNodes.dialogs.idpSendCommand);

            //$j('#IDPAgentVersions button').button({ text: null, icons: { primary: 'ui-icon-info' } });
            // check asset configuration for AVL properties
            // move to default tab
            //$('#query-idp-tab-diagnostics').tab('show');
            $('#query-idp-accordion .primary-card button').removeClass('disabled');

            if (_.indexOf(tracking.data.avlConfigurations, asset.Configuration) === -1) {
                // disable AVL properties commands
                $('#accordion-query-idp-avl-parameters-head button').addClass('disabled');
                $('#accordion-query-idp-io-parameters-head button').addClass('disabled');
                $('#accordion-query-idp-geofence-head button').addClass('disabled');
                $('#accordion-query-idp-meters-head button').addClass('disabled');
            }

            $j('.hidden', dialog).hide();

            $j.when(requestAVLInformation(asset.Id)).then(function () {
                if (!asset.IDP.Garmin) {
                    $('#accordion-query-idp-garmin-head button').addClass('disabled');
                }
                if (!asset.IDP.EyeAlert) {
                    $('#accordion-query-idp-eyealert-head button').addClass('disabled');
                }
                if (!asset.IDP.Immobilizer && (!asset.IDP.AVL || asset.Configuration !== 'GSEAgentsV2')) {
                    $('#accordion-query-idp-immobilizer-head button').addClass('disabled');
                }

                if (!asset.IDP.Updater) {
                    $('#accordion-query-idp-updater-head button').addClass('disabled');
                }

                // show/hide basic report section based on Report agent enabled
                if (asset.IDP.Report) {
                    $j('.idp-report', dialog).show();
                } else {
                    $j('.idp-report', dialog).hide();
                }

                if ($j.inArray(asset.Configuration, tracking.data.arcConfigurations) == -1) {
                    // disable ARC properties commands
                    $('#accordion-query-idp-arc-head button').addClass('disabled');
                }

                if (asset.DeviceId == tracking.devices.SKYWAVE_SG7100) {
                    // hide pins not available in the SG7100
                    $j('.sg7100', dialog).hide();
                } else {
                    $j('.sg7100', dialog).show();
                }

                $('.avl17', dialog).hide(); // .avl17 is for features no longer in past 1.7
                $('.avl20', dialog).show(); // .avl20 and .avl30 are for features that were added after those versions
                $('.avl30', dialog).show();

                if (asset.Configuration == 'AVL') {
                    $('.avl17', dialog).show();
                    $('.avl20', dialog).hide();
                    $('.avl30', dialog).hide();
                    $('#accordion-query-io-parameters-head button').addClass('disabled');
                } else if (asset.Configuration == 'AVL2') {
                    $('.avl17', dialog).hide();
                    $('.avl20', dialog).show();
                    $('.avl30', dialog).hide();
                } else if (asset.Configuration == 'AVL3' || asset.Configuration == 'ATS' || asset.Configuration == 'GSEAgentsV1' || asset.Configuration == 'GSEAgentsV2') {
                    // hide deprecated pins? Accel?
                }
             });

            // if not dual-mode, hide gprs options
            if (_.indexOf(tracking.devices.SKYWAVE_IDP_DUAL_MODE, asset.DeviceId) === -1) {
                // satellite only, disable/hide gprs options
                $j('input[name=IDPGateway][value!=1]').prop('disabled', true).prop('checked', false).next('label').children('input:text').prop('disabled', true);
                $j('input[name=IDPGateway][value=1]').prop('checked', true);
                $j('.dual-mode-delivery', dialog).not('td').hide();
                $j('td.dual-mode-delivery div', dialog).hide();
            } else {
                $j('.dual-mode-delivery', dialog).not('td').show();
                $j('td.dual-mode-delivery div', dialog).show();
                $j('input[name=IDPGateway]').prop('disabled', false).prop('checked', false).next('label').children('input:text').prop('disabled', false);
                $j('input[name=IDPGateway][value=3]').prop('checked', true);
            }
            $j('.collapse.idp-accel', dialog).show();
            if (asset.DeviceId === tracking.devices.SKYWAVE_7XX) {
                $j('.idp-eio', dialog).hide();
                $j('.collapse.idp-eeio', dialog).show();
            } else if (asset.DeviceId === tracking.devices.SKYWAVE_SG7100) {
                $j('.idp-eio, .idp-eeio, .idp-accel', dialog).hide();
            } else {
                $j('.collapse.idp-eio', dialog).show();
                $j('.idp-eeio', dialog).hide();
            }

            // PINS specific to the IDP-782
            if ((asset.DeviceId !== tracking.devices.SKYWAVE_782)
                && (asset.DeviceId !== tracking.devices.SKYWAVE_782_CELL)
                && (asset.DeviceId !== tracking.devices.SKYWAVE_ST9100)) {
                $j('.idp-782', dialog).addClass('stayhidden').hide();
            } else {
                $j('.idp-782', dialog).removeClass('stayhidden');
                $j('td.idp-782', dialog).show();
            }

            // populate groups and assets, should this just be static?
            var cont = $j('.GroupsContainer', dialog).empty();
            if (cont.length > 0) {
                var sortedGroups = sortAssetGroups();
                for (var i = 0; i < sortedGroups.length; i++) {
                    var group = sortedGroups[i];
                    addGroupToGroupListWithName(group, 0, cont[0], 'IDPGroups');
                }
            }
            // assets
            $j('select.AssetsIncluded', dialog).empty();
            var cont = $j('select.AssetsNotIncluded', dialog).empty();
            if (cont.length > 0){
                for (var i = 0; i < tracking.data.assets.length; i++) {
                    var incasset = tracking.data.assets[i];
                    if ($j.inArray(incasset.Configuration, tracking.data.avlConfigurations) != -1)
                        cont.append($j('<option>').val(incasset.Id).text(incasset.Name + ' (' + incasset.UniqueId + ')'));
                }
            }
            // uncheck all avl properties
            // TODO: is this necessary? just reset form?
            //$j('#IDPAVLParameters input:checkbox,#IDPAVLIOParameters input:checkbox,#IDPCoreParameters input:checkbox,#IDPGarminParameters input:checkbox,#IDPServiceMeters input:checkbox,#IDPARCProperties input:checkbox').prop('checked', false);
            //$j('#IDPAVLParameters input,#IDPAVLParameters select,#IDPAVLIOParameters input,#IDPAVLIOParameters select,#IDPCoreParameters input,#IDPCoreParameters select,#IDPGarminParameters input,#IDPGarminParameters select,#IDPServiceMeters input,#IDPServiceMeters select,#IDPARCProperties input,#IDPARCProperties select').not(':checkbox').prop('disabled', true);
        } else {
            console.log('Unknown Device for Command: ' + asset.DeviceId);
        }
        if (dialog != null) {
            var dialogDom = dialog[0];
            var isLarge = dialogDom.getAttribute('data-is-large') !== null ? true : false;
            openDialogPanel(dialog[0], dialogTitle, asset, isLarge, null, 'asset', 'send-command', openSendCommandDialog);
            dialog.data('assetId', asset.Id);
        }
    }

    function disabledHandler(act) {
        return function (key, opt) {
            if (act)
                return true;
            return false;
        };
    }

    function statusDisabled(isActive, currentStatusId, statusId) {
        return function (key, opt) {
            //if (!isActive)
            //    return true;
            if (currentStatusId == null)
                return false;

            if (currentStatusId == statusId)
                return true;

            return false;
        }
    }

    function isMessagingEnabledForAssetGroup(group) {
        if (tracking.options.enabledFeatures.indexOf('TWO_WAY_MESSAGING') === -1) {
            return false;
        }

        if (group === undefined || group === null) {
            return false;
        }

        var groupAssetIds = findAssetIdsUnderGroup(group);
        var hasAssetWithMessagingEnabled = false;
        _.each(groupAssetIds, function (assetId) {
            if (tracking.data.assetsById[assetId] !== undefined) {
                if (isMessagingEnabledForAsset(tracking.data.assetsById[assetId])) {
                    hasAssetWithMessagingEnabled = true;
                    return;
                }
            }
        });
        return hasAssetWithMessagingEnabled;
    }

    function isMessagingEnabledForAsset(asset) {
        if (tracking.options.enabledFeatures.indexOf('TWO_WAY_MESSAGING') === -1) {
            return false;
        }

        if (asset == null)
            return false;

        var device = findDeviceById(asset.DeviceId);
        if (!device.SupportsMessaging)
            return false;

        if (asset.DeviceId === tracking.devices.BIVY_STICK) {
            // Bivy Stick must be registered with Messaging enabled
            if (asset.Bivy === null || asset.Bivy.Messages !== true || asset.Bivy.Registered !== true) {
                return false;
            }
        }

        if (($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1)
            || ($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) != -1)) {
            //// IDP devices must have the AVL and Garmin services enabled
            //if (asset.IDP.AVL !== true)
            //    return false;
            // IDP devices technically only need Garmin enabled
            if (asset.IDP.Garmin !== true)
                return false;
        }
        return true;
    }

    function isOutputEnabledForAsset(asset) {
        if (tracking.options.enabledFeatures.indexOf('REMOTE_MANAGEMENT') === -1) {
            return false;
        }
        var device = findDeviceById(asset.DeviceId);
        if (device.OutputPinCount == 0)
            return false;
        if (($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1)
            || ($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) != -1)) {
            // IDP devices must have the AVL or ARC service enabled
            if ((asset.IDP.AVL !== true) && (asset.IDP.ARC !== true))
                return false;
        }
        return true;
    }

    function isWaypointEnabledForAsset(asset) {
        if (tracking.options.enabledFeatures.indexOf('WAYPOINTS') === -1) {
            return false;
        }

        var device = findDeviceById(asset.DeviceId);
        if (!device.SupportsPositionUpdates)
            return false;
        if (($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1)
            || ($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) != -1)) {
            // IDP devices must have the AVL service enabled
            //if (asset.IDP.AVL !== true)
            //    return false;
            if (asset.IDP.Garmin !== true)
                return false;
        }
        return true;
    }

    function isGarminFormsEnabledForAsset(asset) {
        if (tracking.options.enabledFeatures.indexOf('GARMIN_INTEGRATION') === -1) {
            return false;
        }
        return tracking.data.devicesById[asset.DeviceId].SupportsGarminForms;
    }

    function isServiceMeterEnabledForAsset(asset) {
        if (tracking.options.enabledFeatures.indexOf('LOGS_SERVICE_METER') === -1) {
            return false;
        }
        if (($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1)
            || ($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) != -1)) {
            // IDP devices must have the AVL service enabled
            if (asset.IDP.AVL !== true)
                return false;
        } else {
            return false;
        }
        return true;
    }

    function groupMenuAction(action, options) {
        var li = $j(this).parent();
        var id = li.attr('id').substring(6);
        var group = findGroupById(id);
        if (group == null) // all assets/fences will be null
            return;
        switch (action) {
            case 'track':
                toggleFollowGroup(group);
                break;
            case 'position-history':
                var assetIds = findAssetIdsUnderGroup(group);
                loadHistory(assetIds);
                break;
            case 'message':
                openSendMessageDialog(null, group);
                break;
            case 'delete':
                var deleteGroupModal = $('#delete-group-modal');
                $('.modal-body', deleteGroupModal).text(tracking.strings.MSG_DELETE_GROUP_CONFIRM.replace('{0}', group.Name));
                $j('#hfDeleteGroupId').val(group.Id);
                deleteGroupModal.modal('show');
                break;
            case 'edit':
                openAssetGroupDialog(group);
                break;
            default:
                break;
        }
    }

    function buildGroupContextMenuItems(group) {
        var groupItems = {

        };
        if ($j.inArray('track', tracking.options.assetMenuExclude) == -1) {
            groupItems['track'] = {
                name: isFollowingGroup(group) ? tracking.strings.UNFOLLOW_LIVE : tracking.strings.FOLLOW_LIVE,
                //name: tracking.strings.FOLLOW_GROUP,
                icon: 'follow'
            };
        }
        if ($j.inArray('message', tracking.options.assetMenuExclude) == -1) {
            groupItems['message'] = {
                name: tracking.strings.SEND_MESSAGE,
                icon: 'message',
                disabled: function (key, opt) {
                    if (tracking.user.isAnonymous) {
                        return true;
                    }
                    return false;
                }
            };
        }
        groupItems['position-history'] = {
            name: tracking.strings.HISTORY_REPLAY,
            icon: 'history'
        };
        groupItems['sep2'] = '---------';
        groupItems['edit'] = {
            name: tracking.strings.EDIT_GROUP,
            icon: 'edit',
            disabled: function (key, opt) {
                if (tracking.user.isAnonymous) {
                    return true;
                }
                if ((tracking.user.canEditAssetGroups === undefined) || !tracking.user.canEditAssetGroups) {
                    return true;
                }
                return false;
            }
        };
        groupItems['delete'] = {
            name: tracking.strings.DELETE_GROUP,
            icon: 'delete',
            disabled: function (key, opt) {
                if (tracking.user.isAnonymous) {
                    return true;
                }
                if ((tracking.user.canEditAssetGroups === undefined) || !tracking.user.canEditAssetGroups) {
                    return true;
                }
                return false;
            }
        };
        groupItems['sep1'] = '---------';
        groupItems['quit'] = { name: tracking.strings.CLOSE, icon: 'quit' };

        return {
            callback: groupMenuAction,
            items: groupItems
        };
    }

    function initializeFenceContextMenu(selector) {
        var fenceItems = {};
        if ($j.inArray('message', tracking.options.assetMenuExclude) == -1) {
            fenceItems['message'] = {
                name: tracking.strings.SEND_MESSAGE,
                icon: 'message',
                disabled: function (key, opt) {
                    if (tracking.user.isAnonymous)
                        return true;

                    var id = getItemIdFromContextMenu(this, 'fenceId');
                    var fence = findFenceById(id);
                    if (fence == null)
                        return true;

                    if (findAssetIdsInGeofence(fence).length == 0)
                        return true;

                    return false;
                }
            };
        }
        fenceItems['position-history'] = {
            name: tracking.strings.HISTORY_REPLAY,
            icon: 'history',
            disabled: function (key, opt) {
                var id = getItemIdFromContextMenu(this, 'fenceId');
                var fence = findFenceById(id);
                if (fence == null)
                    return true;

                if (findAssetIdsInGeofence(fence).length == 0)
                    return true;
                return false;
            }
        };
        fenceItems['group'] = {
            name: tracking.strings.GROUP_ASSETS,
            icon: 'add-to-group',
            disabled: function (key, opt) {
                if (tracking.user.isAnonymous) {
                    return true;
                }

                var id = getItemIdFromContextMenu(this, 'fenceId');
                var fence = findFenceById(id);
                if (fence == null)
                    return true;

                if (findAssetIdsInGeofence(fence).length == 0)
                    return true;
                return false;
            }
        };
        fenceItems['alert'] = {
            name: tracking.strings.CREATE_ALERT,
            icon: 'alert',
            disabled: function (key, opt) {
                if (tracking.user.isAnonymous) {
                    return true;
                }

                var id = getItemIdFromContextMenu(this, 'fenceId');
                var fence = findFenceById(id);
                if (fence == null)
                    return true;

                return false;
            }
        };
        fenceItems['report'] = {
            name: tracking.strings.LOCATION_REPORT,
            icon: 'report',
            disabled: function (key, opt) {
                var id = getItemIdFromContextMenu(this, 'fenceId');
                var fence = findFenceById(id);
                if (fence == null)
                    return true;

                return false;
            }
        };

        fenceItems['sep1'] = '---------';
        fenceItems['edit'] = {
            name: tracking.strings.EDIT_FENCE,
            icon: 'edit',
            disabled: function (key, opt) {
                if (tracking.user.isAnonymous) {
                    return true;
                }
                if ((tracking.user.canEditGeofences === undefined) || !tracking.user.canEditGeofences) {
                    return true;
                }
                return false;
            }
        };
        fenceItems['delete'] = {
            name: tracking.strings.DELETE_FENCE,
            icon: 'delete',
            disabled: function (key, opt) {
                if (tracking.user.isAnonymous) {
                    return true;
                }
                if ((tracking.user.canEditGeofences === undefined) || !tracking.user.canEditGeofences) {
                    return true;
                }
                return false;
            }
        };
        fenceItems['sep2'] = '---------';
        fenceItems['quit'] = { name: tracking.strings.CLOSE, icon: 'quit' };

    }

    function getItemIdFromContextMenu(item, key) {
        var li = $j(item).parent();
        var input = li.children('input.showhide');
        var id = input.data(key);
        if (id == undefined) {
            id = $j(item).data(key);
        }
        if (id == undefined) {
            id = $j(item).parent().parent().data(key);
        }

        return id;
    }

    function getCurrentMarkerForAsset(asset, mode) {
        if (mode === undefined) {
            mode = tracking.state.activeMapMode;
        }
        if (mode === tracking.mapModes.LIVE) {
            var latestMarkers = _.filter(tracking.data.live.markersByAssetId[asset.Id], function(item) { return !item.data.location.IsHidden;});
            if (latestMarkers !== undefined && latestMarkers.length > 0) {
                return latestMarkers[latestMarkers.length - 1];
            }
        } else if (mode === tracking.mapModes.HISTORY) {
            var latestHistory = tracking.data.history.markersByAssetId[asset.Id];
            if (latestHistory !== undefined && latestHistory.length > 0) {
                return latestHistory[0];
            }
        }
        return undefined;
    }

    function getJourneyDataGroupForCurrentViewMode() {
        if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
            return tracking.dataGroups.JOURNEY_HISTORY;
        } else {
            return tracking.dataGroups.SHARED_VIEW_JOURNEY;
        }
    }

    function getMapMarkersForDataGroup(dataGroup) {
        switch (dataGroup) {
            // normal mode
            case tracking.dataGroups.NORMAL_LIVE:
                return tracking.data.live.markers;
            case tracking.dataGroups.NORMAL_HISTORY:
                return tracking.data.history.markers;
            case tracking.dataGroups.JOURNEY_HISTORY:
                return tracking.data.trips.markers;
            // shared view mode
            case tracking.dataGroups.SHARED_VIEW_HISTORY:
                return tracking.data.sharedView.markers;
            case tracking.dataGroups.SHARED_VIEW_JOURNEY:
                return tracking.data.sharedView.trips.markers;
            //case tracking.dataGroups.BEHAVIOR_ANALYSIS:
            //    return tracking.data.behaviorAnalysis.markers;
            //case tracking.dataGroups.FOLLOW_ASSET:
            //    return tracking.data.followAsset.markers;
            default:
                console.warn('Unknown map marker group: ' + dataGroup);
                return undefined;
        }
    }

    function openAssetLatestPosition(asset) {
        var matchedMarker = getCurrentMarkerForAsset(asset);
        if (matchedMarker === undefined || matchedMarker === null) {
            return;
        }

        toggleAssetActive(asset.Id, true, true);

        if (!tracking.options.disablePositionPopup) {
            //matchedMarker.fire('click');
            markerClick(matchedMarker, 'position', null, false);
        } else {
            tracking.map.setView(matchedMarker.getLatLng(), tracking.options.defaultZoom);
        }
    }

    function enablePlaceMarkerDraggable(marker) {
        if (marker === undefined || marker === null) {
            return;
        }
        if (marker.dragging !== undefined && marker.dragging !== null) {
            marker.dragging.enable();
        }
        marker.data.isMoved = true;
        marker.on('dragend', function (e) {
            var latLng = e.target.getLatLng();

            $('#add-place-location').text(convertToLatLngPreference(latLng.lat, latLng.lng));
            $('#hfPlaceLat').val(latLng.lat);
            $('#hfPlaceLng').val(latLng.lng);
        });
    }

    // TODO reusable wizard component (see Shared Views)
    var ItemWizard = function (steps, container, options) {
        this.initialized = false;

        this.options = {
        };
        this.data = {
            steps: []
        };
        this.domNodes = {
            wizardContainer: container
        };

        var self = this;
        function init(steps, options) {
            $.extend(true, self.options, options);
            self.initialized = true;
            self.data.steps = steps;

            var container = self.domNodes.wizardContainer;
        }

        function validateStep(step) {

        }

        this.changeStep = function (step) {

        };

        init(steps, options);
    }

    function goToWizardStep(wizard, stepId, stepNum) {
        var SUCCESS_STEP_ID = 'success';
        var steps = wizard.steps;
        var stepsById = _.keyBy(steps, 'id');
        var chosenStep = null;
        if (stepId !== undefined && stepId !== null) {
            chosenStep = stepsById[stepId];
        } else if (stepNum !== undefined && stepNum !== null) {
            chosenStep = _.find(steps, { step: stepNum });
        }
        if (chosenStep === undefined || chosenStep === null) {
            chosenStep = _.find(steps, { step: 1 });
        }
        console.log('chosen step', chosenStep);

        if (wizard.currentStep !== undefined && wizard.currentStep !== null) {
            // only validate if moving forward
            if (chosenStep.step > wizard.currentStep.step) {
                if (wizard.currentStep.form !== undefined) {
                    var isValid = $(wizard.currentStep.form.currentForm).valid();
                    console.log('check form validity', isValid, wizard.currentStep.form);
                    if (!isValid) {
                        wizard.currentStep.form.focusInvalid();
                        return;
                    }
                }
            }
        }

        wizard.querySelector('.step-title use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgPath(chosenStep.icon));
        wizard.querySelector('.step-title span').textContent = chosenStep.title;
        var allSteps = wizard.querySelectorAll('.wizard-step');
        _.each(allSteps, function (wizardStep) {
            if (wizardStep.getAttribute('data-step-id') === chosenStep.id) {
                wizardStep.classList.add('is-active');
            } else {
                wizardStep.classList.remove('is-active');
            }
        });

        var navigableSteps = _.reject(steps, function (step) { return step.id === SUCCESS_STEP_ID; });
        var lastStepNum = _.last(navigableSteps).step;

        var isFirstStep = chosenStep.step === 1;
        var isLastStep = chosenStep.step === lastStepNum;
        wizard.querySelector('.wizard-buttons .prev-step').disabled = isFirstStep;
        wizard.querySelector('.wizard-buttons .next-step').disabled = isLastStep;
        if (isLastStep) {
            wizard.querySelector('.wizard-buttons .next-step').classList.remove('is-visible');
            wizard.querySelector('.wizard-buttons .final-step').classList.add('is-visible');
        } else {
            wizard.querySelector('.wizard-buttons .next-step').classList.add('is-visible');
            wizard.querySelector('.wizard-buttons .final-step').classList.remove('is-visible');
        }
        wizard.currentStep = chosenStep;

        if (chosenStep.id === SUCCESS_STEP_ID) {
            wizard.querySelector('.wizard-buttons').classList.add('toggle-content');
        } else {
            wizard.querySelector('.wizard-buttons').classList.remove('toggle-content');
        }

        // set progress meter
        var totalSteps = lastStepNum;
        var width = ((chosenStep.step - 1) * (100 / totalSteps)).toFixed(0) + '%';
        var progressBar = wizard.querySelector('.progress-bar');
        progressBar.style.width = width;
        progressBar.textContent = width;

        return chosenStep;
    }

    function openSharedViewStatisticsDialog(sharedView) {
        if (sharedView === undefined || sharedView === null) {
            return;
        }
        tracking.data.sharedView.current = sharedView;
        var data = {
            request: {
                id: sharedView.Id
            }
        };

        // TODO clear previous statistics?
        handleAjaxFormSubmission('GetSharedViewStatistics', data, null, null, null, tracking.strings.MSG_GET_SHARED_VIEW_STATISTICS_ERROR, function (result) {
            // highcharts... recreate chart
            if (result.VisitCounts.length > 0) {
                document.getElementById('shared-view-visits').classList.add('is-visible');
            } else {
                document.getElementById('shared-view-visits').classList.remove('is-visible');
            }

            var data = [];
            _.each(result.VisitCounts, function(dayCounts) {
                data.push(dayCounts.Visits);
            }); // _.pluck
            var chartColors = [tracking.options.defaultColor, '#2b908f', '#90ee7e', '#f45b5b', '#7798BF', '#aaeeee', '#ff0066',
                '#eeaaee', '#55BF3B', '#DF5353', '#7798BF', '#aaeeee']
            var activationChart = new Highcharts.Chart({
                colors: chartColors,
                chart: {
                    renderTo: 'shared-view-visits',
                    type: 'area',
                    backgroundColor: {
                        linearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },
                        stops: [
                            [0, '#2a2a2b'],
                            [1, '#3e3e40']
                        ]
                    },
                    plotBorderColor: '#606063'
                },
                title: {
                    text: tracking.strings.VISITOR_DETAILS,
                    style: {
                        color: '#E0E0E3',
                        //textTransform: 'uppercase',
                        fontSize: '20px'
                    }
                },
                credits: {
                    enabled: false
                },
                xAxis: {
                    title: {
                        text: null,
                        style: {
                            color: '#A0A0A3'
                        }
                    },
                    type: 'datetime',
                    labels: {
                        style: {
                            color: '#E0E0E3'
                        }
                    },
                    gridLineColor: '#707073',
                    lineColor: '#707073',
                    minorGridLineColor: '#505053',
                    tickColor: '#707073'
                },
                yAxis: [
                    {
                        title: {
                            text: tracking.strings.NUMBER_OF_VISITS,
                            style: {
                                color: '#A0A0A3'
                            }
                        },
                        labels: {
                            formatter: function () {
                                return this.value;
                            },
                            style: {
                                color: '#E0E0E3'
                            }
                        },
                        lineColor: '#707073',
                        minorGridLineColor: '#505053',
                        tickColor: '#707073',
                        tickWidth: 1
                    }
                ],
                tooltip: {
                    formatter: function () {
                        return this.series.name + ': <strong>' + Highcharts.numberFormat(this.y, 0) + '</strong> on ' + Highcharts.dateFormat('%A, %B %d', this.x);
                    },
                    backgroundColor: 'rgba(0, 0, 0, 0.85)',
                    style: {
                        color: '#F0F0F0'
                    }
                },
                plotOptions: {
                    area: { //pointStart
                        fillColor: {
                            linearGradient: {
                                x1: 0,
                                y1: 0,
                                x2: 0,
                                y2: 1
                            },
                            stops: [
                                [0, chartColors[0]],
                                [1, Highcharts.color(chartColors[0]).setOpacity(0).get('rgba')]
                            ]
                        },
                        marker: {
                            radius: 2
                        },
                        lineWidth: 1,
                        states: {
                            hover: {
                                lineWidth: 1
                            }
                        },
                        threshold: null
                    },
                    series: {
                        dataLabels: {
                            color: '#F0F0F3',
                            style: {
                                fontSize: '13px'
                            }
                        },
                        marker: {
                            lineColor: '#333'
                        }
                    },
                    boxplot: {
                        fillColor: '#505053'
                    },
                    candlestick: {
                        lineColor: 'white'
                    },
                    errorbar: {
                        color: 'white'
                    }
                },
                legend: {
                    backgroundColor: 'rgba(0, 0, 0, 0.5)',
                    itemStyle: {
                        color: '#E0E0E3'
                    },
                    itemHoverStyle: {
                        color: '#FFF'
                    },
                    itemHiddenStyle: {
                        color: '#606063'
                    },
                    title: {
                        style: {
                            color: '#C0C0C0'
                        }
                    }
                },
                series: [
                    {
                        name: tracking.strings.DAILY_VISITS,
                        pointStart: Date.UTC(result.StartYear, result.StartMonth - 1, result.StartDay, 0, 0, 0),
                        pointInterval: 24 * 3600 * 1000,
                        yAxis: 0,
                        data: data
                    }
                ],
                credits: {
                    style: {
                        color: '#666'
                    }
                },
                labels: {
                    style: {
                        color: '#707073'
                    }
                },
                drilldown: {
                    activeAxisLabelStyle: {
                        color: '#F0F0F3'
                    },
                    activeDataLabelStyle: {
                        color: '#F0F0F3'
                    }
                },
                navigation: {
                    buttonOptions: {
                        symbolStroke: '#DDDDDD',
                        theme: {
                            fill: '#505053'
                        }
                    }
                },
                // scroll charts
                rangeSelector: {
                    buttonTheme: {
                        fill: '#505053',
                        stroke: '#000000',
                        style: {
                            color: '#CCC'
                        },
                        states: {
                            hover: {
                                fill: '#707073',
                                stroke: '#000000',
                                style: {
                                    color: 'white'
                                }
                            },
                            select: {
                                fill: '#000003',
                                stroke: '#000000',
                                style: {
                                    color: 'white'
                                }
                            }
                        }
                    },
                    inputBoxBorderColor: '#505053',
                    inputStyle: {
                        backgroundColor: '#333',
                        color: 'silver'
                    },
                    labelStyle: {
                        color: 'silver'
                    }
                },
                navigator: {
                    handles: {
                        backgroundColor: '#666',
                        borderColor: '#AAA'
                    },
                    outlineColor: '#CCC',
                    maskFill: 'rgba(255,255,255,0.1)',
                    series: {
                        color: '#7798BF',
                        lineColor: '#A6C7ED'
                    },
                    xAxis: {
                        gridLineColor: '#505053'
                    }
                },
                scrollbar: {
                    barBackgroundColor: '#808083',
                    barBorderColor: '#808083',
                    buttonArrowColor: '#CCC',
                    buttonBackgroundColor: '#606063',
                    buttonBorderColor: '#606063',
                    rifleColor: '#FFF',
                    trackBackgroundColor: '#404043',
                    trackBorderColor: '#404043'
                }
            });


            var table = $('#shared-view-visitor-activity').DataTable();
            table.clear().draw();
            var rows = [[tracking.strings.ANONYMOUS, result.AnonymousVisitCount, result.AnonymousLastVisit]];
            _.each(result.Invites, function (invite) {
                rows.push([invite.Email, invite.TotalVisits, invite.LastVisit]);
            });
            table.rows.add(rows).draw();
        });

        var dialog = tracking.data.domNodes.dialogs.sharedViewStatistics;
        openDialogPanel(dialog, tracking.strings.VISITOR_DETAILS, sharedView, false, checkForShareViewChange(sharedView), 'shared-view', 'statistics', openSharedViewStatisticsDialog);
        $(tracking.data.domNodes.infoDialogs.sharedViewInformation).dialog('close');
    }

    function querySharedViewInvites(sharedView) {
        // get existing invites list
        // maybe only if not already loaded?
        var data = {
            request: {
                id: sharedView.Id
            }
        };

        var itemList = document.getElementById('shared-with-items');
        itemList.setAttribute('data-shared-view-id', sharedView.Id);
        // clear previous data
        while (itemList.firstChild) {
            itemList.removeChild(itemList.firstChild);
        }
        return handleAjaxFormSubmission('GetSharedViewInvites', data, null, null, null, tracking.strings.MSG_GET_SHARED_VIEW_INVITES_ERROR, function (result) {
            if (parseInt(itemList.getAttribute('data-shared-view-id')) !== sharedView.Id) {
                return;
            }
            var items = document.createDocumentFragment();
            if (result.Invites.length === 0) {
                document.getElementById('shared-with-none').classList.add('is-visible');
            } else {
                document.getElementById('shared-with-none').classList.remove('is-visible');
            }
            _.each(result.Invites, function (invite) {
                var li = document.createElement('li');
                li.className = 'list-group-item';
                var head = document.createElement('div');
                head.className = 'list-group-item-header';
                var email = document.createElement('a');
                email.href = 'mailto:' + invite.Email;
                email.textContent = invite.Email;
                var time = document.createElement('span');
                time.textContent = invite.SharedOn;

                head.appendChild(email);
                head.appendChild(time);
                li.appendChild(head);

                var link = document.createElement('a');
                link.href = sharedView.Link + '/' + invite.UniqueKey;
                link.textContent = sharedView.Link + '/' + invite.UniqueKey;
                link.setAttribute('target', '_blank');
                link.setAttribute('rel', 'nofollow');
                li.appendChild(link);
                items.appendChild(li);
            });
            itemList.appendChild(items);
        });
    }

    function openSharedViewShareDialog(sharedView) {
        if (sharedView === undefined || sharedView === null) {
            return;
        }
        tracking.data.sharedView.current = sharedView;

        document.getElementById('SharedViewShareLink').textContent = sharedView.Link;

        if (!sharedView.IsPublic) {
            document.getElementById('SharedViewSharePrivate').classList.add('is-visible');
        } else {
            document.getElementById('SharedViewSharePrivate').classList.remove('is-visible');
        }

        querySharedViewInvites(sharedView);
        var dialog = tracking.data.domNodes.dialogs.sharedViewShare;
        openDialogPanel(dialog, tracking.strings.SHARE, sharedView, false, checkForShareViewChange(sharedView), 'shared-view', 'share', openSharedViewShareDialog);
        $(tracking.data.domNodes.infoDialogs.sharedViewInformation).dialog('close');
    }

    function renderDomElement(data, type, row) {
        if (data === undefined || data === null) {
            return '';
        }
        if (data instanceof Element || data instanceof HTMLDocument) {
            return data.outerHTML;
        }
        return '';
    }

    function getDescriptionForDriver(driver) {
        var description = '';
        if (driver == null) {
            return description;
        }
        if (driver.IButtonId !== null) {
            description = driver.IButtonId;
        }
        if (driver.GarminId !== null) {
            if (description !== '') {
                description += ' / ';
            }
            description += driver.GarminId;
        }
        return description; 
    }

    function openSharedViewDialog(sharedView, step) {
        if (tracking.user.isAnonymous) {
            return;
        }

        if (sharedView === undefined) {
            sharedView = null;
        }

        // only reset the current step?
        _.each(tracking.data.wizards.sharedView.steps, function (step) {
            if (step.form !== undefined) {
                step.form.resetForm();
                step.form.currentForm.reset();
            }
        });

        // hide shared view information dialog

        var dialog = tracking.data.domNodes.dialogs.sharedView;
        var wizard = dialog.querySelector('.step-wizard');
        wizard.currentStep = null;
        var wizardHeader = wizard.querySelector('.wizard-header');

        // dialog title based on section when editing
        var dialogTitle = sharedView !== null ? tracking.strings.EDIT_SHARED_VIEW : tracking.strings.ADD_SHARED_VIEW;
        
        var chosenStep = goToWizardStep(wizard, step);

        var editSharedView = null;
        if (sharedView === null) {
            tracking.data.sharedView.current = null;
            clearSharedViewData(true);
            $(tracking.data.domNodes.infoDialogs.sharedViewInformation).dialog('close');            

            editSharedView = {
                AssetGroupIds: [],
                AssetIds: [],
                PlaceIds: [],
                FenceIds: [],

                Color: '#bbbbbb',
                Description: '',
                DriverIds: [],
                Name: '',

                ExpiresOnEpoch: null,
                FromDateEpoch: null,
                ToDateEpoch: null,
                IsAvailable: true,
                IsEnabled: true,
                IsTimeframeRelative: true,
                RelativeTimeframeNumber: 7,
                RelativeTimeframeType: 'd',
                IsMessagingEnabled: false,
                IsPublic: true,
                Password: '',
                Preferences: {
                    DefaultMode: '',
                    EmergencyAudio: false,
                    Fuel: tracking.preferences.PREFERENCE_FUEL_UNIT,
                    HistoryViewNumber: '7',
                    HistoryViewType: 'd',
                    Language: tracking.preferences.PREFERENCE_LANGUAGE,
                    LatLng: tracking.preferences.PREFERENCE_LAT_LNG,
                    MapType: tracking.data.MAP_TYPES[tracking.state.currentMapType],
                    PositionAlpha: tracking.preferences.PREFERENCE_ALPHA_POSITIONS,
                    PositionConsolidation: tracking.preferences.PREFERENCE_GROUP_POSITIONS,
                    RemoveRoads: tracking.preferences.PREFERENCE_REMOVE_ROADS,
                    Speed: tracking.preferences.PREFERENCE_SPEED,
                    Timezone: tracking.preferences.PREFERENCE_TIMEZONE
                }
            };

            // new shared view, show wizard, have it be placed in the "primary" panel
            wizardHeader.classList.add('is-visible');
            dialog.classList.add('is-wizard');    
        } else {
            // editing an existing sharedView
            var deferreds = [];
            if (tracking.data.sharedView.current !== sharedView) {
                // load and display data
                deferreds.push(querySharedViewData(sharedView, null, false));
            }

            tracking.data.sharedView.current = sharedView;
            editSharedView = sharedView;
            //editSharedView = cloneSharedView(tracking.data.sharedView.current);

            // existing shared view, hide wizard
            wizardHeader.classList.remove('is-visible');
            dialog.classList.remove('is-wizard');
            
            dialogTitle = chosenStep.title;
        }

        // populate from existing saved values

        document.getElementById('SharedViewId').value = editSharedView.Id;

        // details
        var name = document.getElementById('SharedViewName');
        name.value = editSharedView.Name;

        var description = document.getElementById('SharedViewDescription');
        description.value = editSharedView.Description;

        var $color = $(document.getElementById('SharedViewColor'));
        $color.val(editSharedView.Color);
        $color.css('background-color', editSharedView.Color);
        $color.next('span').css('background-color', editSharedView.Color);

        // access controls
        if (editSharedView.IsEnabled) {
            document.getElementById('SharedViewIsEnabledYes').checked = true;
        } else {
            document.getElementById('SharedViewIsEnabledNo').checked = true;
        }

        if (editSharedView.IsPublic) {
            document.getElementById('SharedViewIsPublicYes').checked = true;
            document.getElementById('SharedViewPrivatePassword').classList.remove('is-visible');
            document.getElementById('SharedViewPassword').classList.remove('required');
        } else {
            document.getElementById('SharedViewIsPublicNo').checked = true;
            document.getElementById('SharedViewPrivatePassword').classList.add('is-visible');
            document.getElementById('SharedViewPassword').classList.add('required');
        }
        if (editSharedView.Password === undefined || editSharedView.Password === null) {
            document.getElementById('SharedViewPassword').value = '';
        } else {
            document.getElementById('SharedViewPassword').value = editSharedView.Password;
        }

        if (editSharedView.ExpiresOnEpoch !== null) {
            document.getElementById('SharedViewDoesExpireYes').checked = true;
            document.getElementById('SharedViewExpireDate').classList.add('is-visible');
            document.getElementById('SharedViewExpiresOn').classList.add('required');
            $('#SharedViewExpiresOn').datetimepicker('setDate', moment(editSharedView.ExpiresOnEpoch).toDate());
        } else {
            document.getElementById('SharedViewDoesExpireNo').checked = true;
            document.getElementById('SharedViewExpireDate').classList.remove('is-visible');
            document.getElementById('SharedViewExpiresOn').value = '';
            document.getElementById('SharedViewExpiresOn').classList.remove('required');
        }

        if (editSharedView.IsTimeframeRelative) {
            document.getElementById('SharedViewDataTimeframeRelative').checked = true;
            document.getElementById('SharedViewRelativeDateRange').classList.add('is-visible');
            document.getElementById('SharedViewDataTimeframeRelativeNumber').disabled = false;
            document.getElementById('SharedViewDataTimeframeRelativeType').disabled = false;
        } else {
            document.getElementById('SharedViewDataTimeframeRelativeNumber').disabled = true;
            document.getElementById('SharedViewDataTimeframeRelativeType').disabled = true;
            if (editSharedView.FromDateEpoch !== null || editSharedView.ToDateEpoch !== null) {
                document.getElementById('SharedViewDataTimeframeCustom').checked = true;
                document.getElementById('SharedViewDateRange').classList.add('is-visible');
            } else {
                document.getElementById('SharedViewDataTimeframeAll').checked = true;
                document.getElementById('SharedViewDateRange').classList.remove('is-visible');
            }
        }

        if (editSharedView.RelativeTimeframeNumber === null) {
            document.getElementById('SharedViewDataTimeframeRelativeNumber').value = '7';            
        } else {
            document.getElementById('SharedViewDataTimeframeRelativeNumber').value = editSharedView.RelativeTimeframeNumber;
        }
        if (editSharedView.RelativeTimeframeType === null) {
            document.getElementById('SharedViewDataTimeframeRelativeType').value = 'd';
        } else {
            document.getElementById('SharedViewDataTimeframeRelativeType').value = editSharedView.RelativeTimeframeType;
        }

        if (editSharedView.FromDateEpoch !== null) {
            $('#SharedViewFrom').datetimepicker('setDate', moment(editSharedView.FromDateEpoch).toDate());
            $('#SharedViewTo').datetimepicker('option', 'minDate', moment(editSharedView.FromDateEpoch).toDate());
        } else {
            document.getElementById('SharedViewFrom').value = '';
            $('#SharedViewTo').datetimepicker('option', 'maxDate', tracking.options.dateRangeMin === null ? null : new Date(tracking.options.dateRangeMin));
        }
        if (editSharedView.ToDateEpoch !== null) {
            $('#SharedViewTo').datetimepicker('setDate', moment(editSharedView.ToDateEpoch).toDate());
            $('#SharedViewFrom').datetimepicker('option', 'maxDate', moment(editSharedView.ToDateEpoch).toDate());
        } else {
            document.getElementById('SharedViewTo').value = '';
            $('#SharedViewFrom').datetimepicker('option', 'maxDate', tracking.options.dateRangeMax === null ? null : new Date(tracking.options.dateRangeMax));
        }

        if (editSharedView.IsMessagingEnabled) {
            document.getElementById('SharedViewIsMessagingEnabledYes').checked = true;
        } else {
            document.getElementById('SharedViewIsMessagingEnabledNo').checked = true;
        }

        // data visualization
        document.getElementById('SharedViewDefaultMode').value = editSharedView.Preferences.DefaultMode;
        //document.getElementById('SharedViewHistoryViewNumber').value = editSharedView.Preferences.HistoryViewNumber;
        //document.getElementById('SharedViewHistoryViewType').value = editSharedView.Preferences.HistoryViewType;

        //if (editSharedView.Preferences.DefaultMode === tracking.mapModes.HISTORY) {
        //    document.getElementById('SharedViewModeHistory').classList.add('is-visible');
        //} else {
        //    document.getElementById('SharedViewModeHistory').classList.remove('is-visible');
        //}

        // map settings
        document.getElementById('SharedViewMapType').value = editSharedView.Preferences.MapType;
        if (editSharedView.Preferences.PositionConsolidation) {
            document.getElementById('SharedViewPositionConsolidationYes').checked = true;
        } else {
            document.getElementById('SharedViewPositionConsolidationNo').checked = true;
        }
        if (editSharedView.Preferences.PositionAlpha) {
            document.getElementById('SharedViewPositionAlphaYes').checked = true;
        } else {
            document.getElementById('SharedViewPositionAlphaNo').checked = true;
        }
        if (editSharedView.Preferences.RemoveRoads) {
            document.getElementById('SharedViewRemoveRoadsYes').checked = true;
        } else {
            document.getElementById('SharedViewRemoveRoadsNo').checked = true;
        }
        //if (editSharedView.Preferences.EmergencyAudio) {
        //    document.getElementById('SharedViewEmergencyAudioYes').checked = true;
        //} else {
        //    document.getElementById('SharedViewEmergencyAudioNo').checked = true;
        //}

        // permissions

        // preferences
        if (editSharedView.Preferences.Language === null) {
            document.getElementById('SharedViewLanguage').value = '';
        } else {
            document.getElementById('SharedViewLanguage').value = editSharedView.Preferences.Language;
        }
        document.getElementById('SharedViewTimezone').value = editSharedView.Preferences.Timezone;
        document.getElementById('SharedViewLatLngFormat').value = editSharedView.Preferences.LatLng;
        document.getElementById('SharedViewSpeedFormat').value = editSharedView.Preferences.Speed;
        document.getElementById('SharedViewFuelFormat').value = editSharedView.Preferences.Fuel;

        // permissions
        if (editSharedView.AssetIds.length > 0) {
            document.getElementById('SharedViewPermissionsAssets').checked = true;
            document.getElementById('accordion-shared-view-assets-content').classList.add('show');
        } else {
            document.getElementById('SharedViewPermissionsAssets').checked = false;
            document.getElementById('accordion-shared-view-assets-content').classList.remove('show');
        }
            
        // groups
        if (editSharedView.AssetGroupIds.length > 0) {
            document.getElementById('SharedViewPermissionsGroups').checked = true;
            document.getElementById('accordion-shared-view-groups-content').classList.add('show');
        } else {
            document.getElementById('SharedViewPermissionsGroups').checked = false;
            document.getElementById('accordion-shared-view-groups-content').classList.remove('show');
        }

        // geofences
        if (editSharedView.FenceIds.length > 0) {
            document.getElementById('SharedViewPermissionsGeofences').checked = true;
            document.getElementById('accordion-shared-view-geofences-content').classList.add('show');
        } else {
            document.getElementById('SharedViewPermissionsGeofences').checked = false;
            document.getElementById('accordion-shared-view-geofences-content').classList.remove('show');
        }
            
        // places
        if (editSharedView.PlaceIds.length > 0) {
            document.getElementById('SharedViewPermissionsPlaces').checked = true;
            document.getElementById('accordion-shared-view-places-content').classList.add('show');
        } else {
            document.getElementById('SharedViewPermissionsPlaces').checked = false;
            document.getElementById('accordion-shared-view-places-content').classList.remove('show');
        }

        // drivers
        if (editSharedView.DriverIds.length > 0) {
            document.getElementById('SharedViewPermissionsDrivers').checked = true;
            document.getElementById('accordion-shared-view-drivers-content').classList.add('show');
        } else {
            document.getElementById('SharedViewPermissionsDrivers').checked = false;
            document.getElementById('accordion-shared-view-drivers-content').classList.remove('show');
        }
            
        //}
        // groups
        // TODO use checkbox list for groups
        var cont = document.getElementById('shared-view-groups-list');
        cont.innerHTML = '';
        var fragment = document.createDocumentFragment();
        var sortedGroups = sortAssetGroups();
        for (var i = 0; i < sortedGroups.length; i++) {
            var group = sortedGroups[i];
            addGroupToGroupListWithName(group, 0, fragment, 'SharedViewAssetGroupIds', null, true);
        }
        cont.appendChild(fragment);
        if (sharedView !== null) {
            // select groups if chosen
            _.each(sharedView.AssetGroupIds, function(assetGroupId) {
                var group = document.getElementById('SharedViewAssetGroupIds' + assetGroupId);
                if (group !== undefined) {
                    group.checked = true;
                    restrictAllChildren(group, cont);
                }
            });
        }


        // assets
        var sharedViewAssets = _.filter(tracking.data.assets, function (item) { return true; });
        populateCheckboxList('shared-view-assets-list', sharedViewAssets, 'SharedViewAssetIds',
            function (item) {
                if (sharedView === null) {
                    return false;
                }
                return _.indexOf(sharedView.AssetIds, item.Id) !== -1;
            }, function (item) {
                return item.Name;
            }, 'assets', function(item) { return item.UniqueId; });
        var sharedViewGeofences = _.filter(tracking.data.fences, function (item) { return true; });
        populateCheckboxList('shared-view-geofences-list', sharedViewGeofences, 'SharedViewFenceIds',
            function (item) {
                if (sharedView === null) {
                    return false;
                }
                return _.indexOf(sharedView.FenceIds, item.Id) !== -1;
            }, function (item) {
                return item.Name;
            }, 'fences', function(item) { return item.Description; });
        var sharedViewPlaces = _.filter(tracking.data.places, function (item) { return true; });
        populateCheckboxList('shared-view-places-list', sharedViewPlaces, 'SharedViewPlaceIds',
            function (item) {
                if (sharedView === null) {
                    return false;
                }
                return _.indexOf(sharedView.PlaceIds, item.Id) !== -1;
            }, function (item) {
                return item.Name;
            }, 'places', function(item) { return item.Description; } );
        var sharedViewDrivers = _.filter(tracking.data.drivers, function (item) { return true; });
        populateCheckboxList('shared-view-drivers-list', sharedViewDrivers, 'SharedViewDriverIds',
            function (item) {
                if (sharedView === null) {
                    return false;
                }
                return _.indexOf(sharedView.DriverIds, item.Id) !== -1;
            }, function (item) {
                return item.DriverId;
        }, 'drivers', getDescriptionForDriver);

        // disable groupings if they have no options available
        var assetsOption = document.getElementById('SharedViewPermissionsAssets');
        if (tracking.data.assets.length === 0) {
            assetsOption.disabled = true;
        } else if (!assetsOption.classList.contains('disabled')) {
            assetsOption.disabled = false;
        }
        var fencesOption = document.getElementById('SharedViewPermissionsGeofences');
        if (tracking.data.fences.length === 0) {
            fencesOption.disabled = true;
        } else if (!fencesOption.classList.contains('disabled')) {
            fencesOption.disabled = false;
        }
        var placesOption = document.getElementById('SharedViewPermissionsPlaces');
        if (tracking.data.places.length === 0) {
            placesOption.disabled = true;
        } else if (!placesOption.classList.contains('disabled')) {
            placesOption.disabled = false;
        }
        var assetGroupsOption = document.getElementById('SharedViewPermissionsGroups');
        if (tracking.data.groups.length === 1) {
            assetGroupsOption.disabled = true;
        } else if (!assetGroupsOption.classList.contains('disabled')) {
            assetGroupsOption.disabled = false;
        }
        var driversOption = document.getElementById('SharedViewPermissionsDrivers');
        if (tracking.data.drivers.length === 0) {
            driversOption.disabled = true;
        } else if (!driversOption.classList.contains('disabled')) {
            driversOption.disabled = false;
        }

        tracking.data.sharedView.temp = cloneSharedView(editSharedView);

        var buttonText = sharedView !== null ? tracking.strings.SAVE_CHANGES : tracking.strings.CREATE_SHARED_VIEW;
        changePrimaryButtonLabel(tracking.data.domNodes.dialogs.sharedView, buttonText);
        var callback = function(item) { openSharedViewDialog(item, step); };
        openDialogPanel(dialog, dialogTitle, sharedView, false, checkForShareViewChange(sharedView, true), 'shared-view', (sharedView !== null ? 'edit-shared-view' : null), (sharedView !== null ? callback : null));
        $(tracking.data.domNodes.infoDialogs.sharedViewInformation).dialog('close');
        if (sharedView === null) {
            moveSecondaryPanelToPrimary();
        }
    }

    function openPlaceDialog(place) {
        tracking.data.validation.placeFind.resetForm();
        tracking.data.validation.placeFind.currentForm.reset();
        tracking.data.validation.placeSearch.resetForm();
        tracking.data.validation.placeSearch.currentForm.reset();
        tracking.data.validation.placeAdd.resetForm();
        tracking.data.validation.placeAdd.currentForm.reset();

        var dialog = tracking.data.domNodes.dialogs.addPlace;
        var dialogTitle = place !== null ? tracking.strings.EDIT_PLACE : tracking.strings.ADD_PLACE;

        var buttonText = place !== null ? tracking.strings.SAVE_CHANGES : tracking.strings.CREATE_PLACE;
        changePrimaryButtonLabel(tracking.data.domNodes.dialogs.addPlace, buttonText);

        openDialogPanel(dialog, dialogTitle, place, false, function () {
            tracking.state.isChoosingPlace = false;
            stopChoosingMapLocation(tracking.state.mapClickHandlers.PLACE);

            // disable marker dragging
            if (place !== null) {
                var updatedPlace = findPlaceById(place.Id);
                var location = updatedPlace.Location;
                if (location.marker !== undefined && location.marker !== null && location.marker.data !== undefined && location.marker.data.isMoved === true) {
                    if (location.marker.dragging !== undefined && location.marker.dragging !== null) {
                        location.marker.dragging.disable();
                    }
                    // restore the marker to the original location, if not saved
                    location.marker.setLatLng(L.latLng(updatedPlace.Location.Lat, updatedPlace.Location.Lng));
                }
            }
        }, 'place', (place !== null ? 'edit-place' : null), (place !== null ? openPlaceDialog : null));

        $('#edit-place-photo').hide();
        $('#edit-place-remove-photo').val('false');
        if (place == null) {
            // new place
            tracking.state.isChoosingPlace = true;
            startChoosingMapLocation(tracking.state.mapClickHandlers.PLACE);
            $('#accordion-edit-place-main-content').collapse('show');
            $('#add-place-search').addClass('is-visible');
            $('#form-add-place').removeClass('is-visible');
            $('#rbEditPlaceColor-red').prop('checked', true);
        } else {
            // editing existing place
            $('#add-place-search').removeClass('is-visible');
            $('#form-add-place').addClass('is-visible');

            // pan to current location and make place draggable
            var location = place.Location;
            if (location.marker !== undefined && location.marker !== null) {
                enablePlaceMarkerDraggable(location.marker);
            }
            var loc = L.latLng(location.Lat, location.Lng);
            tracking.map.panTo(loc);

            $('#add-place-location').text(convertToLatLngPreference(loc.lat, loc.lng, location.Grid));
            if (place.PhotoType != null && place.PhotoType != '') {
                $('#edit-place-photo img').attr('src', '/uploads/images/places/' + place.Id + '_thumb.' + place.PhotoType + '?rnd=' + new Date().getTime());
                $('#edit-place-photo a').attr('href', '/uploads/images/places/' + place.Id + '.' + place.PhotoType);
                $('#edit-place-photo').show();
            }
            $('#txtPlaceContact').val(place.Contact);
            $('#txtPlaceUniqueKey').val(place.UniqueKey);
            $('#txtPlaceName').val(place.Name);
            $('#txtPlaceDescription').val(place.Description);
            $('#rbEditPlaceColor-' + place.Color).prop('checked', true);
            $('#hfPlaceLat').val(place.Location.Lat);
            $('#hfPlaceLng').val(place.Location.Lng);
            $('#hfPlaceId').val(place.Id);

            if (place.Attributes != null) {
                for (var i = 0; i < place.Attributes.length; i++) {
                    var attr = place.Attributes[i];
                    $('#EditPlaceAttribute' + attr.Id).val(attr.Value);
                }
            }
        }

        $('#accordion-edit-place-users-head button').prop('disabled', !tracking.user.isAdmin);
        if (tracking.user.isAdmin) {
            var cont = document.getElementById('place-users');
            // populate users
            var inputs = [];
            for (var k = 0; k < tracking.data.users.length; k++) {
                var user = tracking.data.users[k];
                var isChecked = false;
                if (place != null) {
                    for (var i = 0; i < tracking.data.placeUsers.length; i++) {
                        if (tracking.data.placeUsers[i].PlaceId != place.Id) {
                            continue;
                        }

                        for (var j = 0; j < tracking.data.placeUsers[i].UserIds.length; j++) {
                            if (tracking.data.placeUsers[i].UserIds[j] == user.Id) {
                                isChecked = true;
                                break;
                            }
                        }
                        break;
                    }
                }
                var checkbox = el('input.custom-control-input', { type: 'checkbox', id: 'EditPlaceUser' + k, name: 'EditPlaceUserIds', value: user.Id, checked: isChecked });
                if (isChecked) {
                    checkbox.setAttribute('checked', 'checked');
                }
                inputs.push(el('div.custom-control.custom-checkbox', [
                    checkbox,
                    text(' '),
                    el('label.custom-control-label', { for: 'EditPlaceUser' + k }, user.Name + ' (' + user.Username + ')')
                ]));
            }
            setChildren(cont, inputs);
        }
    }

    function populateGeofenceDocuments(fence) {
        var docs = $j('#geofence-documents');
        $j('tbody tr', docs).not('.noresults').remove();
        var results = $j('tr.noresults', docs).show();

        if (fence != null) {
            if ((fence.Documents != null) && (fence.Documents.length > 0)) {
                results.hide();
                for (var i = 0; i < fence.Documents.length; i++) {
                    var doc = fence.Documents[i];
                    var row = $j('<tr>')
                                .append($j('<td class="center">').append($j('<span>').addClass(doc.IsExpired ? 'expired' : (doc.IsAboutToExpire ? 'abouttoexpire' : 'valid'))))
                                .append($j('<td>').append($j('<a target="_blank">').text(doc.Name).attr('href', '/uploads/fences/' + fence.Id + '/' + doc.Filename)))
                                .append($j('<td>').text(doc.StartDate))
                                .append($j('<td>').text(doc.EndDate))
                                .append($j('<td class="center">').append($j('<button class="command delete btn btn-outline-danger">').data('docId', doc.Id).data('fenceId', fence.Id).text(tracking.strings.DELETE)));
                    $j('tbody', docs).append(row);
                }
            }
        }
    }

    function changePrimaryButtonLabel(dialog, text) {
        var id = null;
        var btn = id !== null ? document.getElementById(id) : (dialog !== null ? dialog.querySelector('.dialog-buttons .btn-primary') : null);
        if (btn === null) {
            return;
        }
        var span = btn.querySelector('span');
        if (span !== null) {
            span.textContent = text;
        } else {
            btn.textContent = text;
        }

        //var icn = btn.querySelector('use');
        //if (icn !== null && icon !== undefined && icon !== null) {
        //    icn.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#' + icon);
        //}
    }

    function openGeofenceDialog(fence) {
        if (fence === null && tracking.options.enabledFeatures.indexOf('GEOFENCING') === -1) {
            openUpgradeDialog();
            return;
        }

        var dialog = $(tracking.data.domNodes.dialogs.geofence);
        console.log('opening geofence dialog');
        tracking.data.validation.geofence.resetForm();
        tracking.data.validation.geofence.currentForm.reset();

        var serverSideOption = document.getElementById('edit-fence-server-side');
        if (_.find(tracking.data.assets, function (asset) { return tracking.devices.SKYWAVE_IDP.indexOf(asset.DeviceId) !== -1; }) !== undefined) {
            serverSideOption.classList.add('is-visible');
        }

        var dialogTitle = fence !== null ? tracking.strings.UPDATE_GEOFENCE : tracking.strings.ADD_GEOFENCE;
        openDialogPanel(tracking.data.domNodes.dialogs.geofence, dialogTitle, fence, false, function () {
            // going to call this after crap above
            console.log('closing geofence dialog');
            if (tracking.state.isEditingGeofence) {
                // add back the fence paths
                var id = $j('#hfEditFenceId', dialog).val();
                var editedFence = findFenceById(id);
                addFencePath(editedFence);
            }
            cleanupFenceEditing();
        }, 'fence', (fence !== null ? 'edit-fence' : null), (fence !== null ? openGeofenceDialog : null));

        tracking.state.isEditingGeofence = fence !== null;

        $j('#add-document-status').text('').hide();
        $j('#edit-geofence-photo').hide();

        var buttonText = tracking.state.isEditingGeofence ? tracking.strings.SAVE_CHANGES : tracking.strings.CREATE_GEOFENCE;
        changePrimaryButtonLabel(tracking.data.domNodes.dialogs.geofence, buttonText);

        populateGeofenceDocuments(fence);

        $('#edit-fence-accordion .primary-card button').prop('disabled', false);

        if (tracking.user.isAdmin) {
            var cont = dialog.find('.user-list')[0];

            var inputs = [];
            for (var k = 0; k < tracking.data.users.length; k++) {
                var user = tracking.data.users[k];
                var isChecked = false;
                if (fence != null) {
                    for (var i = 0; i < tracking.data.fenceUsers.length; i++) {
                        if (tracking.data.fenceUsers[i].FenceId != fence.Id)
                            continue;

                        for (var j = 0; j < tracking.data.fenceUsers[i].UserIds.length; j++) {
                            if (tracking.data.fenceUsers[i].UserIds[j] == user.Id) {
                                isChecked = true;
                                break;
                            }
                        }
                        break;
                    }
                }
                var checkbox = el('input.custom-control-input', { type: 'checkbox', id: 'EditFenceUser' + k, name: 'EditFenceUserIds', value: user.Id, checked: isChecked });
                if (isChecked) {
                    checkbox.setAttribute('checked', 'checked');
                }
                inputs.push(el('div.custom-control.custom-checkbox', [
                    checkbox,
                    text(' '),
                    el('label.custom-control-label', { for: 'EditFenceUser' + k }, user.Name + ' (' + user.Username + ')')
                ]));
            }
            setChildren(cont, inputs);
        } else {
            $('#accordion-edit-fence-users-head button').prop('disabled', true);
            $('#accordion-edit-fence-documents-head button').prop('disabled', true);
        }

        $('#edit-geofence-remove-photo').val('false');
        if (!tracking.state.isEditingGeofence) {
            $('#accordion-edit-fence-main-content').collapse('show');
            $('#accordion-edit-fence-documents-head button').prop('disabled', true);
        } else {
            // add hidden id
            $j(dialog).data('fenceId', fence.Id);
            $j('#hfEditFenceId').val(fence.Id);
            $j('#txtGeofenceName').val(fence.Name);
            $j('#txtGeofenceColor').val(fence.Color);
            $j('#txtGeofenceDescription').val(fence.Description);
            $j('#ddlFenceContactId').val(fence.ContactId);
            $j('#chkGeofenceServerSide').prop('checked', fence.IsServerSideOnly);
            $j('#txtGeofenceColor').css('background-color', fence.Color);
            $j('#txtGeofenceColor').next('span').css('background-color', fence.Color);

            if (fence.PhotoType != null && fence.PhotoType != '') {
                $j('#edit-geofence-photo img').attr('src', '/uploads/images/fences/' + fence.Id + '_thumb.' + fence.PhotoType + '?rnd=' + new Date().getTime());
                $j('#edit-geofence-photo a').attr('href', '/uploads/images/fences/' + fence.Id + '.' + fence.PhotoType);
                $j('#edit-geofence-photo').show();
            }
            // attribute fields
            if (fence.Attributes != null) {
                for (var i = 0; i < fence.Attributes.length; i++) {
                    var attr = fence.Attributes[i];
                    $j('#EditGeofenceAttribute' + attr.Id).val(attr.Value);
                    // todo: special case for file/photo upload?
                }
            }

            // hide current fence
            editFenceSegments(fence); // populate segments
            MapToolbar.select(null); // clear edit selection
            centerOnFence(fence.Id);
            removeFencePaths(fence.Id);
        }

        document.getElementById('txtGeofenceName').focus();
    }

    function openSendMessageToFenceDialog(fence) {
        openSendMessageDialog(null, null, fence);
    }

    function openSendMessageToAssetGroupDialog(group) {
        openSendMessageDialog(null, group);
    }

    function openSendMessageToAssetDialog(asset) {
        openSendMessageDialog(asset);
    }

    function openSendMessageDialog(asset, group, fence) {
        if (asset == null
            && group == null
            && fence == null) {
            return;
        }
        tracking.data.validation.sendMessage.resetForm();
        tracking.data.validation.sendMessage.currentForm.reset();

        var dialog = $j(tracking.data.domNodes.dialogs.sendMessage);
        $j('#send-message-assets').hide();
        $j('#send-message-assets-container').empty();
        $j('#sendMessageStatus').text('').hide();
        $j('#send-message-beam-templates').hide();
        $j('#send-message-quick').hide();
        $j('#quick-messages').empty();
        $j('#message-text-label').text(tracking.strings.TEXT + ':');
        $j('#txtMessageText').addClass('required');
        $j('div.idp-only', dialog).hide();
        $j('#chkMessageNotifyOnRead').prop('checked', false);
        $j(dialog).removeData('assetId');
        $j(dialog).removeData('groupId');
        $j(dialog).removeData('fenceId');
        if (asset != null) {
            var device = findDeviceById(asset.DeviceId);
            if (!device.SupportsMessaging) {
                return;
            }

            $j('#txtMessageDestination').text(asset.Name + ' (' + asset.UniqueId + ')');

            // message length indicator based on device of asset
            // todo: standardsize messagemaxsize to be characters... (byte limits aren't much use in the UI)
            // clear previous maxchars
            $j('#txtMessageText').val('').maxChar('destroy');
            if ((device.MessageMaxSize > 0) && (device.MessageMaxSize < 1500)) {
                $j('#txtMessageText').maxChar('init', device.MessageMaxSize, { 'indicator': 'txtMessageTextIndicator' });
            }

            if (asset.DeviceId === tracking.devices.BEAM) {
                $j('#send-message-beam-templates').show().find('option:selected').prop('selected', false);
            }

            if (device.SupportsQuickMessages) {
                if (asset.QuickMessageToMobileTemplateId != '') {
                    $j('#send-message-quick').show().find('#quick-messages').append($j('<option>').val('').text(''));
                    loadQuickMessageTemplate(asset.QuickMessageToMobileTemplateId);
                }
            }

            if (($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) != -1) || ($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) != -1)) {
                $j('div.idp-only', dialog).show();
            }
            $j(dialog).data('assetId', asset.Id);

            openDialogPanel(tracking.data.domNodes.dialogs.sendMessage, tracking.strings.SEND_MESSAGE, asset, false, null, 'asset', 'send-message', openSendMessageToAssetDialog);
        } else if (group != null) {
            $j('#txtMessageDestination').text(group.Name);
            $j(dialog).data('groupId', group.Id);
            openDialogPanel(tracking.data.domNodes.dialogs.sendMessage, tracking.strings.SEND_MESSAGE, group, false, null, 'group', 'send-message', openSendMessageToAssetGroupDialog);
        } else if (fence != null) {
            $j('#txtMessageDestination').text(fence.Name);
            $j(dialog).data('fenceId', fence.Id);
            // populate assets
            var assetIds = findAssetIdsInGeofence(fence);
            var cont = $j('#send-message-assets-container');
            var assets = new Array();
            for (var i = 0; i < assetIds.length; i++) {
                var asset = findAssetById(assetIds[i]);
                if (asset == null)
                    continue;
                assets.push(asset);
            }
            assets.sort(sortByName);
            for (var i = 0; i < assets.length; i++) {
                var asset = assets[i];
                var input = $j('<input class="custom-control-input" type="checkbox" name="SendMessageAssetIds" id="SendMessageAssetId' + asset.Id + '"/>').val(asset.Id).prop('checked', true);
                var label = $j('<label class="custom-control-label" for="SendMessageAssetId' + asset.Id + '"/>').text(asset.Name);
                cont.append($('<div class="custom-control custom-checkbox">').append(input).append(label));
            }
            $j('#send-message-assets').show();

            openDialogPanel(tracking.data.domNodes.dialogs.sendMessage, tracking.strings.SEND_MESSAGE, fence, false, null, 'fence', 'send-message', openSendMessageToFenceDialog);
        }

        document.getElementById('txtMessageText').focus();
    }

    function getInputLabelById(asset, id) {
        if (asset == null)
            return '';
        if (asset.InputLabels == null)
            return '';
        for (var i = 0; i < asset.InputLabels.length; i++) {
            var label = asset.InputLabels[i];
            if (label.InputPin == id)
                return label;
        }
        return '';
    }

    function getInputLabelByIndex(asset, index) {
        if (asset == null)
            return null;
        if (asset.InputLabels == null)
            return null;
        for (var i = 0; i < asset.InputLabels.length; i++) {
            var label = asset.InputLabels[i];
            if (label.Number == index)
                return label;
            //return label.Label;
        }
        return null;
    }

    function getOutputLabelByIndex(asset, index) {
        if (asset == null)
            return null;
        if (asset.OutputLabels == null)
            return null;
        for (var i = 0; i < asset.OutputLabels.length; i++) {
            var label = asset.OutputLabels[i];
            if (label.Number == index)
                return label;
        }
        return null;
    }

    function createLabelText(index, value, label) {
        var output = 'Output ' + index;
        var setting = '';
        switch (value) {
            case 0:
                setting = tracking.strings.OFF;
                break;
            case 1:
                setting = tracking.strings.ON;
                break;
            case 2:
                setting = '1 second pulse';
                break;
        }
        if (label != null) {
            if ((label.Label != null) && (label.Label != '')) {
                output = label.Label;
            }
        }
    }

    function labelOrDefault(label, orDefault) {
        if (label == null)
            return orDefault;
        if (label == '')
            return orDefault;
        return label;
    }

    function createLabel(index, value, label) {
        var output = tracking.strings.OUTPUT_NUMBER.replace('{0}', index);
        var option = $j('<option></option>');
        option.val(index + ',' + value);
        var defaultText = tracking.strings.SET_IO_COMMAND;
        if (label == null) {
            // defaults
            var text = defaultText.replace('{0}', output);
            switch (value) {
                case 0: // Off
                    text = text.replace('{1}', tracking.strings.OFF);
                    break;
                case 1: // On
                    text = text.replace('{1}', tracking.strings.ON);
                    break;
                case 2: // Pulse
                    text = text.replace('{1}', tracking.strings.ONE_SECOND_PULSE);
                    break;
            }
            option.text(text);
        } else {
            // user preferences
            var text = defaultText.replace('{0}', labelOrDefault(label.Label, output));
            switch (value) {
                case 0: // Off
                    if (label.OffIsDisabled)
                        return null;
                    text = text.replace('{1}', labelOrDefault(label.OffLabel, tracking.strings.OFF));
                    break;
                case 1: // On
                    if (label.OnIsDisabled)
                        return null;
                    text = text.replace('{1}', labelOrDefault(label.OnLabel, tracking.strings.ON));
                    break;
                case 2: // Pulse
                    if (label.PulseIsDisabled)
                        return null;
                    text = text.replace('{1}', labelOrDefault(label.PulseLabel, tracking.strings.ONE_SECOND_PULSE));
                    break;
            }
            option.text(text);
        }
        return option;
    }

    function addGroupToGroupListInAddGroupDialog(group, level, options) {
        if (group.IsDefault) {
            return;
        }
        var prepend = '';
        for(var i = 0; i < level; i++) {
            prepend += '--';
        }
        if(prepend != '') {
            prepend += ' ';
        }
        options.push(el('option', { value: group.Id }, prepend + group.Name));
        level++;
        if (group.GroupIds != null) {
            for (var k = 0; k < group.GroupIds.length; k++) {
                var subGroup = findGroupById(group.GroupIds[k]);
                addGroupToGroupListInAddGroupDialog(subGroup, level, options);
            }
        }
    }

    function addGroupToGroupListInAssetEditDialog(group, level, cont, asset) {
        if (group.IsDefault) {
            return;
        }
        var isChecked = false;
        if (asset != null) {
            for (var j = 0; j < asset.GroupIds.length; j++) {
                if (asset.GroupIds[j] == group.Id) {
                    isChecked = true;
                    break;
                }
            }
        }
        var checkbox = el('input.custom-control-input', { type: 'checkbox', id: 'EditAssetGroup' + group.Id, name: 'EditAssetGroupIds', value: group.Id, checked: isChecked });
        if (isChecked) {
            checkbox.setAttribute('checked', 'checked');
        }
        var input = el('div.custom-control.custom-checkbox', { style: { marginLeft: (level * 15) + 'px' } }, [
            checkbox,
            text(' '),
            el('label.custom-control-label', { for: 'EditAssetGroup' + group.Id }, group.Name)
        ]);
        mount(cont, input);
        level++;
        if (group.GroupIds != null) {
            for (var k = 0; k < group.GroupIds.length; k++) {
                var subGroup = findGroupById(group.GroupIds[k]);
                addGroupToGroupListInAssetEditDialog(subGroup, level, cont, asset);
            }
        }
    }

    function addItemGroupToItemGroupListInAssetEditDialog(group, level, cont, selectedGroupIds) {
        if (group.IsDefault) {
            return;
        }
        var isChecked = _.indexOf(selectedGroupIds, group.Id) !== -1;
        var checkbox = el('input.custom-control-input', { type: 'checkbox', id: 'EditAssetDriverGroup' + group.Id, name: 'EditAssetDriverGroupIds', value: group.Id, checked: isChecked });
        if (isChecked) {
            checkbox.setAttribute('checked', 'checked');
        }
        var input = el('div.custom-control.custom-checkbox', { style: { marginLeft: (level * 15) + 'px' } }, [
            checkbox,
            text(' '),
            el('label.custom-control-label', { for: 'EditAssetDriverGroup' + group.Id }, group.Name)
        ]);
        mount(cont, input);
        level++;
        if (group.GroupIds != null) {
            for (var k = 0; k < group.GroupIds.length; k++) {
                var subGroup = _.find(tracking.data.driverGroups, { Id: group.GroupIds[k] });
                addItemGroupToItemGroupListInAssetEditDialog(subGroup, level, cont, selectedGroupIds);
            }
        }
    }

    function unrestrictDirectChildren(item, cont) {
        var items = cont.querySelectorAll('input[data-parent="' + item.value + '"]');
        _.each(items, function (item) {
            item.disabled = false;
        });
        //$('input[data-parent=' + item.val() + ']').each(function (index, elem) {
        //    var child = $(elem);
        //    child.prop('disabled', false);
        //});
    }

    function restrictAllChildren(item, cont) {
        // all children checkboxes must be checked and disabled
        var items = cont.querySelectorAll('input[data-parent="' + item.value + '"]');
        _.each(items, function (item) {
            item.disabled = true;
            item.checked = true;
            restrictAllChildren(item, cont);
        });
        //$('input[data-parent=' + item.val() + ']').each(function (index, elem) {
        //    var child = $(elem);
        //    child.prop('checked', true).prop('disabled', true);
        //    restrictAllChildren(child);
        //});
    }

    function addGroupToGroupListWithName(group, level, cont, name, parent, includeIcon) {
        if (group.IsDefault) {
            return;
        }
        
        var control = document.createElement('div');
        control.className = 'custom-control custom-checkbox';
        if (level > 0) {
            control.style.marginLeft = (level * 15) + 'px';
        }

        var input = document.createElement('input');
        input.className = 'custom-control-input'
        input.type = 'checkbox';
        input.id = name + group.Id;
        input.name = name;
        input.value = group.Id;
        if (parent !== undefined && parent !== null) {
            input.setAttribute('data-parent', parent.Id);
        }

        var label = document.createElement('label');
        label.className = 'custom-control-label';
        label.setAttribute('for', name + group.Id);

        var span = document.createElement('span');
        span.textContent = group.Name;

        if (includeIcon === true) {
            var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            var iconType = document.createElementNS('http://www.w3.org/2000/svg', 'use');
            iconType.setAttributeNS('http://www.w3.org/1999/xlink', 'href', getSvgIconForItemType('groups', group));
            svg.setAttribute('style', 'color: ' + group.Color);
            svg.appendChild(iconType);
            label.appendChild(svg);
            label.classList.add('has-svg-icon');
        }

        label.appendChild(span);
        control.appendChild(input);
        control.appendChild(label);
        cont.appendChild(control);
        level++;
        if (group.GroupIds != null) {
            for (var k = 0; k < group.GroupIds.length; k++) {
                var subGroup = findGroupById(group.GroupIds[k]);
                addGroupToGroupListWithName(subGroup, level, cont, name, group, includeIcon);
            }
        }
    }

    function requestLastContact(assetId) {
        var data = {
            assetId: assetId
        };
        var status = document.getElementById('edit-asset-status');
        toggleLoadingMessage(true, 'asset-contact');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetLastContact'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    if (result.Success == true) {
                        $j('#AssetContact').show();
                        if (result.ContactedOn != null) {
                            $j('#AssetLastContact').text(result.ContactedOn);
                        } else {
                            $j('#AssetLastContact').text(tracking.strings.NEVER);
                        }
                        if (result.IPAddress != null) {
                            $j('#AssetLastIP').show().text('(' + result.IPAddress + ')');
                        } else {
                            $j('#AssetLastIP').hide();
                        }
                    } else {
                        // message failure, keep text field to allow retry
                        formShowErrorMessage(status, tracking.strings.MSG_LAST_CONTACT_ERROR);
                        if (result.ErrorMessage != null && result.ErrorMessage != '') {
                            formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                        }
                    }
                }
                toggleLoadingMessage(false, 'asset-contact');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_LAST_CONTACT_ERROR);
                toggleLoadingMessage(false, 'asset-contact');
            }
        });
    }

    function openEditAssetDialog(asset) {
        var dialog = $(tracking.data.domNodes.dialogs.editAsset);
        var dialogTitle;
        tracking.state.isAddingAsset = asset === null;

        var buttonText = !tracking.state.isAddingAsset ? tracking.strings.SAVE_CHANGES : tracking.strings.CREATE_ASSET;
        changePrimaryButtonLabel(tracking.data.domNodes.dialogs.editAsset, buttonText);

        if (tracking.state.isAddingAsset) {
            if (tracking.user.assetLimit !== null && tracking.data.assets.length >= tracking.user.assetLimit) {
                $j('#assetlimit-modal').modal('show');
                return;
            }

            dialogTitle = tracking.strings.ADD_ASSET_TITLE;
            //dialogTitle = '<svg><use xlink:href="/content/svg/tracking.svg?v=15#plus-circle"></use></svg> <span>' + tracking.strings.ADD_ASSET_TITLE + '</span>';
            $j('.portal-registration,.dnid-download', dialog).hide();
        } else {
            dialogTitle = tracking.strings.EDIT_ASSET;
            //dialogTitle = '<svg><use xlink:href="/content/svg/tracking.svg?v=15#edit"></use></svg> <span>' + tracking.strings.EDIT_ASSET + ' ' + asset.Name + '</span>';
            $j('.portal-registration,.dnid-download', dialog).show();
        }

        var id = null;
        tracking.data.validation.editAsset.resetForm(); // reset validation
        tracking.data.validation.editAsset.currentForm.reset();
        if (asset === null) {
            // make first tab active when adding
            $('#accordion-edit-asset-main-content').collapse('show');
        }
        $j('#SendPortalRegistration', dialog).prop('disabled', false);
        $j('#txtEditAssetUniqueId', dialog).prop('disabled', (tracking.user.assetControlFederated ? true : false));
        $j('#edit-asset-photo', dialog).hide();
        $j('#edit-asset-icon', dialog).hide();
        $j('#txtEditAssetFuelEfficiency,#txtEditAssetCustomMessage', dialog).val('');
        $j('input[name=EditAssetContactIds]', dialog).prop('checked', false);
        $j('#ddlAssetSRID', dialog).val('4326');
        $j('#ddlEditAssetCannedMessageGroup', dialog).val('');
        $j('#ddlEditAssetGatewayAccount', dialog).val('');
        $j('#ddlEditAssetDriverStatusTemplate,#ddlEditAssetDriverDeviceType', dialog).val('');
        $j('#ddlEditAssetQuickMessageFromMobileTemplate,#ddlEditAssetQuickMessageToMobileTemplate', dialog).val('');
        $j('#ddlEditAssetConfiguration', dialog).val('None');
        $j('label[for=ddlEditAssetDevice]', dialog).parent().show();
        $j('#EditAssetDevice', dialog).parent().show();
        $('#ddlEditAssetDeviceManufacturer').change();
        $j('#AssetContact', dialog).hide();
        var softwarePackage = document.getElementById('ddlEditAssetSoftwarePackage');
        softwarePackage.value = '';
        document.getElementById('edit-asset-remove-photo').value = 'false';

        if (!tracking.user.limitAssetEditing) {
            if (asset != null) {
                // get asset's latest status
                $j('#UpdateAssetStatus', dialog).prop('disabled', false);
                $('#RefreshAssetStatus,#ResetAssetStatus', dialog).removeClass('disabled').prop('disabled', false);
                loadAssetStatus(asset);
                loadAssetAlerts(asset);
                loadAssetAdditional(asset);
                //loadAssetDrivers(asset);

                // query for last contact/ip information
                requestLastContact(asset.Id);

                id = asset.Id;
                var device = findDeviceById(asset.DeviceId);
                $j('#hfEditAssetId').val(id);
                // populate existing values
                $j('#txtEditAssetName').val(asset.Name);
                $j('#txtEditAssetUniqueId').val(asset.UniqueId);
                $j('#txtEditAssetSecondaryUniqueId').val(asset.SecondaryUniqueId);
                $j('#txtEditAssetSensitivity').val(asset.Sensitivity);
                // disallow device selection, output chosen device instead
                $j('#ddlEditAssetDevice').val(asset.DeviceId);

                if (tracking.options.hideDeviceName) {
                    $j('#EditAssetDevice').parent().hide();
                    $j('label[for=ddlEditAssetDevice]', dialog).parent().hide();
                } else {
                    $j('#EditAssetDevice').val(device.Manufacturer + ': ' + device.Name).show();
                }
                $j('#ddlEditAssetDevice,#ddlEditAssetDeviceManufacturer').hide();

                $j('input[name=rbEditAssetColor][value="' + asset.Color + '"]:radio', dialog[0]).prop('checked', true);
                var iconChosen = $j('input[name=rbEditAssetIcon][value="' + asset.Class + '"]:radio', dialog[0]).prop('checked', true);
                var chosenSet = iconChosen.parent().parent()[0].getAttribute('data-icon-set');
                if (chosenSet === null) {
                    chosenSet = tracking.options.defaultIconSet;
                }
                $('#EditAssetIconSet').val(chosenSet);
                $('.icon-set', dialog[0]).removeClass('is-visible');
                $('#icon-set-' + chosenSet).addClass('is-visible');
                if (asset.Class == 'Upload') {
                    $j('#edit-asset-icon img').attr('src', '/markers/upload?aid=' + asset.Id + '&lm=' + asset.IconModified);
                    $j('#edit-asset-icon').show();
                    $('#edit-asset-icon-tab-upload').tab('show');
                } else {
                    $('#edit-asset-icon-tab-system').tab('show');
                    // icon set
                }
                var icons = $('input[name=rbEditAssetIcon]', dialog[0]);
                _.each(icons, function(icon) {
                    var iconImage = $(icon).next().children('img').attr('src', '/markers/' + icon.value + '?color=' + asset.Color);
                });

                // attribute fields
                if (asset.Attributes != null) {
                    for (var i = 0; i < asset.Attributes.length; i++) {
                        var attr = asset.Attributes[i];
                        $j('#EditAssetAttribute' + attr.Id).val(attr.Value);
                        // todo: special case for file/photo upload?
                    }
                }

                // extraneous fields
                $j('#txtEditAssetIMEI').val(asset.IMEI);
                $j('#txtEditAssetSerialNumber').val(asset.SerialNumber);
                $j('#txtEditAssetInmarsatCSerialNumber').val(asset.SerialNumber);
                $j('#txtEditAssetSIMICCID').val(asset.SIMICCID);
                $j('#txtEditAssetSIMPhoneNumber').val(asset.SIMPhoneNumber);
                $j('#txtEditAssetSIMPIN').val(asset.SIMPIN);
                $j('#txtEditAssetSIMPINUnlock').val(asset.SIMPINUnlock);
                $j('#txtEditAssetSIMProvider').val(asset.SIMProvider);
                $j('#txtEditAssetMission').val(asset.Mission);
                $j('#txtEditAssetDriver').val(asset.Driver);
                $j('#txtEditAssetPhoneNumber').val(asset.PhoneNumber);
                $j('#txtEditAssetPlateNumber').val(asset.PlateNumber);
                $j('#txtEditAssetLicenseNumber').val(asset.LicenseNumber);
                $j('#txtEditAssetNationalIdentityCardNumber').val(asset.NationalIdentityCardNumber);
                $j('#txtEditAssetNotes').val(asset.Notes);
                $j('#txtEditAssetSoftwareVersion').val(asset.SoftwareVersion);
                $j('#txtEditAssetVehicleMakeAndModel').val(asset.VehicleMakeAndModel);
                $j('#txtEditAssetVehiclePurchaseDate').val(asset.VehiclePurchaseDate);
                $j('#txtEditAssetVehicleVIN').val(asset.VehicleVIN);
                $j('#txtEditAssetVesselName').val(asset.VesselName);
                $j('#txtEditAssetVesselCallSign').val(asset.VesselCallSign);
                $j('#txtEditAssetVesselIMONumber').val(asset.VesselIMONumber);
                $j('#txtEditAssetVesselFlagRegistry').val(asset.VesselFlagRegistry);
                $j('#txtEditAssetVesselTonnage').val(asset.VesselTonnage);
                $j('#txtEditAssetVesselClass').val(asset.VesselClass);
                $j('#txtEditAssetVesselSkipper').val(asset.VesselSkipper);
                $j('#txtEditAssetVesselMMSI').val(asset.VesselMMSI);
                $j('#txtEditAssetCustomMessage').val(asset.CustomMessage);
                if (asset.ContactIds != null) {
                    for (var i = 0; i < asset.ContactIds.length; i++) {
                        var contactId = asset.ContactIds[i];
                        $j('#EditAssetContactIds' + contactId).prop('checked', true);
                    }
                }
                if (asset.FuelEfficiency != '') {
                    // convert from L/100 km to user preference
                    $j('#txtEditAssetFuelEfficiency').val(convertFuelEfficiencyToStandard(asset.FuelEfficiency, tracking.preferences.PREFERENCE_FUEL_UNIT));
                }
                $j('input[name=rbDrawLinesBetweenPositions][value="' + asset.DrawLinesBetweenPositions + '"]:radio', dialog[0]).prop('checked', true);
                $j('input[name=rbSnapLinesToRoads][value="' + asset.SnapLinesToRoads + '"]:radio', dialog[0]).prop('checked', true);
                $j('input[name=rbMicroUseCorrectedCourse][value="' + asset.MicroUseCorrectedCourse + '"]:radio', dialog[0]).prop('checked', true);
                $j('input[name=rbSuppressEmergency][value="' + asset.SuppressEmergency + '"]:radio', dialog[0]).prop('checked', true);
                $j('input[name=rbPrioritizeIncomingMessages][value="' + asset.PrioritizeIncomingMessages + '"]:radio', dialog[0]).prop('checked', true);
                $j('input[name=rbEditAssetIsOutOfService][value="' + asset.IsOutOfService + '"]:radio', dialog[0]).prop('checked', true);
                $j('#chkHideInformationAltitude', dialog[0]).prop('checked', asset.HideAltitude);
                $j('#chkHideInformationAddress', dialog[0]).prop('checked', asset.HideAddress);
                $j('#chkHideInformationCourse', dialog[0]).prop('checked', asset.HideCourse);
                $j('#chkHideInformationFlags', dialog[0]).prop('checked', asset.HideFlags);
                $j('#chkHideInformationSpeed', dialog[0]).prop('checked', asset.HideSpeed);
                $j('#chkHideInformationAccuracy', dialog[0]).prop('checked', asset.HideAccuracy);
                $j('#txtEditAssetDestinationId').val(asset.DestinationId);
                $j('#EditAssetAllowGroupLocationSharing').prop('checked', asset.IsGroupLocationSharingEnabled);
                $j('#txtEditAssetAnalogTolerance').val(asset.AnalogTolerance);
                $j('#txtEditAssetReceiveKey').val(asset.ReceiveEncryptionKey); // either empty string or placeholder (acts like a bit field in the DTO)
                $j('#txtEditAssetTransmitKey').val(asset.TransmitEncryptionKey); // either empty string or placeholder (acts like a bit field in the DTO)
                $j('#txtEditAssetDevicePwrd').val(asset.Password); // either empty string or placeholder (acts like a bit field in the DTO)
                $j('#txtEditAssetMobilePwrd').val(asset.MobilePassword); // either empty string or placeholder (acts like a bit field in the DTO)
                $j('#txtEditAssetMobileUnme').val(asset.MobileUsername);
                if (asset.DisplaySRID != '') {
                    $j('#ddlAssetSRID').val(asset.DisplaySRID);
                }
                if (asset.CannedMessageGroupId != '') {
                    $j('#ddlEditAssetCannedMessageGroup').val(asset.CannedMessageGroupId);
                }
                if (asset.AddressBookGroupId != '') {
                    $j('#ddlEditAssetAddressBookGroup').val(asset.AddressBookGroupId);
                }
                if (asset.GatewayAccountId != '') {
                    $j('#ddlEditAssetGatewayAccount').val(asset.GatewayAccountId);
                }
                if (asset.SecondaryGatewayAccountId != '') {
                    $j('#ddlEditAssetSecondaryGatewayAccount').val(asset.SecondaryGatewayAccountId);
                }
                if (asset.DriverStatusTemplateId != '') {
                    $j('#ddlEditAssetDriverStatusTemplate').val(asset.DriverStatusTemplateId);
                }
                $j('input[name=EditAssetDriverDeviceType]', dialog).prop('checked', false);
                if (asset.DriverDeviceType != null) {
                    if ((asset.DriverDeviceType & 1) == 1) {
                        $j('#EditAssetDriverDeviceTypeGarmin').prop('checked', true);
                    }
                    if ((asset.DriverDeviceType & 2) == 2) {
                        $j('#EditAssetDriverDeviceTypeIButton').prop('checked', true);
                    }
                }
                $j('#ddlEditAssetDriverStatusTemplate').val(asset.DriverStatusTemplateId);
                if (asset.QuickMessageFromMobileTemplateId != '') {
                    $j('#ddlEditAssetQuickMessageFromMobileTemplate').val(asset.QuickMessageFromMobileTemplateId);
                }
                if (asset.QuickMessageToMobileTemplateId != '') {
                    $j('#ddlEditAssetQuickMessageToMobileTemplate').val(asset.QuickMessageToMobileTemplateId);
                }
                $j('#ddlEditAssetConfiguration').val(asset.Configuration);

                // show photo
                if (asset.PhotoType != null && asset.PhotoType != '') {
                    $j('#edit-asset-photo img').attr('src', '/uploads/images/assets/' + asset.Id + '_thumb.' + asset.PhotoType + '?rnd=' + new Date().getTime());
                    $('#edit-asset-photo a').attr('href', '/uploads/images/assets/' + asset.Id + '.' + asset.PhotoType);
                    $j('#edit-asset-photo').show();
                }

                if (asset.InmarsatC != null) {
                    $j('#txtEditAssetInmarsatCDNID').val(asset.InmarsatC.DNID).prop('disabled', true);
                    $j('#txtEditAssetInmarsatCMemberNumber').val(asset.InmarsatC.MemberNumber).prop('disabled', true);
                    $j('#txtEditAssetInmarsatCSubaddress').val(asset.InmarsatC.Subaddress);
                }
            } else {
            	$j('#txtEditAssetInmarsatCDNID,#txtEditAssetInmarsatCMemberNumber').prop('disabled', false);
                $j('#AssetAlerts').DataTable().clear();

                $j('#txtEditAssetUniqueId').val('');
                $j('#txtEditAssetSecondaryUniqueId,#txtEditAssetInmarsatCSerialNumber,#txtEditAssetInmarsatCSubaddress').val('');
                $j('#UpdateAssetStatus').prop('disabled', true);
                $('#RefreshAssetStatus,#ResetAssetStatus').addClass('disabled').prop('disabled', true);

                $j('#EditAssetDevice').hide();
                $j('#ddlEditAssetDevice,#ddlEditAssetDeviceManufacturer').show();

                $j('#txtEditAssetSensitivity').val('50');
                $j('#txtEditAssetAnalogTolerance').val('0' + tracking.options.decimalSeparator + '3');
                $j('input[name=rbDrawLinesBetweenPositions][value="true"]:radio', dialog[0]).prop('checked', true); // default to draw lines
                $j('input[name=rbSnapLinesToRoads][value="false"]:radio', dialog[0]).prop('checked', true); // default to not snap
                $j('input[name=rbMicroUseCorrectedCourse][value="false"]:radio', dialog[0]).prop('checked', true); // default to not use
                $j('input[name=rbSuppressEmergency][value="false"]:radio', dialog[0]).prop('checked', true); // default to not use
                $j('input[name=rbPrioritizeIncomingMessages][value="true"]:radio', dialog[0]).prop('checked', true); // default to prioritize
                $j('input[name=rbEditAssetIsOutOfService][value="false"]:radio', dialog[0]).prop('checked', true); // default to in service
                $j('#EditAssetAllowGroupLocationSharing').prop('checked', true);
                // choose default icon and color
                $j('input[name=rbEditAssetIcon][value="Default_Generic"]:radio', dialog[0]).prop('checked', true);
                $('#edit-asset-icon-tab-system').tab('show');
                $('#EditAssetIconSet').val(tracking.options.defaultIconSet);
                $('input[name=rbEditAssetColor][value="blue"]:radio', dialog[0]).prop('checked', true);
                $('.icon-set', dialog[0]).removeClass('is-visible');
                $('#icon-set-' + tracking.options.defaultIconSet).addClass('is-visible');

                var icons = $('input[name=rbEditAssetIcon]', dialog[0]);
                _.each(icons, function(icon) {
                    var iconImage = $(icon).next().children('img').attr('src', '/markers/' + icon.value + '?color=blue');
                });
            }

            // necessary? non-admins cannot edit the config of an asset anyways
            if (tracking.user.isAdmin) {
                $j('#EditAssetSIMFields').show();
            } else {
                $j('#EditAssetSIMFields').hide();
            }

            $j('input[name=rbEditAssetFuelEfficiency][value="' + tracking.preferences.PREFERENCE_FUEL_UNIT + '"]:radio', dialog[0]).prop('checked', true);

            // deal with groups that have parents
            // create a globalGroupHierarchy that is sorted properly?

            // populate groups, along with selecting groups this asset currently belongs to
            var cont = $j('#edit-asset-groups-container');
            var groupContainer = document.getElementById('edit-asset-groups-container')
            cont.empty();
            var sortedGroups = sortAssetGroups();
            for (var i = 0; i < sortedGroups.length; i++) {
                var group = sortedGroups[i];
                addGroupToGroupListInAssetEditDialog(group, 0, groupContainer, asset);
            }

            var userList = tracking.user.isAdmin ? tracking.data.users : [];

            populateCheckboxList('edit-asset-users-list', userList, 'EditAssetUserIds',
                function (item) {
                    if (asset === null) {
                        return false;
                    }
                    var assetUsers = _.find(tracking.data.assetUsers, { AssetId: asset.Id });
                    if (assetUsers === undefined) {
                        return false;
                    }
                    return _.indexOf(assetUsers.UserIds, item.Id) !== -1;
                }, function (item) {
                    return item.Name;
                }, 'users', function(item) { return item.Username; });


            var garminForms = [];
            $('#garmin-forms option').each(function (index, elem) {
                garminForms.push({
                    Id: parseInt(this.value),
                    Title: this.innerText
                });
            });
            var garminFormsList = tracking.user.isAdmin ? garminForms : [];
            populateCheckboxList('edit-asset-garmin-list', garminFormsList, 'EditAssetGarminIds',
                function (item) {
                    if (asset === null) {
                        return false;
                    }
                    var assetForms = _.find(tracking.data.assetForms, { AssetId: asset.Id });
                    if (assetForms === undefined) {
                        return false;
                    }
                    return _.indexOf(assetForms.FormIds, item.Id) !== -1;
                }, function (item) {
                    return item.Title;
                }, 'forms');

            // populate checkboxes and update the list
            // only text-messagable assets

            var assetAssets = _.filter(tracking.data.assets, function (item) { return asset !== null && isMessagingEnabledForAsset(item) && asset.Id !== item.Id; });
            populateCheckboxList('edit-asset-assets-list', assetAssets, 'EditAssetAssetIds',
                function (item) {
                    if (asset === null) {
                        return false;
                    }
                    return _.indexOf(asset.AssetIds, item.Id) !== -1;
                }, function (item) {
                    return item.Name;
                }, 'assets', function(item) { return item.UniqueId; });

            // driver groups
            var cont = $j('#edit-asset-driver-groups-container');
            var groupContainer = document.getElementById('edit-asset-driver-groups-container')
            cont.empty();
            var sortedGroups = sortItemGroups(tracking.data.driverGroups);

            for (var i = 0; i < sortedGroups.length; i++) {
                var group = sortedGroups[i];
                var assetDriverGroups = [];
                if (asset !== null && asset !== undefined) {
                    var assignedGroups = _.find(tracking.data.assetDriverGroups, { AssetId: asset.Id });
                    if (assignedGroups !== undefined) {
                        assetDriverGroups = assignedGroups.DriverGroupIds;
                    }
                }
                addItemGroupToItemGroupListInAssetEditDialog(group, 0, groupContainer, assetDriverGroups);
            }

            populateCheckboxList('edit-asset-drivers-list', tracking.data.drivers, 'EditAssetDriverIds',
                function (item) {
                    if (asset === null) {
                        return false;
                    }
                    var assetDrivers = _.find(tracking.data.assetDrivers, { AssetId: asset.Id });
                    if (assetDrivers === undefined) {
                        return false;
                    }
                    return _.indexOf(assetDrivers.DriverIds, item.Id) !== -1;
                }, function (item) {
                    return item.DriverId;
            }, 'drivers', getDescriptionForDriver);

            // select main tab
            $('#edit-asset-tab-main').tab('show');

            // clear file selection - todo: technically should be hidden unless it exists (otherwise triggers a 404)
            $j('#fileEditAssetPhoto').val('').next('.custom-file-label').text(tracking.strings.NO_FILE_SELECTED);

            deviceDialogChange($j('#ddlEditAssetDevice'), asset);
            if (asset != null) {
                // iridium extreme
            	if (asset.DeviceId == tracking.devices.IRIDIUM_EXTREME) {
                    $j('#txtEditAssetIMEIVerificationCode').val(hash1(asset.UniqueId));
                    $j('#txtEditAssetPortalRegistrationCode').val(hash2(asset.UniqueId));
                }
            }
            if (asset != null) {
                // why did i move this to after the dialog change?... probably because of the input mask issue
            	$j('#txtEditAssetUniqueId').val(asset.UniqueId);
            	$j('#txtEditAssetSecondaryUniqueId').val(asset.SecondaryUniqueId);
            }
            $j('#txtEditAssetNotes').maxChar('init', 250, { 'indicator': 'txtEditAssetNotesIndicator' });
        } else {
            if (asset != null) {
                id = asset.Id;
                $j('#hfEditAssetId').val(id);
                // populate existing values
                $j('#txtEditAssetName').val(asset.Name);
            }
        }

        //openDialogPanel()
        openDialogPanel(tracking.data.domNodes.dialogs.editAsset, dialogTitle, asset, true, null, 'asset', (asset !== null ? 'edit-asset' : null), (asset !== null ? openEditAssetDialog : null));
        //setDialogTitle(dialog, dialogTitle);
        //$j(dialog).dialog('open');
        document.getElementById('txtEditAssetName').focus();


        var selector = dialog.parent().find('div.asset-selector'); // unused?
        if (asset != null) {
            // todo: only for idp assets
            if (!tracking.user.limitAssetEditing) {
                loadIDPAssetIOConfiguration(asset);
                loadIDPAVLAssetIOConfiguration(asset);
                loadIDPARCAssetIOConfiguration(asset);
            }
            dialog.data('assetId', asset.Id);
            selector.show();
        } else {
            selector.hide();
        }        
    }

    function populateCheckboxList(containerId, items, name, isCheckedFunction, getNameFunction, type, getDescriptionFunction) {
        var container = document.getElementById(containerId);
        cont = container.querySelector('.list');
        var hiddenCont = container.querySelector('.list-hidden');
        cont.innerHTML = '';
        hiddenCont.innerHTML = '';

        var assetAssetsContainer = document.createDocumentFragment();
        var assetHiddenContainer = document.createDocumentFragment();
        for (var k = 0; k < items.length; k++) {
            var item = items[k];
            var isChecked = isCheckedFunction(item);
            var formCheck = document.createElement('div');
            formCheck.className = 'custom-control custom-checkbox';
            var input = document.createElement('input');
            input.setAttribute('type', 'checkbox');
            input.setAttribute('id', name + k);
            input.setAttribute('name', name + '-name');
            input.className = 'custom-control-input';
            input.setAttribute('value', item.Id);
            if (isChecked) {
                input.setAttribute('checked', 'checked');
            }
            formCheck.appendChild(input);

            var label = document.createElement('label');
            label.setAttribute('for', name + k);
            label.className = 'custom-control-label';

            if (type !== undefined && type !== null) {
                var icon = getGenericIconUrlForItemType(type, item);
                var svgIcon = getSvgIconForItemType(type, item);
                if (svgIcon !== null) {
                    var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
                    var iconType = document.createElementNS('http://www.w3.org/2000/svg', 'use');
                    iconType.setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgIcon);
                    svg.appendChild(iconType);
                    if (item.Color !== undefined && item.Color !== null) {
                        svg.setAttribute('style', 'color: ' + item.Color + '; --color-primary: ' + item.Color + '; --color-secondary: ' + item.Color);
                    }
                    label.appendChild(svg);
                    label.classList.add('has-svg-icon');
                } else if (icon !== null) {
                    label.classList.add('has-icon');
                    label.style.backgroundImage = icon;
                }
            }

            var span = document.createElement('span');
            var nameText = document.createTextNode(getNameFunction(item));
            span.appendChild(nameText);

            if (getDescriptionFunction !== undefined && getDescriptionFunction !== null) {
                var description = getDescriptionFunction(item);
                if (description !== null) {
                    var desc = document.createElement('div');
                    desc.textContent = description;
                    desc.classList.add('meta');
                    span.appendChild(desc);
                }
            }
            

            label.appendChild(span);

            formCheck.appendChild(label);
            assetAssetsContainer.appendChild(formCheck);

            var hidden = input.cloneNode();
            hidden.setAttribute('id', hidden.id + '-hidden');
            hidden.setAttribute('name', name);
            assetHiddenContainer.appendChild(hidden);
        }
        cont.appendChild(assetAssetsContainer);
        hiddenCont.appendChild(assetHiddenContainer);

        // shouldn't we check if it exists first?
        var list = utility.createCheckboxFilterList(containerId);
        return list;
    }

    function disableFenceInteraction() {
        for (var i = 0; i < tracking.data.fenceMarkers.length; i++) {
            var poly = tracking.data.fenceMarkers[i];
            poly.setOptions({ clickable: false });
        }
    }

    function enableFenceInteraction() {
        if (fenceOverrides.length > 0)
            return;

        for (var i = 0; i < tracking.data.fenceMarkers.length; i++) {
            var poly = tracking.data.fenceMarkers[i];
            poly.setOptions({ clickable: true });
        }
    }

    function stopLiveFollowing() {
        tracking.data.domNodes.followLiveStatus.classList.remove('is-visible');
        $j('#live-follow-updated-time').data('time', null);
        tracking.state.liveFollow.isActive = false;
        tracking.state.liveFollow.asset = null;
        tracking.state.liveFollow.assets = [];
        tracking.state.liveFollow.groups = [];
    }

    function isFollowingGroup(group) {
        var groupAssets = [];
        for (var i = 0; i < tracking.data.assets.length; i++) {
            var asset = tracking.data.assets[i];
            if ($j.inArray(group.Id, asset.GroupIds) != -1) {
                groupAssets.push(asset);
            }
        }
        if (groupAssets.length == 0) {
            return false;
        }
        for (var i = 0; i < groupAssets.length; i++) {
            var asset = groupAssets[i];
            if ($j.inArray(asset, tracking.state.liveFollow.assets) == -1) {
                return false;
            }
        }
        return true;
    }

    function toggleFollowGroup(group) {
        var groupAssets = [];
        // should this get all assets under the group and its subgroup or just the immediately assigned assets?
        if (group.Id === 'all-assets') {
            groupAssets = tracking.data.assets;
        }
        for (var i = 0; i < tracking.data.assets.length; i++) {
            var asset = tracking.data.assets[i];
            if ($.inArray(group.Id, asset.GroupIds) != -1) {
                groupAssets.push(asset);
            }
        }

        if (isFollowingGroup(group)) {
            // all assets being followed, so un follow them
            for (var i = 0; i < groupAssets.length; i++) {
                liveUnfollowAsset(groupAssets[i]);
            }
        } else {
            // not all assets being followed, follow ones that are not
            for (var i = 0; i < groupAssets.length; i++) {
                liveFollowAsset(groupAssets[i]);
            }
        }

        updateLiveFollowStatus();
    }

    function liveFollowAsset(asset) {
        tracking.state.liveFollow.isActive = true;
        tracking.state.liveFollow.asset = asset;
        var index = $.inArray(asset, tracking.state.liveFollow.assets);
        if (index == -1) {
            tracking.state.liveFollow.assets.push(asset);
        }
    }

    function liveUnfollowAsset(asset) {
        var index = $.inArray(asset, tracking.state.liveFollow.assets);
        if (index !== -1) {
            tracking.state.liveFollow.assets.splice(index, 1);
        }
        if (tracking.state.liveFollow.assets.length === 0) {
            stopLiveFollowing();
        }
    }

    function openMostRecentLiveFollowPosition() {
        if (!tracking.state.liveFollow.isActive) {
            return;
        }

        // todo: pan/zoom to include all followed assets instead?
        var latest = null;
        _.each(tracking.state.liveFollow.assets, function (asset) {
            var assetLatest = tracking.data.live.latestPositionsByAssetId[asset.Id];
            if (assetLatest === undefined) {
                return;
            }
            if (latest === null || (latest.Position.Epoch < assetLatest.Position.Epoch)) {
                latest = assetLatest;
            }
        });

        if (latest !== null) {
            openAssetLatestPosition(findAssetById(latest.AssetId));

            var loc = latest.Position;
            var locationTime = moment(loc.Time, tracking.user.dateFormat);
            var priorTime = $('#live-follow-updated-time').data('time');
            if (priorTime == null) {
                $('#live-follow-updated-time').text(loc.Time);
                $('#live-follow-updated-time').data('time', locationTime);
            } else {
                if (locationTime.isAfter(priorTime)) {
                    $('#live-follow-updated-time').data('time', locationTime);
                    $('#live-follow-updated-time').text(loc.Time);
                }
            }
        } else {
            $('#live-follow-updated-time').text(tracking.strings.NEVER);
        }
    }

    function updateLiveFollowStatus() {
        var assetsFollowed = tracking.state.liveFollow.assets.length;
        if (assetsFollowed === 0) {
            tracking.data.domNodes.followLiveStatus.classList.remove('is-visible');
            return;
        }

        if (assetsFollowed === 1) {
            $('#live-follow-assets').text(tracking.state.liveFollow.assets[0].Name).prop('title', tracking.state.liveFollow.assets[0].Name);
        } else {
            var title = '';
            for (var i = 0; i < assetsFollowed; i++) {
                if (i > 0) {
                    title += ', ';
                }
                title += tracking.state.liveFollow.assets[i].Name;
            }
            $('#live-follow-assets').text(tracking.strings.MULTIPLE_ASSETS.replace('{0}', tracking.state.liveFollow.assets.length))
                .prop('title', title);
        }
        tracking.data.domNodes.followLiveStatus.classList.add('is-visible');

        if (tracking.state.activeMapMode !== tracking.mapModes.LIVE) {
            // can only follow in live mode
            switchMapMode(true, null, false);
        } else {
            openMostRecentLiveFollowPosition();
        }
    }

    function toggleFollowAsset(asset) {
        var index = $.inArray(asset, tracking.state.liveFollow.assets);
        if (index == -1) {
            liveFollowAsset(asset);
        } else {
            liveUnfollowAsset(asset);
        }

        updateLiveFollowStatus();
    }

    function setDialogTitle(dialog, title) {
        $j(dialog).dialog('option', 'title', title);
    }

    function padLeft(input, length) {
        if (input == null)
            return input;
        if (input == '')
            return input;
        if (input.length >= length)
            return input;
        var pad = length - input.length;
        return pad > 0 ? new Array(pad).join(' ') + input : input;
    }

    function padRight(input, length) {
        if (input == null)
            return input;
        if (input == '')
            return input;
        if (input.length >= length)
            return input;
        var pad = length - input.length;
        return pad > 0 ? input + new Array(pad).join(' ') : input;
    }

    function createOBDOutput(item) {
        var info = [];
        if (item.PowerVoltage != null) {
            info = addToOBDOutput('Power Voltage', item.PowerVoltage, info, 'V');
        }
        if (item.VehicleSpeed != null) {
            info = addToOBDOutput('Vehicle Speed', item.VehicleSpeed, info, 'mps');
        }
        if (item.ThrottlePosition != null) {
            info = addToOBDOutput('Throttle Position', item.ThrottlePosition, info, '%');
        }
        if (item.EngineRPM != null) {
            info = addToOBDOutput('Engine RPM', item.EngineRPM, info);
        }
        if (item.EngineLoad != null) {
            info = addToOBDOutput('Engine Load', item.EngineLoad, info, '%');
        }
        if (item.EngineCoolantTemperature != null) {
            info = addToOBDOutput('Engine Coolant Temp.', item.EngineCoolantTemperature, info, '°C');
        }
        if (item.FuelLevelPercentage != null) {
            info = addToOBDOutput('Fuel Level', item.FuelLevelPercentage, info, '%');
        }
        if (item.FuelLevelRemaining != null) {
            info = addToOBDOutput('Fuel Level Remaining', item.FuelLevelRemaining, info);
        }
        if (item.FuelConsumption != null) {
            info = addToOBDOutput('Fuel Consumption', item.FuelConsumption, info, 'L/100km');
        }
        if (item.FuelRate != null) {
            info = addToOBDOutput('Fuel Rate', item.FuelRate, info);
        }
        if (item.IsMILActive != null) {
            info = addToOBDOutput('MIL Active', item.IsMILActive, info);
        }
        if (item.DTCCount != null) {
            info = addToOBDOutput('DTC Count', item.DTCCount, info);
        }
        if (item.DTCs != null) {
            info = addToOBDOutput('DTCs', item.DTCs.split(',').join(', '), info);
        }
        if (item.DTCsClearedDistance != null) {
            info = addToOBDOutput('DTCs Cleared Distance', item.DTCsClearedDistance, info, 'km');
        }
        if (item.MILActivatedDistance != null) {
            info = addToOBDOutput('MIL Activated Distance', item.MILActivatedDistance, info, 'km');
        }
        if (item.Odometer != null) {
            info = addToOBDOutput('Odometer', item.Odometer, info, 'km');
        }
        if (item.MaintenanceRequired != null) {
            info = addToOBDOutput('Maintenance Required', item.MaintenanceRequired, info);
        }
        return info;
    }

    function addToOBDOutput(label, value, output, unit) {
        if (unit == null) {
            unit = '';
        }
        return includeRowIfNotNull(label, value + unit);
    }

    function createIOOutput(asset, item, isTable) {
        var settings = [];
        settings = addToIOOutput(asset, item.AnalogPin1, 'a1', tracking.strings.ANALOG_PIN + ' 1', settings, isTable);
        settings = addToIOOutput(asset, item.AnalogPin2, "a2", tracking.strings.ANALOG_PIN + " 2", settings, isTable);
        settings = addToIOOutput(asset, item.AnalogPin3, "a3", tracking.strings.ANALOG_PIN + " 3", settings, isTable);
        settings = addToIOOutput(asset, item.AnalogPin4, "a4", tracking.strings.ANALOG_PIN + " 4", settings, isTable);
        settings = addToIOOutput(asset, item.AnalogPin5, "a5", tracking.strings.ANALOG_PIN + " 5", settings, isTable);
        settings = addToIOOutput(asset, item.AnalogPin6, "a6", tracking.strings.ANALOG_PIN + " 6", settings, isTable);
        settings = addToIOOutput(asset, item.AnalogPin7, "a7", tracking.strings.ANALOG_PIN + " 7", settings, isTable);
        settings = addToIOOutput(asset, item.AnalogPin8, "a8", tracking.strings.ANALOG_PIN + " 8", settings, isTable);
        settings = addToIOOutput(asset, item.AnalogPin9, "a9", tracking.strings.ANALOG_PIN + " 9", settings, isTable);
        settings = addToIOOutput(asset, item.AnalogPin10, "a10", tracking.strings.ANALOG_PIN + " 10", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalPin1, "d1", tracking.strings.DIGITAL_PIN + " 1", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalPin2, "d2", tracking.strings.DIGITAL_PIN + " 2", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalPin3, "d3", tracking.strings.DIGITAL_PIN + " 3", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalPin4, "d4", tracking.strings.DIGITAL_PIN + " 4", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalPin5, "d5", tracking.strings.DIGITAL_PIN + " 5", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalPin6, "d6", tracking.strings.DIGITAL_PIN + " 6", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalPin7, "d7", tracking.strings.DIGITAL_PIN + " 7", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalPin8, "d8", tracking.strings.DIGITAL_PIN + " 8", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalPin9, "d9", tracking.strings.DIGITAL_PIN + " 9", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalPin10, "d10", tracking.strings.DIGITAL_PIN + " 10", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalPin11, "d11", tracking.strings.DIGITAL_PIN + " 11", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalPin12, "d12", tracking.strings.DIGITAL_PIN + " 12", settings, isTable);
        settings = addToIOOutput(asset, item.DigitalIngition, "d98", tracking.strings.DIGITAL_IGNITION, settings, isTable);
        settings = addToIOOutput(asset, item.DigitalSOS, "d99", tracking.strings.DIGITAL_SOS, settings, isTable);
        if (!isTable) {
            return settings.join('');
        }
        return settings;
    }

	function addToIOOutput(asset, pin, id, defaultLabel, output, isTable) {
        if (pin == null) {
            return output;
        }
	    var label = getInputLabelById(asset, id);
	    if ((label != null) && (label != '')) {
	        if (label.IsNormallyOpen) {
	            pin = !pin;
	        }
	        if (label.InputPin.indexOf('d') != -1) {
	            // we may have a label override
	            var onLabel = tracking.strings.ON;
	            if (!label.OnIsDisabled && (label.OnLabel != null) && (label.OnLabel != '')) {
	                onLabel = label.OnLabel;
	            }
	            var offLabel = tracking.strings.OFF;
	            if (!label.OffIsDisabled && (label.OffLabel != null) && (label.OffLabel != '')) {
	                offLabel = label.OffLabel;
	            }
	            pin = pin ? onLabel : offLabel;
	        } else {
	            // analog, we may need to factor pin
	            if((label.AnalogFactor != null) && (label.AnalogFactor != '')) {
	                pin = pin / $j.parseFloat(label.AnalogFactor);
	            }
	            if (label.AnalogBehavior == 1) { //IsFuel
	                var level = (pin / $j.parseFloat(label.MaxVoltage));
	                pin = (level * 100).toFixed(2) + '% (' + (level * $j.parseFloat(label.FuelCapacity)).toFixed(2) + ' ' + fuelText() + ')';
	            } else if (label.AnalogUnit != null) {
	                pin += ' ' + label.AnalogUnit;
	            }
	        }
	        var pinLabel = label.Label;
	        if ((pinLabel == null) || (pinLabel == '')) {
	            pinLabel = defaultLabel;
	        }
	        if (isTable) {
	            output.push(includeRowIfNotNull(pinLabel, pin));
	        } else {
	            output.push(padRight(pinLabel, 15) + ': ' + pin + "\n");
	        }
	    } else {
	        if (isTable) {
                output.push(includeRowIfNotNull(defaultLabel, pin));
	        } else {
                output.push(padRight(defaultLabel, 15) + ': ' + pin + "\n");
	        }
	    }
	    return output;
	}

    function updateAssetDriverStatus(asset, driverId, statusId, btn, notes) {
        var status = document.getElementById('current-drivers-status');
        var data = { assetId: asset.Id, driverId: driverId, statusId: statusId, notes: notes };
        handleAjaxFormSubmission('AssetDriverUpdateStatus', data, btn, status, tracking.strings.MSG_UPDATE_DRIVER_SUCCESS, tracking.strings.MSG_UPDATE_DRIVER_ERROR, function () {
            loadAssetDrivers(asset);
        });

     //   if(btn != null)
	    //    btn.prop('disabled', true);
	    //toggleLoadingMessage(true, 'asset-driver-update');
	    //$j.ajax({
	    //    type: 'POST',
	    //    url: wrapUrl('/services/GPSService.asmx/AssetDriverUpdateStatus'),
	    //    data: JSON.stringify(data),
	    //    contentType: 'application/json; charset=utf-8',
	    //    dataType: 'json',
	    //    success: function (msg) {
	    //        if (btn != null)
	    //            btn.prop('disabled', false);
	    //        if (msg.d) {
	    //            var result = msg.d;
	    //            if (result.Success == true) {
	    //                loadAssetDrivers(asset);
     //                   tracking.data.validation.currentDriver.resetForm();
     //                   tracking.data.validation.currentDriver.currentForm.reset();
	    //            } else {
	    //                utility.handleWebServiceError(tracking.strings.MSG_UPDATE_DRIVER_ERROR);
	    //            }
	    //        }
	    //        toggleLoadingMessage(false, 'asset-driver-update');
	    //    },
	    //    error: function (xhr, status, error) {
	    //        utility.handleWebServiceError(tracking.strings.MSG_UPDATE_DRIVER_ERROR);
	    //        toggleLoadingMessage(false, 'asset-driver-update');
	    //        if (btn != null)
	    //            btn.prop('disabled', false);
	    //    }
	    //});
	}

    function loginAssetDriver(asset, driverId, notes, btn) {
        var status = document.getElementById('current-drivers-status');
        var data = { assetId: asset.Id, driverId: driverId, notes: notes };
        handleAjaxFormSubmission('AssetDriverLogin', data, btn, status, tracking.strings.MSG_LOGIN_DRIVER_SUCCESS, tracking.strings.MSG_LOGIN_DRIVER_ERROR, function () {
            tracking.data.validation.currentDriver.resetForm();
            tracking.data.validation.currentDriver.currentForm.reset();

            loadAssetDrivers(asset);
        });


	    //var btn = $j('#LoginDriver')[0];

     //   btn.disabled = true;
	    //toggleLoadingMessage(true, 'asset-driver-login');
	    //$j.ajax({
	    //    type: 'POST',
	    //    url: wrapUrl('/services/GPSService.asmx/AssetDriverLogin'),
	    //    data: JSON.stringify(data),
	    //    contentType: 'application/json; charset=utf-8',
	    //    dataType: 'json',
	    //    success: function (msg) {
     //           btn.disabled = false;
	    //        if (msg.d) {
	    //            var result = msg.d;
	    //            if (result.Success == true) {
	    //                loadAssetDrivers(asset);
	    //            } else {
	    //                utility.handleWebServiceError(tracking.strings.MSG_LOGIN_DRIVER_ERROR);
	    //            }
	    //        }
	    //        toggleLoadingMessage(false, 'asset-driver-login');
	    //    },
	    //    error: function (xhr, status, error) {
	    //        utility.handleWebServiceError(tracking.strings.MSG_LOGIN_DRIVER_ERROR);
	    //        toggleLoadingMessage(false, 'asset-driver-login');
     //           btn.disabled = false;
	    //    }
	    //});
	}

	function renderQuickMessageTemplate(templateId) {
	    var messages = tracking.data.quickMessages[templateId];
        if(messages == null)
            return;
	    var cont = $j('#quick-messages');
	    for (var i = 0; i < messages.length; i++) {
	        var message = messages[i];
	        cont.append($j('<option>').val(message.Id).text(message.Text));
	    }
	}

	function loadQuickMessageTemplate(templateId) {
	    if (tracking.data.quickMessages[templateId] != null) {
	        renderQuickMessageTemplate(templateId);
	    } else {
	        toggleLoadingMessage(true, 'quick-messages');
	        var data = { id: templateId };
            return $j.ajax({
	            type: 'POST',
	            url: wrapUrl('/services/GPSService.asmx/GetQuickMessageToMobileTemplate'),
	            data: JSON.stringify(data),
	            contentType: 'application/json; charset=utf-8',
	            dataType: 'json',
	            success: function (msg) {
	                if (msg.d) {
	                    var result = msg.d;
	                    if (result.Success == true) {
	                        tracking.data.quickMessages[templateId] = result.Messages;
	                        renderQuickMessageTemplate(templateId);
	                    } else {
	                        utility.handleWebServiceError('');
	                    }
	                }
	                toggleLoadingMessage(false, 'quick-messages');
	            },
	            error: function (xhr, status, error) {
	                utility.handleWebServiceError('');
	                toggleLoadingMessage(false, 'quick-messages');
	                btn.prop('disabled', false);
	            }
	        });
	    }
	}

    function loadAssetDrivers(asset) {
        var btn = $j('#RefreshAssetDrivers');
        var data = { assetId: asset.Id };
        btn.prop('disabled', true);
        toggleLoadingMessage(true, 'asset-drivers');
        $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetAssetDriverInformation'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                btn.prop('disabled', false);
                if (msg.d) {
                    var result = msg.d;
                    if (result.Success == true) {
                        var statuses = $j('#AssetStatuses').empty();
                        statuses.append($j('<option>'));
                        var logoffStatus = $j('<option>').val(-1).text(tracking.strings.LOGOUT);
                        var loginStatus = $j('<option>').val(0).text(tracking.strings.LOGIN);

                        if (result.Statuses != null) {
                            // statuses defined for this asset
                            for (var i = 0; i < result.Statuses.length; i++) {
                                var status = result.Statuses[i];
                                if (status.IsLogOff)
                                    logoffStatus = $j('<option>').val(-1).text(status.Status);
                                else
                                    statuses.append($j('<option>').val(status.Id).text(status.Status));
                            }
                        }
                        statuses.append(logoffStatus);

                        // clear current drivers listing
                        var drivers = $j('#AssetDrivers tbody').empty();
                        var driverIds = [];
                        if (result.Drivers != null) {
                            for (var i = 0; i < result.Drivers.length; i++) {
                                var driver = result.Drivers[i];
                                var status = driver.Status;
                                if (status == null) {
                                    statusId = '';
                                } else {
                                    statusId = status.Id;
                                }
                                var statusOptions = statuses.clone(false).removeAttr('id').val(statusId).data('driverId', driver.Driver.Id).show();
                                var driverDescription = getDescriptionForDriver(driver.Driver);
                                if (driverDescription !== '') {
                                    driverDescription = ' (' + driverDescription + ')';
                                }
                                drivers.append(
                                    $j('<tr>')
                                        .append($j('<td>').text(driver.Driver.DriverId + driverDescription))
                                        .append($j('<td>')
                                            .append(statusOptions))
                                        .append($j('<td>').append($j('<button>').addClass('update-driver btn btn-primary').text(tracking.strings.UPDATE_STATUS)))
                                );
                                driverIds.push(driver.Driver.Id);
                            }
                        }

                        var login = $j('#LoginDrivers').empty();
                        var assetDrivers = findAssetDriverIdsByAssetIdSorted(asset.Id);

                        if (assetDrivers != null) {
                            for (var i = 0; i < assetDrivers.length; i++) {
                                var driverId = assetDrivers[i];
                                if ($j.inArray(driverId, driverIds) == -1) {
                                    var driver = findDriverById(driverId);
                                    if (driver == null)
                                        continue;
                                    var driverDescription = getDescriptionForDriver(driver);
                                    if (driverDescription !== '') {
                                        driverDescription = ' (' + driverDescription + ')';
                                    }
                                    login.append($j('<option>').val(driverId).text(driver.DriverId + driverDescription));
                                }
                            }
                        }

                        var stats = $j('#LoginDriversStatus').empty();
                        if ((result.Statuses == null) || (result.Statuses.length == 0)) {
                            stats.append(loginStatus);
                        } else {
                            // statuses defined for this asset
                            for (var i = 0; i < result.Statuses.length; i++) {
                                var status = result.Statuses[i];
                                if (!status.IsLogOff)
                                    stats.append($j('<option>').val(status.Id).text(status.Status));
                            }
                        }
                    } else {
                        utility.handleWebServiceError(tracking.strings.MSG_GET_DRIVERS_ERROR);
                    }
                }
                toggleLoadingMessage(false, 'asset-drivers');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_GET_DRIVERS_ERROR);
                toggleLoadingMessage(false, 'asset-drivers');
                btn.prop('disabled', false);
            }
        });
    }

	function loadAssetAdditional(asset) {
		if(asset == null)
            return;
        var device = findDeviceById(asset.DeviceId);
		if(!device.RequiresPollingInterval)
			return;
		toggleLoadingMessage(true, 'asset-additional');
		var data = {
			assetId: asset.Id
		};
		return $j.ajax({
			type: 'POST',
			url: wrapUrl('/services/GPSService.asmx/GetAssetAdditional'),
			data: JSON.stringify(data),
			contentType: 'application/json; charset=utf-8',
			dataType: 'json',
			success: function (msg) {
				if (msg.d) {
					var result = msg.d;
					if (result.Success == true) {
						if (result.PollingInterval != '') {
							var days = (result.PollingInterval / 86400);
							var hours = (result.PollingInterval / 3600);
							var minutes = (result.PollingInterval / 60);
							if ((days >= 1) && ((result.PollingInterval % 86400) == 0)) {
								$j('#txtEditAssetPollingInterval').val(days);
								$j('#ddlEditAssetPollingInterval').val('d');
							} else if ((hours >= 1) && ((result.PollingInterval % 3600) == 0)) {
								$j('#txtEditAssetPollingInterval').val(hours);
								$j('#ddlEditAssetPollingInterval').val('h');
							} else if ((minutes >= 1) && ((result.PollingInterval % 60) == 0)) {
								$j('#txtEditAssetPollingInterval').val(minutes);
								$j('#ddlEditAssetPollingInterval').val('m');
							} else {
								$j('#txtEditAssetPollingInterval').val(result.PollingInterval);
								$j('#ddlEditAssetPollingInterval').val('s');
							}
						}
					}
				}
				toggleLoadingMessage(false, 'asset-additional');
			},
			error: function (xhr, status, error) {
				utility.handleWebServiceError(tracking.strings.MSG_GET_STATUS_ERROR);
				toggleLoadingMessage(false, 'asset-additional');
				btn.prop('disabled', false);
			}
		});
	}

    function loadAssetAlerts(asset) {
        // clear existing
        var table = $j('#AssetAlerts').DataTable();
        table.clear().draw();

        var btn = $j('#RefreshAssetAlerts');
        var data = { assetId: asset.Id };
        btn.addClass('disabled');
        toggleLoadingMessage(true, 'asset-alerts');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetAssetAlerts'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                btn.removeClass('disabled');
                if (msg.d) {
                    var result = msg.d;
                    if (result.Success == true) {
                        // populate alerts table
                        var assetAlertsData = [];
                        var alerts = result.Alerts;
                        for (var i = 0; i < alerts.length; i++) {
                            var alert = alerts[i];
                            var filterItems = [];
                            for (var j = 0; j < alert.Filters.length; j++) {
                                filterItems.push(el('li', alert.Filters[j]));
                            }
                            var filters = el('ul', filterItems);
                            assetAlertsData.push([
                                alert.Id,
                                el('form', { method: 'get', action: 'Alerts/EditAlert/' + alert.Id }, el('button.alert-edit.btn.btn-secondary', { title: tracking.strings.EDIT, dataset: { alertId: alert.Id } }, svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#edit' } })))),
                                alert.Name,
                                alert.Description,
                                alert.Type,
                                filters,
                                el('button.alert-remove.btn.btn-secondary', { title: tracking.strings.REMOVE_FROM_ALERT, disabled: alert.IsLinkedByGroup, dataset: { alertId: alert.Id, alertType: alert.Type, linkedByGroup: alert.IsLinkedByGroup } }, svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#minus' } })))
                            ]);
                        }

                        table.rows.add(assetAlertsData).draw();
                    } else {
                        utility.handleWebServiceError(tracking.strings.MSG_GET_ALERTS_ERROR);
                    }
                }
                toggleLoadingMessage(false, 'asset-alerts');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_GET_ALERTS_ERROR);
                toggleLoadingMessage(false, 'asset-alerts');
                btn.removeClass('disabled');
            }
        });
    }

    function loadIDPAVLAssetIOConfiguration(asset) {
        if (asset == null)
            return;

        if ($j.inArray(asset.Configuration, tracking.data.avlConfigurations) == -1)
            return;

	    if (($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) == -1) && ($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) == -1))
	        return;

        var data = { assetId: asset.Id };
        toggleLoadingMessage(true, 'asset-io-avl-configuration');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/IDPAVLGetLatestProperties'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                toggleLoadingMessage(false, 'asset-io-avl-configuration');
                if (msg.d) {
                    var result = msg.d;
                    if (result.Success == true) {
                        if (result.ServiceProperties == null)
                            return;
                        var digitalInputs = $j('#InputLabelsDigital tbody tr');
                        for (var i = 0; i < digitalInputs.length; i++) {
                            var elem = digitalInputs.eq(i).find('select.avl');
                            switch (i) {
                                case 0:
                                    elem.val(result.ServiceProperties.FuncDigInp1);
                                    break;
                                case 1:
                                    elem.val(result.ServiceProperties.FuncDigInp2);
                                    break;
                                case 2:
                                    elem.val(result.ServiceProperties.FuncDigInp3);
                                    break;
                                case 3:
                                    elem.val(result.ServiceProperties.FuncDigInp4);
                                    break;
                                case 4:
                                    elem.val(result.ServiceProperties.FuncDigInp5);
                                    break;
                                case 5:
                                    elem.val(result.ServiceProperties.FuncDigInp6);
                                    break;
                                case 6:
                                    elem.val(result.ServiceProperties.FuncDigInp7);
                                    break;
                                case 7:
                                    elem.val(result.ServiceProperties.FuncDigInp8);
                                    break;
                                case 8:
                                    elem.val(result.ServiceProperties.FuncDigInp9);
                                    break;
                                case 9:
                                    elem.val(result.ServiceProperties.FuncDigInp10);
                                    break;
                                case 10:
                                    elem.val(result.ServiceProperties.FuncDigInp11);
                                    break;
                                case 11:
                                    elem.val(result.ServiceProperties.FuncDigInp12);
                                    break;
                            }
                        }
                        var analogInputs = $j('#InputLabelsAnalog tbody tr');
                        for (var i = 0; i < analogInputs.length; i++) {
                            var elem = analogInputs.eq(i).find('span.sensor');
                            elem.text('');
                            switch (i) {
                                case 0:
                                    if (result.ServiceProperties.Sensor1Properties != null) {
                                        elem.text(result.ServiceProperties.Sensor1Properties.SourceSIN + ':' + result.ServiceProperties.Sensor1Properties.SourcePIN);
                                    }
                                    break;
                                case 1:
                                    if (result.ServiceProperties.Sensor2Properties != null) {
                                        elem.text(result.ServiceProperties.Sensor2Properties.SourceSIN + ':' + result.ServiceProperties.Sensor2Properties.SourcePIN);
                                    }
                                    break;
                                case 2:
                                    if (result.ServiceProperties.Sensor3Properties != null) {
                                        elem.text(result.ServiceProperties.Sensor3Properties.SourceSIN + ':' + result.ServiceProperties.Sensor3Properties.SourcePIN);
                                    }
                                    break;
                                case 3:
                                    if (result.ServiceProperties.Sensor4Properties != null) {
                                        elem.text(result.ServiceProperties.Sensor4Properties.SourceSIN + ':' + result.ServiceProperties.Sensor4Properties.SourcePIN);
                                    }
                                    break;
                            }
                        }
                    } else {
                        utility.handleWebServiceError(null);
                    }
                }
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(null);
                toggleLoadingMessage(false, 'asset-io-avl-configuration');
            }
        });
    }

    function selectARCConfiguration(value, num, analogInputs, digitalInputs, outputs) {
        switch (value) {
            case 1:
            case 2:
            case 3:
            case 9:
                // digital
                digitalInputs.eq(num).find('select.arc').val(value);
                break;
            case 4:
            case 6:
            case 7:
            case 8:
            case 10: // analog
            case 11:
                analogInputs.eq(num).find('select.arc').val(value);
                break;
            case 5:
            case 12:
                outputs.eq(num).find('select.arc').val(value);
                break;
        }
    }

    function loadIDPARCAssetIOConfiguration(asset) {
        if (asset == null)
            return;

        if ($j.inArray(asset.Configuration, tracking.data.arcConfigurations) == -1)
            return;

	    if (($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) == -1) && ($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) == -1))
	        return;

        var data = { assetId: asset.Id };
        toggleLoadingMessage(true, 'asset-io-arc-configuration');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/IDPARCGetLatestProperties'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                toggleLoadingMessage(false, 'asset-io-arc-configuration');
                if (msg.d) {
                    var result = msg.d;
                    if (result.Success == true) {
                        if (result.ARCTwoProperties == null)
                            return;
                        var digitalInputs = $j('#InputLabelsDigital tbody tr');
                        var analogInputs = $j('#InputLabelsAnalog tbody tr');
                        var outputs = $j('#OutputLabels tbody tr');
                        analogInputs.find('select.arc').val('-1'); // not configured
                        digitalInputs.find('select.arc').val('-1'); // not configured
                        outputs.find('select.arc').val('-1'); // not configured
                        selectARCConfiguration(result.ARCTwoProperties.IO1.EventType, 0, analogInputs, digitalInputs, outputs);
                        selectARCConfiguration(result.ARCTwoProperties.IO2.EventType, 1, analogInputs, digitalInputs, outputs);
                        selectARCConfiguration(result.ARCTwoProperties.IO3.EventType, 2, analogInputs, digitalInputs, outputs);
                        selectARCConfiguration(result.ARCTwoProperties.IO4.EventType, 3, analogInputs, digitalInputs, outputs);
                    } else {
                        utility.handleWebServiceError(null);
                    }
                }
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(null);
                toggleLoadingMessage(false, 'asset-io-arc-configuration');
            }
        });
    }

	function loadIDPAssetIOConfiguration(asset) {
	    if (asset == null)
	        return;
	    if (($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP_DUAL_MODE) == -1) && ($j.inArray(asset.DeviceId, tracking.devices.SKYWAVE_IDP) == -1))
            return;

        var data = { assetId: asset.Id };
        toggleLoadingMessage(true, 'asset-io-configuration');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/IDPGetLatestCoreProperties'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                toggleLoadingMessage(false, 'asset-io-configuration');
                if (msg.d) {
                    var result = msg.d;
                    if (result.Success == true) {
                        if (result.CoreProperties == null)
                            return;
                        if (result.CoreProperties.EIO != null) {
                            $j('#EditAssetInputLabel1').parent().parent().find('select.eio').val(result.CoreProperties.EIO.Port1Config);
                            $j('#EditAssetInputLabel2').parent().parent().find('select.eio').val(result.CoreProperties.EIO.Port2Config);
                            $j('#EditAssetInputLabel3').parent().parent().find('select.eio').val(result.CoreProperties.EIO.Port3Config);
                            $j('#EditAssetInputLabel4').parent().parent().find('select.eio').val(result.CoreProperties.EIO.Port4Config);
                        } else if (result.CoreProperties.EEIO != null) {
                            $j('#EditAssetInputLabel1').parent().parent().find('select.eeio').val(result.CoreProperties.EEIO.Input1Config);
                            $j('#EditAssetInputLabel2').parent().parent().find('select.eeio').val(result.CoreProperties.EEIO.Input2Config);
                            $j('#EditAssetInputLabel3').parent().parent().find('select.eeio').val(result.CoreProperties.EEIO.Input3Config);
                            $j('#EditAssetInputLabel4').parent().parent().find('select.eeio').val(result.CoreProperties.EEIO.Input4Config);
                        }
                    } else {
                        utility.handleWebServiceError(null);
                    }
                }
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(null);
                toggleLoadingMessage(false, 'asset-io-configuration');
            }
        });
    }

    function loadAssetStatus(asset) {
        if (asset == null)
            return;
        var btn = $j('#RefreshAssetStatus');
        var data = { assetId: asset.Id };
        btn.addClass('disabled').prop('disabled', true);
        toggleLoadingMessage(true, 'asset-status');
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetAssetStatus'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                btn.removeClass('disabled').prop('disabled', false);
                if (msg.d) {
                    var result = msg.d;
                    if (result.Success === true) {
                        $j('#edit-asset-current-status input[value=""]').prop('checked', true);
                        $j('input[name=rbEditAssetMoving][value=' + result.Status.IsMoving + ']').prop('checked', true);
                        $j('input[name=rbEditAssetIgnitionOn][value=' + result.Status.IsIgnitionOn + ']').prop('checked', true);
                        $j('input[name=rbEditAssetIdling][value=' + result.Status.IsIdling + ']').prop('checked', true);
                        $j('input[name=rbEditAssetSpeeding][value=' + result.Status.IsSpeeding + ']').prop('checked', true);
                        $j('input[name=rbEditAssetDwelling][value=' + result.Status.IsDwelling + ']').prop('checked', true);
                        $j('input[name=rbEditAssetTowing][value=' + result.Status.IsTowing + ']').prop('checked', true);
                        $j('input[name=rbEditAssetInLowPowerMode][value=' + result.Status.IsInLowPowerMode + ']').prop('checked', true);
                        $j('input[name=rbEditAssetOnBackupPower][value=' + result.Status.IsOnBackupPower + ']').prop('checked', true);
                        $j('input[name=rbEditAssetAntennaCut][value=' + result.Status.IsAntennaCut + ']').prop('checked', true);
                        $j('input[name=rbEditAssetGpsJammed][value=' + result.Status.IsGpsJammed + ']').prop('checked', true);
                        $j('input[name=rbEditAssetCellJammed][value=' + result.Status.IsCellJammed + ']').prop('checked', true);
                        $j('input[name=rbEditAssetImmobilized][value=' + result.Status.IsImmobilized + ']').prop('checked', true);
                        $j('#EditAssetShockLogAlarmCount').text(result.Status.ShockLogAlarmCount);
                        updateAssetState(asset, result.Status);
                    } else {
                        utility.handleWebServiceError(tracking.strings.MSG_GET_STATUS_ERROR);
                    }
                }
                toggleLoadingMessage(false, 'asset-status');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_GET_STATUS_ERROR);
                toggleLoadingMessage(false, 'asset-status');
                btn.removeClass('disabled').prop('disabled', false);
            }
        });
    }

    function openCurrentDriversDialog(asset) {
        tracking.data.validation.currentDriver.resetForm();
        tracking.data.validation.currentDriver.currentForm.reset();

        tracking.data.validation.addPosition.resetForm();
        tracking.data.validation.addPosition.currentForm.reset();
        openDialogPanel(tracking.data.domNodes.dialogs.currentDrivers, tracking.strings.SET_DRIVER_STATUS, asset, false, null, 'asset', 'set-driver', openCurrentDriversDialog);
        loadAssetDrivers(asset);

        $(tracking.data.domNodes.dialogs.currentDrivers).data('assetId', asset.Id);
    }

    function showGarminFormSubmission(submission, panel) {
        var dialog = $j(tracking.data.domNodes.infoDialogs.garminSubmission);
        var openDialog = false;
        if (panel == null) {
            panel = dialog;
            openDialog = true;
            setDialogTitle(dialog, submission.Title);
        }
        var title = $j('.GarminSubmissionTitle', panel).text(submission.Title);
        var date = $j('.GarminSubmissionDate', panel).text(submission.CreatedOn);
        var items = $j('.GarminSubmissionItems', panel).empty();
        for (var i = 0; i < submission.Items.length; i++) {
            var item = submission.Items[i];
            var itemText = item.Title;
            if ((item.Subtitle != null) && (item.Subtitle != '') && (item.Subtitle != item.Title)) {
                itemText += ' - ' + item.Subtitle;
            }
            var itemLI = $j('<li>').text(itemText);
            var itemUL = $j('<ul>');

            if (item.Choices != null) {
                for (var j = 0; j < item.Choices.length; j++) {
                    var choice = item.Choices[j];
                    itemUL.append($j('<li>')
                        .append($j('<div class="custom-control custom-checkbox">')
                            .append($j('<input class="custom-control-input" type="checkbox" disabled="disabled">').attr('checked', choice.IsSelected))
                            .append($j('<label class="custom-control-label">').text(choice.Value))
                        )
                    );
                }
                itemUL.addClass('multiple');
            } else if (item.Waypoint != null) {
                itemUL.append($j('<li>')
                    .append($j('<a class="location" href="#">').text(item.Waypoint.Name).data('location', item.Waypoint.Location).data('name', item.Waypoint.Name))
                    .append($j('<br />'))
                    .append($j('<span>').text(tracking.strings.MESSAGE + ': ' + item.Waypoint.Message))
                    .append($j('<br />'))
                    .append($j('<span>').text(tracking.strings.STATUS + ': ' + item.Waypoint.Status))
                );
            } else {
                itemUL.append(el('li', formattedTextToDiv(item.Choice)));
            }
            itemLI.append(itemUL);
            items.append(itemLI);
        }
        if (openDialog) {
            dialog.dialog('open');
        }
    }

    function loadGarminFormSubmission(assetId, submissionId, panel) {
        toggleLoadingMessage(true, 'form-submission');
        var data = { assetId: assetId, submissionId: submissionId };
        $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetGarminFormSubmissionForAsset'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                if (msg.d) {
                    var result = msg.d;
                    if (result.Success == true) {
                        showGarminFormSubmission(result.Item, panel);
                    } else {
                        utility.handleWebServiceError(tracking.strings.MSG_GARMIN_FORM_SUBMISSION_ERROR);
                    }
                }
                toggleLoadingMessage(false, 'form-submission');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_GARMIN_FORM_SUBMISSION_ERROR);
                toggleLoadingMessage(false, 'form-submission');
            }
        });
    }

    function loadGarminFormsHistoryDialog(asset) {
        $(tracking.data.domNodes.dialogs.garminFormsHistory).data('assetId', asset.Id);
        openDialogPanel(tracking.data.domNodes.dialogs.garminFormsHistory, tracking.strings.GARMIN_FORMS, asset, true, null, 'asset', 'view-logs-garmin-forms', loadGarminFormsHistoryDialog);
        loadGarminFormsHistory(asset);
    }

    function loadGarminFormsHistory(asset) {
        var btn = document.getElementById('RefreshGarminFormLogs');
        var status = document.getElementById('garmin-forms-status');
        var data = { assetId: asset.Id };

        handleAjaxFormSubmission('GetGarminFormSubmissionHistoryForAsset', data, btn, status, null, tracking.strings.MSG_GARMIN_FORMS_HISTORY_ERROR, function (result) {
            if (result.Success == true) {
                var itemData = [];
                if (result.Items != null) {
                    for (var i = 0; i < result.Items.length; i++) {
                        var item = result.Items[i];
                        var address = '';
                        var address = null;
                        if (item.Position != null) {
                            address = item.Position.Address;
                            if (address == null || (address.trim() == '') || (asset.HideAddress)) {
                                address = convertToLatLngPreference(item.Position.DisplayLat, item.Position.DisplayLng, item.Position.Grid);
                            }
                            address = el('a.location', { href: '#', dataset: { marker: item.Position.Id }}, address);
                        }
                        itemData.push([
                            item.Id,
                            item.Title,
                            address,
                            el('button.ViewGarminSubmission.command.details.btn.btn-secondary', { dataset: { assetId: asset.Id, id: item.Id } }, tracking.strings.VIEW),
                            item.CreatedOn
                        ]);
                    }
                }

                $('#GarminFormLogs').dataTable({
                    data: itemData,
                    'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
                    'lengthChange': false, 'paging': true, 'pageLength': 10, 'deferRender': true,
                    'order': [[4, 'desc']],
                    'columnDefs': [{
                        'targets': '_all',
                        'render': $.fn.dataTable.render.text()
                    }],
                    'columns': [
                        { visible: false }, // id
                        {  }, // title
                        { render: renderDomElement }, // address
                        { sortable: false, render: renderDomElement }, // view submission
                        { width: '75px' } // createdon
                    ],
                    'language': tracking.strings.DATATABLE
                });

                $j('#GarminFormLogs').removeAttr('style');

            } else {
                formShowErrorMessage(status, tracking.strings.MSG_GARMIN_FORMS_HISTORY_ERROR);
            }
        });
    }

    function loadDriverHistoryDialog(asset) {
        $(tracking.data.domNodes.dialogs.driverHistory).data('assetId', asset.Id);
        openDialogPanel(tracking.data.domNodes.dialogs.driverHistory, tracking.strings.DRIVER_LOG, asset, true, null, 'asset', 'view-logs-driver', loadDriverHistoryDialog);

        loadDriverHistory(asset);
    }

    function loadDriverHistory(asset) {
        var btn = document.getElementById('RefreshDrivers');
        var status = document.getElementById('drivers-status');
        var data = { assetId: asset.Id };
        handleAjaxFormSubmission('GetDriverHistoryForAsset', data, btn, status, null, tracking.strings.MSG_DRIVER_HISTORY_ERROR, function (result) {
            if (result.Success == true) {
                var itemData = [];
                if (result.Items != null) {
                    for (var i = 0; i < result.Items.length; i++) {
                        var item = result.Items[i];
                        var source = item.Source;
                        if (item.CreatedBy != null) {
                            source = item.CreatedBy;
                        }
                        var history = el('a.history', { href: '#', dataset: { from: item.LoginOn, to: item.LogoutOn } }, tracking.strings.REPLAY_TRIP);
                        itemData.push([
                            //item.Id,
                            item.Driver.DriverId,
                            item.LoginStatus,
                            item.LoginOn,
                            item.LogoutStatus,
                            item.LogoutOn,
                            item.Duration,
                            history,
                            item.Notes,
                            source
                            //status,
                            //position,
                            //item.Source,
                            //item.CreatedOn
                        ]);
                    }
                }

                $('#DriverLogs').dataTable({
                    data: itemData,
                    'destroy': true, 'filter': false, 'info': false, 'jQueryUI': false, 'autoWidth': false,
                    'lengthChange': false, 'paging': true, 'pageLength': 10, 'deferRender': true,
                    'order': [[2, 'desc']],
                    'columnDefs': [{
                        'targets': '_all',
                        'render': $.fn.dataTable.render.text()
                    }],
                    'columns': [
                        { }, // driver
                        { }, // login
                        { width: '75px' }, // login on
                        { }, // logoff
                        { width: '75px' }, // logoff on
                        { }, // duration
                        { render: renderDomElement }, // history replay
                        { }, // notes
                        { } // source
                    ],
                    'language': tracking.strings.DATATABLE
                });

                $j('#DriverLogs').removeAttr('style');
            } else {
                formShowErrorMessage(status, tracking.strings.MSG_DRIVER_HISTORY_ERROR);
            }
        }, null);
    }

    function loadHistory(assetIds, replay) {
        var deferreds = [];
        for (var i = 0; i < tracking.data.assets.length; i++) {
            var makeActive = ($j.inArray(tracking.data.assets[i].Id, assetIds) !== -1);
            deferreds.push(toggleAssetActive(tracking.data.assets[i].Id, makeActive, false));
        }

        // todo: this should be a helper function
        if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
            // always show the control area if it is hidden
            if ($('#controlarea').is(':hidden')) {
                $('#controlarea').slideDown();
            }
            deferreds.push(switchMapMode(false, null, true));
        }

        $.when.apply($, deferreds).done(function() {
            setMapBounds();

            if (replay) {
                var assetId = assetIds[0];
                if (tracking.data.history.positionsByAssetId[assetId] !== undefined) {
                    var positionsForAsset = tracking.data.history.positionsByAssetId[assetId].Positions;
                    if (positionsForAsset !== undefined && positionsForAsset.length > 0) {
                        var latestPositionForAsset = positionsForAsset[positionsForAsset.length - 1];
                        openMarkerForPosition(latestPositionForAsset.Id);
                        playbackStart(assetId, latestPositionForAsset.Id, null);
                    }
                }
            }
        });
    }

    function loadWaypointHistoryDialog(asset) {
        $(tracking.data.domNodes.dialogs.waypointHistory).data('assetId', asset.Id);
        openDialogPanel(tracking.data.domNodes.dialogs.waypointHistory, tracking.strings.WAYPOINT_LOG, asset, true, null, 'asset', 'view-logs-waypoint', loadWaypointHistoryDialog);

        loadWaypointHistory(asset);
    }

    function loadWaypointHistory(asset) {
        var btn = document.getElementById('RefreshWaypoints');
        var status = document.getElementById('waypoint-history-status');
        var data = { assetId: asset.Id };

        handleAjaxFormSubmission('GetWaypointHistoryForAsset', data, btn, status, null, tracking.strings.MSG_IO_HISTORY_ERROR, function (result) {
            if (result.Success == true) {
                var itemData = [];
                for (var i = 0; i < result.Waypoints.length; i++) {
                    var item = result.Waypoints[i];
                    itemData.push([
                        item.Name,
                        convertToLatLngPreference(item.Lat, item.Lng, item.Grid),
                        item.Message,
                        item.CreatedOn,
                        item.Status,
                        item.ETA,
                        item.DistanceTo,
                        item.UpdatedOn,
                        el('button.delete-waypoint.btn.btn-outline-danger', { title: tracking.strings.DELETE, dataset: { waypointId: item.Id } }, svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#trash-alt-light' } }))),
                        item.Id
                    ]);
                }

                $('#WaypointHistory').dataTable({
                    data: itemData,
                    'destroy': true, 'filter': false, 'info': false, 'jQueryUI': false, 'autoWidth': false,
                    'lengthChange': false, 'paging': true, 'pageLength': 5, 'deferRender': true,
                    'order': [[3, 'desc']],
                    'columnDefs': [{
                        'targets': '_all',
                        'render': $.fn.dataTable.render.text()
                    }],
                    'columns': [
                        { sortable: false }, // name
                        { sortable: false }, // lat/lng
                        { sortable: false }, // message
                        { width: '75px' },   // createdon
                        { },                 // status
                        { },                 // eta
                        { sortable: false }, // distance to
                        { width: '75px' },   // updatedon
                        { sortable: false, class: 'center', render: renderDomElement }
                    ],
                    'language': tracking.strings.DATATABLE
                });

                $('#WaypointHistory').removeAttr('style');
            } else {
                formShowErrorMessage(status, tracking.strings.MSG_IO_HISTORY_ERROR);
            }
        }, null);
    }

    function loadIOHistory(asset) {
        toggleLoadingMessage(true, 'io-history');
        var data = { assetId: asset.Id };
        $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetIOHistoryForAsset'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                if (msg.d) {
                    var result = msg.d;
                    if (result.Success == true) {
                        var gpioData = [];
                        for (var i = 0; i < result.GPIOs.length; i++) {
                            var item = result.GPIOs[i];
                            var settings = createIOOutput(asset, item);
                            var address = null;
                            if (item.Position != null) {
                                address = item.Position.Address;
                                if (address == null || (address.trim() == '') || (asset.HideAddress)) {
                                    address = convertToLatLngPreference(item.Position.DisplayLat, item.Position.DisplayLng, item.Position.Grid);
                                }
                                address = el('a.location', { href: '#', dataset: { marker: item.Position.Id } }, address);
                            }
                            gpioData.push([
                                el('pre', settings),
                                address,
                                item.CreatedOn
                            ]);
                        }

                        $('#IOHistory').dataTable({
                            data: gpioData,
                            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
                            'lengthChange': false, 'paging': true, 'pageLength': 5, 'deferRender': true,
                            'order': [[2, 'desc']],
                            'columnDefs': [{
                                'targets': '_all',
                                'render': $.fn.dataTable.render.text()
                            }],
                            'columns': [
                                { sortable: false, render: renderDomElement }, // text
                                { sortable: false, render: renderDomElement }, // position
                                { width: '75px' } // time
                            ],
                            'language': tracking.strings.DATATABLE
                        });

                        $('#IOHistory').removeAttr('style');

                        tracking.data.io = result.GPIOs;
                    } else {
                        // output result.ErrorMessage
                    }
                    //dialog.dialog('open');
                }
                toggleLoadingMessage(false, 'io-history');
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_IO_HISTORY_ERROR);
                toggleLoadingMessage(false, 'io-history');
            }
        });
    }

    function loadLiveMessagesForAssets(assetIds) {
        if (tracking.state.portalLoadedEpoch === null) {
            return;
        }
        var queryAssetIds = [];
        _.each(assetIds, function (assetId) {
            var assetMessages = tracking.data.live.messagesByAssetId[assetId];
            if (assetMessages === undefined || assetMessages === null) {
                queryAssetIds.push(assetId);
            }
        });

        var isLoaded = $.Deferred();
        if (queryAssetIds.length === 0) {
            isLoaded.resolve(true);
            return isLoaded;
        }

        // query for the assets' messages for this date range
        var data = {
            assetIds: queryAssetIds,
            fromDate: moment.utc(tracking.state.portalLoadedEpoch).format(tracking.user.dateFormat),
            toDate: null,
            isUtc: true,
            format: tracking.user.dateFormat,
            lang: tracking.user.dateCulture
        };
        var loadingKey = 'message-live-' + queryAssetIds[0];
        toggleLoadingMessage(true, loadingKey);
        $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetMessageHistoryForAssetsByDateRange'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                if (msg.d) {
                    var result = msg.d;
                    if (result.Success == true) {
                        _.each(result.Assets, function (assetResult) {
                            addAssetMessages(assetResult, tracking.dataGroups.NORMAL_LIVE);
                        });
                    }
                }
                toggleLoadingMessage(false, loadingKey);
                isLoaded.resolve(true);
            },
            error: function (xhr, status, error) {
                isLoaded.reject(false);
                utility.handleWebServiceError(tracking.strings.MSG_MESSAGE_HISTORY_ERROR);
                toggleLoadingMessage(false, loadingKey);
            }
        });
        return isLoaded;
    }

    function loadHistoryMessagesForSharedView(sharedViewId) {
        var sharedView = findSharedViewById(sharedViewId);
        var isLoaded = $.Deferred();

        // check if already loaded
        if (tracking.data.sharedView.isMessagesLoaded) {
            isLoaded.resolve(true);
            return isLoaded;
        }

        var from = null;
        var to = null;
        if (sharedView.FromDateEpoch !== null) {
            from = moment.utc(sharedView.FromDateEpoch).format(tracking.user.dateFormat)
        }
        if (sharedView.ToDateEpoch !== null) {
            to = moment.utc(sharedView.ToDateEpoch).format(tracking.user.dateFormat)
        }

        var queryAssetIds = sharedView.AssetIds.slice(0);
        var assetGroupIds = sharedView.AssetGroupIds;
        _.each(assetGroupIds, function (assetGroupId) {
            var group = findGroupById(assetGroupId);
            if (group !== null) {
                var groupAssetIds = findAssetIdsUnderGroup(group);
                queryAssetIds = queryAssetIds.concat(groupAssetIds);
            }
        });
        
        if (queryAssetIds.length === 0) {
            isLoaded.resolve(true);
            return isLoaded;
        }

        // query for the asset's messages for this date range
        var data = {
            assetIds: queryAssetIds,
            fromDate: from,
            toDate: to,
            isUtc: true,
            format: tracking.user.dateFormat,
            lang: tracking.user.dateCulture
        };

        handleAjaxFormSubmission('GetMessageHistoryForAssetsByDateRange', data, null, null, null, tracking.strings.MSG_MESSAGE_HISTORY_ERROR, function (result) {
            tracking.data.sharedView.isMessagesLoaded = true;
            _.each(result.Assets, function (assetResult) {
                addAssetMessages(assetResult, tracking.dataGroups.SHARED_VIEW_HISTORY, null, sharedView);
            });
            isLoaded.resolve(true);
        }, function(xhr, staus, error) {
                isLoaded.reject(false);
        });

        return isLoaded;
    }

    function loadHistoryMessagesForTrip(tripId) {
        // separate query per date range here
        var trip = findTripById(tripId);
        var journey = findJourneyById(trip.JourneyId);

        var isLoaded = $.Deferred();

        var queryAssetIds = [];
        var tripMessages = tracking.data.trips.messagesByTripId[tripId];
        if (tripMessages === undefined || tripMessages === null) {
            queryAssetIds.push(journey.AssetId);
        }

        if (queryAssetIds.length === 0) {
            isLoaded.resolve(true);
            return isLoaded;
        }

        // query for the asset's messages for this date range
        var data = {
            assetIds: queryAssetIds,
            fromDate: moment.utc(trip.FromEpoch).format(tracking.user.dateFormat),
            toDate: moment.utc(trip.ToEpoch).format(tracking.user.dateFormat),
            isUtc: true,
            format: tracking.user.dateFormat,
            lang: tracking.user.dateCulture
        };
        var loadingKey = 'message-history-trip-' + queryAssetIds[0];
        toggleLoadingMessage(true, loadingKey);
        $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetMessageHistoryForAssetsByDateRange'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                if (msg.d) {
                    var result = msg.d;
                    if (result.Success == true) {
                        _.each(result.Assets, function (assetResult) {
                            addAssetMessages(assetResult, tracking.dataGroups.JOURNEY_HISTORY, trip);
                        });
                    }
                }
                toggleLoadingMessage(false, loadingKey);
                isLoaded.resolve(true);
            },
            error: function (xhr, status, error) {
                isLoaded.reject(false);
                utility.handleWebServiceError(tracking.strings.MSG_MESSAGE_HISTORY_ERROR);
                toggleLoadingMessage(false, loadingKey);
            }
        });

        return isLoaded;
    }

    function loadHistoryMessagesForAssets(assetIds) {
        // todo: loading indicator and error should be populated in the message dialog

        var isLoaded = $.Deferred();
        var queryAssetIds = [];
        _.each(assetIds, function (assetId) {
            var assetMessages = tracking.data.history.messagesByAssetId[assetId];
            if (assetMessages === undefined || assetMessages === null) {
                queryAssetIds.push(assetId);
            }
        });

        if (queryAssetIds.length === 0) {
            isLoaded.resolve(true);
            return isLoaded;
        }

        // query for the asset's messages for this date range
        var data = {
            assetIds: queryAssetIds,
            fromDate: tracking.data.history.fromDate,
            toDate: tracking.data.history.toDate,
            isUtc: false,
            format: tracking.user.dateFormat,
            lang: tracking.user.dateCulture
        };
        var loadingKey = 'message-history-' + queryAssetIds[0];
        toggleLoadingMessage(true, loadingKey);
        $.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetMessageHistoryForAssetsByDateRange'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                if (msg.d) {
                    var result = msg.d;
                    if (result.Success == true) {
                        _.each(result.Assets, function (assetResult) {
                            addAssetMessages(assetResult, tracking.dataGroups.NORMAL_HISTORY);
                        });
                    }
                }
                toggleLoadingMessage(false, loadingKey);
                isLoaded.resolve(true);
            },
            error: function (xhr, status, error) {
                isLoaded.reject(false);
                utility.handleWebServiceError(tracking.strings.MSG_MESSAGE_HISTORY_ERROR);
                toggleLoadingMessage(false, loadingKey);
            }
        });

        return isLoaded;
    }

	function loadMessageHistory(asset) {
        var device = findDeviceById(asset.DeviceId);
	    if (isSendCommandDisabledForDevice(device)) {
            $j('#MessagesSendCommand').prop('disabled', true);
        } else {
            $j('#MessagesSendCommand').prop('disabled', false);
        }

	    toggleLoadingMessage(true, 'message-history');
	    var btn = $j('#RefreshMessages');
	    btn.prop('disabled', true);
	    var data = { assetId: asset.Id };
	    $j.ajax({
	        type: 'POST',
	        url: wrapUrl('/services/GPSService.asmx/GetMessageHistoryForAsset'),
	        data: JSON.stringify(data),
	        contentType: 'application/json; charset=utf-8',
	        dataType: 'json',
	        success: function (msg) {
	            btn.prop('disabled', false);
	            if (msg.d) {
	                var result = msg.d;
	                if (result.Success == true && result.Assets.length > 0) {
                        result = result.Assets[0];

	                    var includeGateway = false;
                        if (_.indexOf(tracking.devices.SKYWAVE_IDP_DUAL_MODE, asset.DeviceId) !== -1) {
	                        includeGateway = true;
	                    }

                        // todo: limit the amount of data for rendering purposes?
                        var inboxData = [];
                        for (var i = 0; i < result.Inbox.length; i++) {
                            var item = result.Inbox[i];
                            var labelItems = [];
                            if (item.IsAcknowledged) {
                                if (item.AcknowledgedBy != null) {
                                    labelItems.push(text(item.AcknowledgedBy));
                                    labelItems.push(el('br'));
                                }
                                if (item.AcknowledgedOn != null) {
                                    labelItems.push(text(item.AcknowledgedOn));
                                }
                            }
                            var checkbox = el('input.custom-control-input', { type: 'checkbox', disabled: true, checked: item.IsAcknowledged });
                            if (item.IsAcknowledged) {
                                checkbox.setAttribute('checked', 'checked');
                            }
                            var acknowledgedElement = el('div.custom-control.custom-checkbox', [ 
                                checkbox,
                                text(' '),
                                el('label.custom-control-label', labelItems)
                            ]);
                            var priorityImage = el('img', { src: '/content/images/icon-priority-' + item.PriorityValue + '.png', alt: item.Priority, style: { border: 0 } });
                            var address = null;
                            if (item.Position != null) {
                                address = item.Position.Address;
                                if (address == null || (address.trim() == '') || (asset.HideAddress)) {
                                    address = convertToLatLngPreference(item.Position.DisplayLat, item.Position.DisplayLng, item.Position.Grid);
                                }
                                address = el('a.location', { href: '#', dataset: { marker: item.Position.Id } }, address);
                            }
                            inboxData.push([
                                priorityImage,
                                formattedTextToDiv(item.Text),
                                //el('pre', item.Text), // would <pre> be preferable?
                                address,
                                item.CreatedOn,
                                item.Source,
                                acknowledgedElement,
                                el('button.command.delete.delete-message.btn.btn-outline-danger', { title: tracking.strings.DELETE, dataset: { messageId: item.Id } }, svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#trash-alt-light' } })))
                            ]);
                        }

                        var inboxCompleted = new $j.Deferred();
                        var outboxCompleted = new $j.Deferred();
                        var alertsCompleted = new $j.Deferred();

                        $('#MessageHistoryInbox').dataTable({
                            data: inboxData,
                            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
                            'lengthChange': false, 'paging': true, 'pagingType': 'simple', 'pageLength': 10, 'deferRender': true,
                            'order': [[3, 'desc']],
                            'columnDefs': [{
                                'targets': '_all',
                                'render': $.fn.dataTable.render.text()
                            }],
                            'columns': [
                                { width: '45px', class: 'center', type: 'string', render: renderDomElement }, // priority
                                { sortable: false, render: renderDomElement }, // text
                                { sortable: false, render: renderDomElement }, // position
                                { width: '75px' }, // time
                                { width: '50px' }, // source
                                { width: '75px', class: 'center', type: 'string', render: renderDomElement }, // isacknowledged
                                { sortable: false, class: 'center', visible: (tracking.user.isAdmin || tracking.user.canEditAssets), render: renderDomElement } // delete
                            ],
                            'language': tracking.strings.DATATABLE,
                            'drawCallback': function (settings) {
                                //$j('button.command.delete', dialog).button({ text: null, icons: { primary: 'ui-icon-trash' } });
                            },
                            'initComplete': function (settings, json) {
                                inboxCompleted.resolve();
                            }
                        });

                        // outbox
                        var outboxData = [];
                        for (var i = 0; i < result.Outbox.length; i++) {
                            var item = result.Outbox[i];
                            outboxData.push([
                                item.Type,
                                item.Text,
                                item.RawText,
                                item.Source,
                                item.CreatedOn,
                                item.IsSent,
                                item.IsError,
                                item.Response,
                                item.SentOn,
                                item.Id,
                                item.Status,
                                el('button.command.delete.delete-message.btn.btn-outline-danger', { title: tracking.strings.DELETE, dataset: { messageId: item.Id } }, svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#trash-alt-light' } })))
                            ]);
                        }

                        $('#MessageHistoryOutbox').dataTable({
                            data: outboxData,
                            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
                            'lengthChange': false, 'paging': true, 'pagingType': 'simple', 'pageLength': 10, 'deferRender': true,
                            'order': [[4, 'desc']],
                            'columnDefs': [{
                                'targets': '_all',
                                'render': $.fn.dataTable.render.text()
                            }],
                            'columns': [
                                { class: 'center', width: '100px' }, // type
                                { // text
                                    sortable: false,
                                    render: function (data, type, row, meta) {
                                        if (type == 'display') {
                                            var raw = row[meta.col + 1];
                                            if ((data == null) || (data == '')) {
                                                if ((raw != null) && (raw != '')) {
                                                    var btn = el('button.btn.btn-sm.message-info.btn-secondary', { title: raw }, [
                                                        svg('svgn', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#info-circle' } })),
                                                        text(' '),
                                                        el('span', 'Info') // TODO: i18n
                                                    ]);
                                                    return btn.outerHTML;
                                                }
                                            }
                                            return $.fn.dataTable.render.text().display(data);
                                        }
                                        return data;
                                    }
                                }, // text
                                { visible: false }, // rawtext
                                { width: '50px', visible: includeGateway }, // source
                                { width: '125px' }, // createdon
                                { 
                                    width: '50px',
                                    class: 'center',
                                    render: function (data, type, row, meta) {
                                        var isSent = data;
                                        var isError = row[meta.col + 1];
                                        var response = row[meta.col + 2];
                                        var id = row[9];
                                        var status = row[10];
                                        var error = null;
                                        if (isError) {
                                            error = el('a.error', { title: response, href: '#', dataset: { messageId: id } }, tracking.strings.ERROR);
                                            status = '';
                                        }
                                        if (status == null) {
                                            status = '';
                                        }
                                        var checkbox = el('input.custom-control-input', { type: 'checkbox', disabled: true, checked: isSent });
                                        if (isSent) {
                                            checkbox.setAttribute('checked', 'checked');
                                        }
                                        var sent = el('div.custom-control.custom-checkbox', [ 
                                            checkbox,
                                            el('label.custom-control-label', [error, text(status)])
                                        ]);
                                        return sent.outerHTML;
                                    }
                                }, // issent
                                { visible: false }, // IsError
                                { visible: false }, // Response
                                { width: '125px' },
                                { visible: false }, // Id
                                { visible: false }, // Status
                                { sortable: false, class: 'center', visible: (tracking.user.isAdmin || tracking.user.canEditAssets), render: renderDomElement } // delete
                            ],
                            'language': tracking.strings.DATATABLE,
                            'initComplete': function (settings, json) {
                                outboxCompleted.resolve();
                            },
                            'drawCallback': function (settings) {
                                //$j('button.command.delete', dialog).button({ text: null, icons: { primary: 'ui-icon-trash' } });
                                //$j('#MessageHistoryOutbox button.message-info').button({ text: null, icons: { primary: 'ui-icon-info' } }).tooltip();
                                $j('#MessageHistoryOutbox button.message-info').bsTooltip({placement:'right'});
                                $j('#MessageHistoryOutbox a.error').bsTooltip().parent().addClass('error');
                            }
                        });

                        var alertsData = [];
                        for (var i = 0; i < result.Alerts.length; i++) {
                            var item = result.Alerts[i].Event;
                            item.Position = result.Alerts[i].Position;
                            if (item.Alert == null) {
                                continue;
                            }

                            var eventposition = getPositionLinkForEvent(item);
                            
                            var type = item.Alert.Type;
                            if (item.Alert.Name != null) {
                                type = item.Alert.Name + ': ' + type;
                            }
                            type = el('span', [ text(item.TypeName + ':'), el('br'), text(type) ]);
                            var ack = el('span', item.Alert.AcknowledgedStatus);
                            if (item.Type === 14) {
                                if (item.Alert.RequiresAcknowledgement) {
                                    ack = el('button.AlertAcknowledge.btn.btn-sm.btn-primary', { role: 'button', dataset: { alert: item.Id }}, tracking.strings.ACKNOWLEDGE);
                                }
                                if (item.Alert.Acknowledged) {
                                    ack = el('span', tracking.strings.ACKNOWLEDGE_STATUS.replace('{Status}', item.Alert.AcknowledgedStatus).replace('{Time}', item.Alert.AcknowledgedOn).replace('{User}', item.Alert.AcknowledgedBy))
                                }
                            }
                            alertsData.push([
                                '',
                                item.Alert.Priority,
                                type,
                                formattedTextToDiv(item.Alert.AcknowledgedText),
                                eventposition, // Position
                                item.Time,
                                ack,
                                item.Type,
                                item.Alert.Color
                            ]);
                        }

                        $('#MessageHistoryAlerts').dataTable({
                            data: alertsData,
                            'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
                            'lengthChange': false, 'paging': true, 'pagingType': 'simple', 'pageLength': 10, 'deferRender': true,
                            'order': [[5, 'desc']],
                            'columnDefs': [{
                                'targets': '_all',
                                'render': $.fn.dataTable.render.text()
                            }],
                            'columns': [
                                { sortable: false, width: '20px' }, // Color Display
                                { sortable: true }, // Priority
                                { sortable: true, render: renderDomElement }, // Type
                                { sortable: false, render: renderDomElement }, // Resolution
                                { sortable: false, render: renderDomElement }, // Position/lat/lng
                                { width: '75px' }, // Time
                                { sortable: false, render: renderDomElement }, // Acknowledge
                                { visible: false }, // Event Type Id
                                { visible: false }, // Color
                            ],
                            'language': tracking.strings.DATATABLE,
                            'initComplete': function (settings, json) {
                                alertsCompleted.resolve();
                            },
                            'drawCallback': function (settings) {
                            },
                            'rowCallback': function (row, data, index) {
                                if (data[8] != '') {
                                    $j('td:eq(0)', row).css('background-color', data[8]);
                                }
                            }
                        });

                        tracking.data.messaging.alerts = result.Alerts;
                        tracking.data.messaging.inbox = result.Inbox;
                        tracking.data.messaging.outbox = result.Outbox;
                    } else {
                        // output result.ErrorMessage
                    }
                    asset.LastIncomingMessage = null;
                    asset.MessageStatuses = [];
                    asset.UnconfirmedMessages = [];
                    tracking.throttles.updatePositionStatus();
                    $j.when(inboxCompleted, outboxCompleted, alertsCompleted).done(function () {
                        // todo: hide loading indicators
                        //dialog.dialog('open');
                    });
                }
                toggleLoadingMessage(false, 'message-history');
            },
            error: function (xhr, status, error) {
                btn.prop('disabled', false);
                utility.handleWebServiceError(tracking.strings.MSG_MESSAGE_HISTORY_ERROR);
                // re-enable show button and clear loading message
                toggleLoadingMessage(false, 'message-history');
            }
        });
    }

    function loadServiceMeterHistoryDialog(asset) {
        $(tracking.data.domNodes.dialogs.serviceMeterHistory).data('assetId', asset.Id);
        openDialogPanel(tracking.data.domNodes.dialogs.serviceMeterHistory, tracking.strings.SERVICE_METERS, asset, true, null, 'asset', 'view-logs-service-meters', loadServiceMeterHistoryDialog);

        loadServiceMeterHistory(asset);
    }

	function loadServiceMeterHistory(asset) {
	    toggleLoadingMessage(true, 'service-meter-history');
        var data = { assetId: asset.Id };
	    $j.ajax({
	        type: 'POST',
	        url: wrapUrl('/services/GPSService.asmx/GetServiceMeterHistoryForAsset'),
	        data: JSON.stringify(data),
	        contentType: 'application/json; charset=utf-8',
	        dataType: 'json',
	        success: function (msg) {
	            if (msg.d) {
	                var result = msg.d;
	                if (result.Success == true) {
	                    var meters = [];
	                    for (var i = 0; i < result.ServiceMeters.length; i++) {
	                        var meter = result.ServiceMeters[i];
	                        meters.push([
                                (meter.SM0Time == null ? '-' : meter.SM0Time),
                                (meter.SM0Distance == null ? '-' : meter.SM0Distance),
                                (meter.SM1Time == null ? '-' : meter.SM1Time),
                                (meter.SM1Distance == null ? '-' : meter.SM1Distance),
                                (meter.SM2Time == null ? '-' : meter.SM2Time),
                                (meter.SM2Distance == null ? '-' : meter.SM2Distance),
                                (meter.SM3Time == null ? '-' : meter.SM3Time),
                                (meter.SM3Distance == null ? '-' : meter.SM3Distance),
                                (meter.SM4Time == null ? '-' : meter.SM4Time),
                                (meter.SM4Distance == null ? '-' : meter.SM4Distance),
                                (meter.Odometer == null ? '-' : meter.Odometer),
                                meter.Time
	                        ]);
	                    }

	                    $('#ServiceMeterHistory').dataTable({
                            data: meters,
	                        'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
	                        'lengthChange': false, 'paging': true, 'pageLength': 10,
	                        'order': [[11, 'desc']],
                            'columnDefs': [{
                                'targets': '_all',
                                'render': $.fn.dataTable.render.text()
                            }],
	                        'columns': [
                                { class: 'center' },
                                { class: 'center' },
                                { class: 'center' },
                                { class: 'center' },
                                { class: 'center' },
                                { class: 'center' },
                                { class: 'center' },
                                { class: 'center' },
                                { class: 'center' },
                                { class: 'center' },
                                { class: 'center' },
                                { class: 'nowrap' },
                                ],
                            'language': tracking.strings.DATATABLE
	                    });
	                }
	            }
	            toggleLoadingMessage(false, 'service-meter-history');
	        },
	        error: function (xhr, status, error) {
	            utility.handleWebServiceError(tracking.strings.ERROR_SERVICE_METER_HISTORY);
	            // re-enable show button and clear loading message
	            toggleLoadingMessage(false, 'service-meter-history');
	        }
	    });
	}

    function loadFillupHistory(asset) {
	    toggleLoadingMessage(true, 'fillup-history');
	    var data = { assetId: asset.Id };
	    $j.ajax({
	        type: 'POST',
	        url: wrapUrl('/services/GPSService.asmx/GetFillupHistoryForAsset'),
	        data: JSON.stringify(data),
	        contentType: 'application/json; charset=utf-8',
	        dataType: 'json',
	        success: function (msg) {
	            if (msg.d) {
	                var result = msg.d;
	                if (result.Success == true) {
	                    var fuelData = [];
	                    for (var i = 0; i < result.Fillups.length; i++) {
	                        var fillup = result.Fillups[i];
	                        var odometer = '';
	                        var odometerdiff = '';
	                        var efficiency = '';
	                        var efficiencydiff = '';
	                        if (result.Fillups[i].Odometer != null) {
	                            odometer = fillup.Odometer + ' ' + distanceText();
	                        }
	                        if (fillup.OdometerDifference != null) {
	                            odometerdiff = fillup.OdometerDifference + ' ' + distanceText();
	                        }
	                        if (fillup.FuelEfficiency != null) {
	                            var actualeff = $j.parseFloat(convertFuelEfficiencyToStandard(fillup.FuelEfficiencyL100KM, tracking.preferences.PREFERENCE_FUEL_UNIT));
	                            var desiredeff = $j.parseFloat(convertFuelEfficiencyToStandard($j.parseFloat(asset.FuelEfficiency), tracking.preferences.PREFERENCE_FUEL_UNIT));
	                            efficiency = actualeff + ' ' + fuelEfficiencyType(tracking.preferences.PREFERENCE_FUEL_UNIT);
	                            if (asset.FuelEfficiency != '') {
	                                // L/100km negative is positive and vice-versa
	                                efficiencydiff = ((1 - (actualeff / desiredeff)) * 100 * -1).toFixed(2);
	                                if (tracking.preferences.PREFERENCE_FUEL_UNIT == 3) {
	                                    efficiencydiff *= -1;
                                    }
	                                if (efficiencydiff > 0) {
	                                    efficiencydiff = '+' + efficiencydiff;
	                                }
	                                efficiencydiff += '%';
	                            }
	                        }
	                        var source = null;
	                        if (fillup.PositionId != null) {
                                source = el('a.location', { href: '#', dataset: { marker: fillup.PositionId } }, tracking.strings.ASSET);
	                        } else {
	                            if (fillup.CreatedBy != '') {
	                                source = el('span', fillup.CreatedBy);
	                            }
	                        }
	                        fuelData.push([
                                result.Fillups[i].Fuel.toFixed(2) + ' ' + fuelText(),
                                odometer,
                                odometerdiff,
                                efficiency,
                                efficiencydiff,
                                source,
                                result.Fillups[i].Time,
                                el('button.delete-refuel.btn.btn-secondary', { dataset: { refuelId: result.Fillups[i].Id }, ariaLabel: tracking.strings.DELETE, title: tracking.strings.DELETE }, svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#trash-alt' } })))
	                        ]);
	                    }

	                    if (tracking.user.isAnonymous) {
	                        $j('#form-add-refuel fieldset').hide();
	                    }

	                    $('#RefuelHistory').dataTable({
                            data: fuelData,
	                        'destroy': true, 'filter': false, 'info': false, 'jQueryUI': true, 'autoWidth': false,
	                        'lengthChange': false, 'paging': true, 'pageLength': 5,
	                        'order': [[6, 'desc']],
                            'columnDefs': [{
                                'targets': '_all',
                                'render': $.fn.dataTable.render.text()
                            }],
	                        'columns': [
                                { class: 'nowrap' }, 
                                {}, 
                                { visible: !tracking.user.isAnonymous },  // ??
                                {}, 
                                {}, 
                                { render: renderDomElement },
                                { class: 'nowrap' }, 
                                { sortable: false, class: 'center', render: renderDomElement }
                            ],
                            'language': tracking.strings.DATATABLE,
	                        'drawCallback': function (oSettings) {
	                        },
	                        'rowCallback': function (row, data, index) {
	                            if (data[4] != '') {
	                                if (data[4].indexOf('-') != -1) {
	                                    $j('td:eq(4)', row).addClass('negative');
	                                } else {
	                                    $j('td:eq(4)', row).addClass('positive');
	                                }
	                            }
	                        }
	                    });
	                } else {
	                    // output result.ErrorMessage
	                }
	            }
	            toggleLoadingMessage(false, 'fillup-history');
	        },
	        error: function (xhr, status, error) {
	            utility.handleWebServiceError(tracking.strings.MSG_REFUEL_HISTORY_ERROR);
	            // re-enable show button and clear loading message
	            toggleLoadingMessage(false, 'fillup-history');
	        }
	    });
	}

    function loadIOHistoryDialog(asset) {
        $(tracking.data.domNodes.dialogs.ioHistory).data('assetId', asset.Id);
        openDialogPanel(tracking.data.domNodes.dialogs.ioHistory, tracking.strings.IO_LOG, asset, true, function () {
            tracking.data.io = null;
        }, 'asset', 'view-logs-io', loadIOHistoryDialog);

	    // clear table rows
	    loadIOHistory(asset);
	}

    function loadMessageHistoryDialog(asset) {
        $(tracking.data.domNodes.dialogs.messageHistory).data('assetId', asset.Id);
        openDialogPanel(tracking.data.domNodes.dialogs.messageHistory, tracking.strings.MESSAGE_LOG, asset, true, function () {
            tracking.data.messaging.inbox = null;
            tracking.data.messaging.outbox = null;
        }, 'asset', 'view-logs-message', loadMessageHistoryDialog);

	    // clear table rows
	    loadMessageHistory(asset);
	}

	function loadFillupHistoryDialog(asset) {
	    // refuel history/add a fillup
        $(tracking.data.domNodes.dialogs.refuelHistory).data('assetId', asset.Id);
        openDialogPanel(tracking.data.domNodes.dialogs.refuelHistory, tracking.strings.REFUEL_LOG, asset, true, null, 'asset', 'view-logs-refuel', loadFillupHistoryDialog);

        tracking.data.validation.refuelAdd.resetForm();
        tracking.data.validation.refuelAdd.currentForm.reset();

	    $j('#txtRefuelDate').val(moment().format(tracking.user.dateFormat));
	    $j('#txtRefuelAsset').text(asset.Name);
	    if (asset.FuelEfficiency != '') {
	        $j('#RefuelEfficiency').show();
	        $j('#txtRefuelFuelEfficiency').text(convertFuelEfficiencyToStandard(asset.FuelEfficiency, tracking.preferences.PREFERENCE_FUEL_UNIT) + ' ' + fuelEfficiencyType(tracking.preferences.PREFERENCE_FUEL_UNIT));
	    } else {
	        $j('#RefuelEfficiency').hide();
	    }
	    $j('#txtRefuelAmountType').text(fuelText()); // litres/gallons/imp gallons
	    $j('#txtRefuelOdometerType').text(distanceText()); // litres/gallons/imp gallons
	    $j('#hfRefuelAssetId').val(asset.Id);

	    loadFillupHistory(asset);
	}

	function setMapBounds() {
	    console.log('setMapBounds()');
        if (tracking.map == null) {
            return;
        }
	    // recreate bounds based on visible positions
	    tracking.clearBounds();
        if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
            if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                var positionMarkers = _.keyBy(tracking.data.live.markers, function(item) { return item.data.location.Id; });
                _.each(tracking.data.live.latestPositions, function (position) {
                    var asset = findAssetById(position.AssetId);
                    if (asset == null) {
                        return;
                    }
                    var position = position.Position;
                    var positionMarker = positionMarkers[position.Id];
                    if (positionMarker === undefined) {
                        return;
                    }
                    if (!tracking.map.hasLayer(positionMarker)) {
                        return;
                    }
                    tracking.extendBounds(positionMarker.getLatLng());
                });
            } else {
	            if (tracking.preferences.PREFERENCE_GROUP_POSITIONS) {
	                // set bounds based on markercluster
	                for (var i = 0; i < tracking.data.assets.length; i++) {
                        var cluster = tracking.data.history.markerClustersByAssetId[tracking.data.assets[i].Id];
	                    if (cluster == undefined) {
                            continue;
                        }
                        if (!tracking.map.hasLayer(cluster)) {
                            continue;
                        }
                        var clusterBounds = cluster.getBounds();
                        if (clusterBounds.isValid()) {
                            tracking.extendBounds(clusterBounds);
                        }
	                }
	            } else {
                    _.each(tracking.data.history.markers, function(positionMarker) {
                        if (!tracking.map.hasLayer(positionMarker)) {
                            return;
                        }
                        tracking.extendBounds(positionMarker.getLatLng());
                    });
	            }
	        }

            // active trip bounds
            _.each(tracking.data.visible.trips, function (tripId) {
                var trip = findTripById(tripId);
                if (trip !== null && tracking.data.trips.positionsByTripId[tripId] !== undefined) {
                    var visiblePositions = _.filter(tracking.data.trips.positionsByTripId[tripId].Positions, function(item) { return !item.IsHidden; });
                    _.each(visiblePositions, function (position) {
                        tracking.extendBounds(L.latLng(position.Lat, position.Lng));
                    });
                }
            });
        } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
            if (tracking.data.sharedView.temp !== null && tracking.data.sharedView.temp.Preferences.PositionConsolidation
                || (tracking.data.sharedView.temp === null && tracking.data.sharedView.current !== null && tracking.data.sharedView.current.Preferences.PositionConsolidation)) {
                // set bounds based on markercluster
                _.each(tracking.data.sharedView.markerClustersByAssetId, function (cluster) {
                    if (!tracking.map.hasLayer(cluster)) {
                        return;
                    }
                    var clusterBounds = cluster.getBounds();
                    if (clusterBounds.isValid()) {
                        tracking.extendBounds(clusterBounds);
                    }
                });
            } else {
                _.each(tracking.data.sharedView.markers, function (positionMarker) {
                    if (!tracking.map.hasLayer(positionMarker)) {
                        return;
                    }
                    tracking.extendBounds(positionMarker.getLatLng());
                });
            }
        }
        if ((tracking.state.bounds == null) || (!tracking.state.bounds.isValid())) {
            // set the map center to be global center
            tracking.map.setView(L.latLng(0, 0), 2);
        } else {
            tracking.map.fitBounds(tracking.state.bounds, { padding: [10, 10] });
            if (tracking.map.getZoom() > tracking.options.maximumZoom) {
                tracking.map.setZoom(tracking.options.maximumZoom);
            }
        }
	}

	function updateAssetState(asset, state) {
        //var priorStatus = mapAssetStateToStatusState(asset.State);
        var priorStatus = null;
        var newStatus = mapAssetStateToStatusState(state);
        asset.State = state;

        _.each(tracking.data.domNodes.assets[asset.Id], function (assetNode) {
            if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                updateAssetStatusIconNodes(assetNode.querySelector('.asset-indicators'), newStatus, priorStatus, asset.Id);
            } else {
                // TODO: this seems bad, detach/reattach instead so they don't have to be recreated?
                // store in tracking.data.domNodes.assets[asset.Id].statuses or such
                var status = assetNode.querySelectorAll('.status');
                _.each(status, function (item) {
                    item.parentNode.removeChild(item);
                });
            }
        });
    }

    function createAssetStatusIconNode(title, iconClass, type) {
        // <svg class="status notify-class"><title>Title</title><use href="/content/svg/tracking.svg?v=15#type"></use></svg>
        var notifyType = 'notify-' + iconClass;
        var icon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        icon.classList.add("status");
        if (type !== undefined && type !== null) {
            icon.classList.add('status-' + type);
            icon.setAttributeNS('http://www.w3.org/2000/svg', 'data-status', iconClass);
        }
        icon.classList.add(notifyType);
        var iconTitle = document.createElementNS('http://www.w3.org/2000/svg', 'title');
        iconTitle.textContent = title;
        icon.appendChild(iconTitle);
        var iconType = document.createElementNS('http://www.w3.org/2000/svg', 'use');
        iconType.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#' + notifyType);
        icon.appendChild(iconType);
        return icon;
    }

    function mapAssetStateToStatusState(state) {
        var status = {
            movement: null,
            engine: null,
            satellite: null,
            cellular: null,
            power: null
        };

        // movement status
        if (state.IsSpeeding === true) {
            status.movement = 'speeding';
        } else if (state.IsTowing === true) {
            status.movement = 'towing';
        } else if (state.IsMoving === true) {
            status.movement = 'moving';
        } else if (state.IsMoving === false || (state.IsMoving === null && state.IsTowing === false)) {
            status.movement = 'stationary';
        }

        // engine status
        if (state.IsImmobilized === true) {
            status.engine = 'immobilized';
        } else if (state.IsIdling === true) {
            status.engine = 'idling';
        } else if (state.IsIgnitionOn === true) {
            status.engine = 'ignition-on';
        } else if (state.IsIgnitionOn === false) {
            status.engine = 'ignition-off';
        }

        // sat signal status
        if (state.IsAntennaCut === true) {
            status.satellite = 'antenna-cut';
        } else if (state.IsGpsJammed === true) {
            status.satellite = 'gps-jammed';
        } else if (state.IsInLowPowerMode === true) {
            status.satellite = 'low-power-mode';
        }

        // cell signal status
        if (state.IsCellJammed === true) {
            status.cellular = 'cell-jammed';
        }

        // power status
        if (state.IsOnBackupPower === true) {
            status.power = 'backup-power';
        }

        return status;
    }

    function updateAssetStatusIconNodes(node, status, priorStatus, assetId) {
        var statusTitles = tracking.statusTitles;
        var newIcons = document.createDocumentFragment();
        var itemsAdded = false;
        // if was null, and now not null it must be added
        // else if was not null and now null it must be removed
        // else if not the same value then it must be updated
        // todo: re-add LPM interval to title

        // prior state is really just what is visible

        // TODO: detach/reattach nodes instead of removing/recreating?

        var statusTypes = ['movement', 'engine', 'satellite', 'cellular', 'power'];
        _.each(statusTypes, function (statusType) {
            // don't necessarily like that we're doing a 5x query per node here
            // other method of state storage instead of DOM?
            var priorStatusNode = node.querySelector('.status-' + statusType);
            if (priorStatusNode === null && status[statusType] !== null) {
                // new icon for state
                newIcons.appendChild(createAssetStatusIconNode(statusTitles[status[statusType]], status[statusType], statusType));
                itemsAdded = true;
            } else if (priorStatusNode !== null && status[statusType] === null) {
                // icon no longer known for state, so remove it
                var iconNode = node.querySelector('.status-' + statusType);
                if (iconNode !== null) {
                    iconNode.parentNode.removeChild(iconNode);
                }
            } else if (priorStatusNode !== null && priorStatusNode.getAttributeNS('http://www.w3.org/2000/svg', 'data-status') !== status[statusType]) {
                // state of icon has changed, update it
                var updateNode = node.querySelector('.status-' + statusType);
                if (updateNode !== null) {
                    // remove prior status classes
                    _.each(updateNode.classList, function(classItem) {
                        if (classItem.indexOf('notify-') !== -1) {
                            updateNode.classList.remove(classItem);
                        }
                    });
                    updateNode.classList.add('notify-' + status[statusType]);
                    updateNode.setAttributeNS('http://www.w3.org/2000/svg', 'data-status', status[statusType]);
                    updateNode.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#' + 'notify-' + status[statusType]);
                    updateNode.querySelector('title').textContent = statusTitles[status[statusType]];
                }
            }
        });

        if (itemsAdded) {
            node.appendChild(newIcons);
        }
    }

    function updateLiveDataForAsset(asset, ignoreState, position) {
        if (asset === undefined || asset === null) {
            return;
        }
        var latestPosition = tracking.data.live.latestPositionsByAssetId[asset.Id];
        if (latestPosition === undefined) {
            return;
        }

        var nodes = tracking.data.domNodes.assets[asset.Id];
        if (nodes === undefined) {
            return;
        }

        var position = latestPosition.Position;
        if (ignoreState === undefined || ignoreState === false) {
            updateAssetState(asset, position.State);
        } else {
            updateAssetState(asset, asset.State);
        }

        updateAssetFunctionBadges(getAssetDataGroupForCurrentViewMode(), asset.Id);
        updateGroupFunctionBadges(getAssetDataGroupForCurrentViewMode(), [asset.Id], 'asset');
        updateTimeBasedNotificationIndicatorsForAsset(asset);
        _.each(asset.ParentGroupIds, function (groupId) {
            updateTimeBasedNotificationIndicatorsForGroup(groupId);
        });

        // refresh the positions dialog if it is currently opened for a related asset or group
        if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
            if (position !== undefined && position !== null) {
                if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
                    && tracking.data.domNodes.panels.secondary.getAttribute('data-item-type') === 'assets'
                    && document.getElementById('dialog-functions').querySelector('.dialog') === tracking.data.domNodes.dialogs.assetPositions) {
                    // todo: only append to the existing listing, don't recreate
                    var assetId = parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id'));
                    if (asset.Id === assetId) {
                        openPositionsForAsset(asset);
                    }
                } else if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
                    && tracking.data.domNodes.panels.secondary.getAttribute('data-item-type') === 'groups'
                    && document.getElementById('dialog-functions').querySelector('.dialog') === tracking.data.domNodes.dialogs.assetPositions) {
                    // todo: only append to the existing listing, don't recreate
                    var groupId = tracking.data.domNodes.panels.secondary.getAttribute('data-item-id');
                    if (asset.ParentGroupIds.indexOf(groupId) !== -1) {
                        openPositionsForGroup(findGroupById(groupId));
                    }
                }
            }
        }
    }

    var FilteredList = function (items, container, renderer, options) {
        this.renderer = renderer;
        this.initialized = false;

        this.options = {
            paging: { currentPage: 1, pageSize: 20 },
            sorting: {
                sortBy: null,
                sortDirection: null
            },
            filtering: {
                search: ''
            },
            groupBy: null
        };
        
        this.domNodes = {
            listContainer: container,
            listItemsContainer: undefined,
            list: undefined,
            listItems: [],
            pagination: [],
            noItems: undefined,
            filterResults: undefined,
            searchBox: undefined,
            filterContainer: undefined
        };
        
        this.data = {
            items: []
        };        

        var self = this;

        function createDataIndex() {
            self.search = new JsSearch.Search('Id');
            self.search.searchIndex = new JsSearch.UnorderedSearchIndex();
            self.search.indexStrategy = new JsSearch.AllSubstringsIndexStrategy();
            self.search.addIndex('EventName');
            self.search.addIndex('AssetName');
            self.search.addIndex('Time');
            self.search.addIndex('Details');
            self.search.addIndex(['Position', 'Time']);
            self.search.addIndex(['Position', 'LatLng']);
            self.search.addIndex(['Position', 'Address']);
            self.search.addIndex('Method');
            self.search.addIndex('Status');
            self.search.addIndex('EventTypes');
            self.search.addIndex('Text');
            self.search.addDocuments(self.data.items);
        }

        function init(items, options) {
            $.extend(true, self.options, options);
            self.initialized = true;
            self.data.items = items;
            createDataIndex();

            var container = self.domNodes.listContainer;
            self.domNodes.pagination = container.querySelectorAll('.pagination');
            self.domNodes.noItems = container.querySelector('.list-none');
            self.domNodes.listItemsContainer = container.querySelector('.list-container');
            self.domNodes.list = container.querySelector('.list');
            self.domNodes.filterResults = container.querySelector('.filter-results');
            self.domNodes.filterContainer = container.querySelector('.filter-box');

            self.domNodes.searchBox = container.querySelector('.list-filter');

            if (!self.domNodes.filterContainer.classList.contains('disabled-feature')) {
                self.domNodes.filterContainer.classList.add('is-visible');
            } else {
                self.domNodes.filterContainer.classList.remove('is-visible');
            }

            var $container = $(container);
            $container.on('click', '.page-link', function (e) {
                e.preventDefault();

                self.changePage(this.getAttribute('data-page'));
            });

            $(container).on('click', '.panel-options-list a.dropdown-item', function (e) {
                e.preventDefault();
                switch (this.getAttribute('data-option-type')) {
                    //case 'sorting':
                    //    changeItemSort(this.getAttribute('data-sort-group'), parseInt(this.getAttribute('data-sort')));
                    //    break;                    
                    case 'list-sort':
                        // for asset notifications, since the sorting options are outside of the list containers
                        var sortBy = this.getAttribute('data-sort');
                        var isActive = this.classList.contains('active');
                        var sortDirection = this.classList.contains('desc') ? 'asc' : 'desc'; // toggle sort direction if active
                        this.classList.remove('desc');
                        this.classList.remove('asc');
                        this.classList.add(sortDirection);
                        this.classList.add('active');
                        // todo: toggle other active options in this group (when we have more than 1 option!)
                        if (!isActive) {
                            sortDirection = 'asc';
                        }
                        this.setAttribute('data-sort-dir', sortDirection);
                        self.sort(sortBy, sortDirection);
                        break;
                }
            });

            if (self.domNodes.searchBox !== undefined && self.domNodes.searchBox !== null) {
                $(self.domNodes.searchBox).on('keyup', function(e) {
                    var target = e.target || e.srcElement;
                    self.options.filtering.search = target.value;
                    self.redraw();
                });
            }

            self.redraw();
        }

        function isFilteringData() {
            var filtering = self.options.filtering;
            var isFiltering = false;
            if (filtering !== undefined && filtering !== null) {
                // are we applying a filter?
                // apply any pre-search filters first

                if (filtering.search !== undefined && filtering.search !== null && filtering.search !== '') {
                    // apply filtering.search to filteredItems
                    isFiltering = true;
                }
            }
            return isFiltering;
        }

        function filterData() {
            var filteredItems = self.data.items.slice(0); // work off a copy
            var filtering = self.options.filtering;

            // filter
            var isFiltering = isFilteringData();
            if (filtering !== undefined && filtering !== null) {
                // are we applying a filter?
                // apply any pre-search filters first

                if (filtering.search !== undefined && filtering.search !== null && filtering.search !== '') {
                    // apply filtering.search to filteredItems
                    filteredItems = self.search.search(filtering.search);
                }
            }
            self.data.filtered = filteredItems;

            // group items
            if (self.options.groupBy !== undefined && self.options.groupBy !== null) {
                // TODO generic grouping somehow
                var filteredGroups = [];
                var filteredGroupLookup = {};
                _.each(self.data.filtered, function (item) {
                    if (filteredGroupLookup[item[self.options.groupBy]] === undefined) {
                        filteredGroupLookup[item[self.options.groupBy]] = { Epoch: item.Epoch, AssetId: item.AssetId, Items: []};
                        filteredGroupLookup[item[self.options.groupBy]][self.options.groupBy] = item[self.options.groupBy];
                        filteredGroups.push(filteredGroupLookup[item[self.options.groupBy]]);
                    }
                    filteredGroupLookup[item[self.options.groupBy]].Items.push(item);
                });
                self.data.filtered = filteredGroups;
            }

            if (isFiltering) {
                self.domNodes.filterResults.classList.add('is-visible');
                self.domNodes.filterResults.querySelector('span').textContent = self.data.filtered.length + '/' + self.data.items.length;
            } else {
                self.domNodes.filterResults.classList.remove('is-visible');
            }
        }

        function sortData() {
            var sorting = self.options.sorting;
            if (sorting !== undefined && sorting !== null) {
                var sortBy = sorting.sortBy;
                var sortDirection = sorting.sortDirection;

                if (sortBy === undefined || sortBy === null) {
                    var activeSort = self.domNodes.listContainer.querySelector('.sort-options.active');
                    if (activeSort !== null) {
                        sortBy = activeSort.getAttribute('data-sort');
                        sortDirection = activeSort.getAttribute('data-sort-dir');
                    }
                }

                if (sortBy !== undefined && sortBy !== null && sortBy !== '') {
                    // will likely have to replace this with specific sorting functions
                    // at some point to support multi-sort (e.g. Asset Name asc, Epoch desc)
                    self.data.filtered = _.sortBy(self.data.filtered, sortBy);
                    if (sortDirection === 'desc') {
                        self.data.filtered = self.data.filtered.reverse();
                    }
                }
                sorting.sortBy = sortBy;
                sorting.sortDirection = sortDirection;
            }
        }

        function pageData() {
            var paging = self.options.paging;

            // page
            var currentPage = 1;
            var pageSize = 20;
            if (paging !== undefined && paging !== null) {
                // paging.currentPage?
                if (paging.currentPage !== undefined) {
                    currentPage = paging.currentPage;
                }
                // paging.pageSize
                if (paging.pageSize !== undefined) {
                    pageSize = paging.pageSize;
                }
            }
            var filteredItemCount = self.data.filtered.length;

            // apply paging
            var totalPages = Math.ceil(filteredItemCount / pageSize);
            if (totalPages === 0) {
                totalPages = 1;
            }
            if (currentPage < 1) {
                currentPage = 1;
            } else if (currentPage > totalPages) {
                currentPage = totalPages;
            }
            paging.currentPage = currentPage;
            paging.totalPages = totalPages;
            var startIndex = pageSize * (currentPage - 1);
            var endIndex = startIndex + pageSize - 1;
            if (endIndex > (filteredItemCount - 1)) {
                endIndex = filteredItemCount - 1;
            }
            var currentItems = endIndex - startIndex;
            var visibleItems = self.data.filtered.slice(startIndex, endIndex + 1);
            self.data.visible = visibleItems;
            self.data.visibleStartIndex = startIndex;
            self.data.visibleEndIndex = endIndex;
        }

        function renderVisibleItems() {
            if (self.renderer === undefined) {
                console.error('No renderer defined for FilteredList.');
                return;
            }

            // remove existing items from list
            var listNode = self.domNodes.list;
            while (listNode.firstChild) {
                listNode.removeChild(listNode.firstChild);
            }

            // render paged items
            var listNodes = [];
            var fragmentNode = document.createDocumentFragment();
            var renderIndex = self.data.visibleStartIndex;
            _.each(self.data.visible, function (item) {
                // render list item
                var li = self.renderer(item, renderIndex);
                if (li !== undefined) {
                    listNodes.push(li);
                    fragmentNode.appendChild(li);
                    renderIndex++;
                } else {
                    console.warn('Could not render list item:');
                    console.warn(item);
                }
            });
            self.domNodes.listItems = listNodes;

            self.domNodes.list.appendChild(fragmentNode);

            // check for pagination nodes
            if (self.domNodes.pagination.length > 0) {
                var showPagination = (self.data.filtered.length > self.options.paging.pageSize);
                var pageNodeFragment = createPageNodes();
                for (var i = self.domNodes.pagination.length - 1; i >= 0; i--) {
                    var paginationContainer = self.domNodes.pagination[i];
                    if (showPagination) {
                        paginationContainer.classList.add('is-visible');
                    } else {
                        paginationContainer.classList.remove('is-visible');
                    }

                    // empty existing pages
                    while (paginationContainer.firstChild) {
                        paginationContainer.removeChild(paginationContainer.firstChild);
                    }

                    var pageNodes = pageNodeFragment;
                    if (i > 0) {
                        pageNodes = pageNodes.cloneNode(true);
                    }
                    paginationContainer.appendChild(pageNodes);
                }
            }

            // if no filtered items are visible, hide the list if we're not searching for anything
            if (self.data.filtered.length > 0) {
                self.domNodes.noItems.classList.remove('is-visible');
                self.domNodes.listItemsContainer.classList.add('is-visible');
                if (!self.domNodes.filterContainer.classList.contains('disabled-feature')) {
                    self.domNodes.filterContainer.classList.add('is-visible');
                }
            } else {
                self.domNodes.noItems.classList.add('is-visible');
                self.domNodes.listItemsContainer.classList.remove('is-visible');
                if (!isFilteringData()) {
                    self.domNodes.filterContainer.classList.remove('is-visible');
                }
            }

            if (self.options.renderCallback !== undefined) {
                self.options.renderCallback(self);
            }
        }

        function createPageNodes() {
            // add paging
            var pages = document.createDocumentFragment();

            var paging = self.options.paging;

            // paging prev,1,2,3 ... last,next
            var hasPrev = paging.currentPage > 1;
            var hasNext = paging.currentPage < paging.totalPages;
            var pagePrev = document.createElement('li');
            pagePrev.className = 'page-item' + (hasPrev ? '' : ' disabled');
            var pagePrevLink = document.createElement('a');
            pagePrevLink.className = 'page page-link';
            //pagePrevLink.textContent = tracking.strings.POSITION_PREV;
            pagePrevLink.href = '#';
            pagePrevLink.setAttribute('data-page', paging.currentPage - 1);
            var pagePrevIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            var pagePrevIconContents = document.createElementNS('http://www.w3.org/2000/svg', 'use');
            pagePrevIconContents.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#caret-left-solid');
            pagePrevIcon.appendChild(pagePrevIconContents);
            pagePrevLink.appendChild(pagePrevIcon);
            pagePrev.appendChild(pagePrevLink);
            pages.appendChild(pagePrev);

            // always have first and last page
            var windowSize = 2;
            var startPage = paging.currentPage - windowSize;
            var endPage = paging.currentPage + windowSize;
            var addedFirstPage = false;
            var addedLastPage = false;
            if (startPage <= 1) {
                startPage = 1;
                addedFirstPage = true;
            }
            if (endPage >= paging.totalPages) {
                endPage = paging.totalPages;
                addedLastPage = true;
            }
            var morePagesBeforeFirst = startPage > 2;
            var morePagesBeforeLast = (paging.totalPages - endPage) > 1;
            if (!addedFirstPage) {
                // add first and ellipsis
                var pageItem = document.createElement('li');
                pageItem.className = 'page-item' + (1 === paging.currentPage ? ' active' : '');
                var pageItemLink = document.createElement('a');
                pageItemLink.className = 'page page-link';
                pageItemLink.textContent = "1";
                pageItemLink.href = '#';
                pageItemLink.setAttribute('data-page', 1);
                pageItem.appendChild(pageItemLink);
                pages.appendChild(pageItem);

                if (morePagesBeforeFirst) {
                    var pageItem = document.createElement('li');
                    pageItem.className = 'page-item disabled';
                    var pageItemLink = document.createElement('a');
                    pageItemLink.className = 'page page-link';
                    pageItemLink.textContent = '...';
                    pageItemLink.href = '#';
                    pageItem.appendChild(pageItemLink);
                    pages.appendChild(pageItem);
                }
            }

            for (var i = startPage; i <= endPage; i++) {
                var pageItem = document.createElement('li');
                pageItem.className = 'page-item' + (i === paging.currentPage ? ' active' : '');
                var pageItemLink = document.createElement('a');
                pageItemLink.className = 'page page-link';
                pageItemLink.textContent = i;
                pageItemLink.href = '#';
                pageItemLink.setAttribute('data-page', i);
                pageItem.appendChild(pageItemLink);
                pages.appendChild(pageItem);
            }

            if (!addedLastPage) {
                // add last and ellipsis
                if (morePagesBeforeLast) {
                    var pageItem = document.createElement('li');
                    pageItem.className = 'page-item disabled';
                    var pageItemLink = document.createElement('a');
                    pageItemLink.className = 'page page-link';
                    pageItemLink.textContent = '...';
                    pageItemLink.href = '#';
                    pageItem.appendChild(pageItemLink);
                    pages.appendChild(pageItem);
                }

                var pageItem = document.createElement('li');
                pageItem.className = 'page-item' + (paging.totalPages === paging.currentPage ? ' active' : '');
                var pageItemLink = document.createElement('a');
                pageItemLink.className = 'page page-link';
                pageItemLink.textContent = paging.totalPages;
                pageItemLink.href = '#';
                pageItemLink.setAttribute('data-page', paging.totalPages);
                pageItem.appendChild(pageItemLink);
                pages.appendChild(pageItem);
            }

            var pageNext = document.createElement('li');
            pageNext.className = 'page-item' + (hasNext ? '' : ' disabled');
            var pageNextLink = document.createElement('a');
            pageNextLink.className = 'page page-link';
            //pageNextLink.textContent = tracking.strings.POSITION_NEXT;
            pageNextLink.href = '#';
            pageNextLink.setAttribute('data-page', paging.currentPage + 1);
            var pageNextIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            var pageNextIconContents = document.createElementNS('http://www.w3.org/2000/svg', 'use');
            pageNextIconContents.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#caret-right-solid');
            pageNextIcon.appendChild(pageNextIconContents);
            pageNextLink.appendChild(pageNextIcon);
            pageNext.appendChild(pageNextLink);
            pages.appendChild(pageNext);

            return pages;
        }

        this.reindex = createDataIndex;

        this.addData = function(items) {
            if (self.search !== undefined && self.search !== null) {
                self.search.addDocuments(items);
            }
            self.data.items = self.data.items.concat(items);
            self.redraw();
        };        

        // TODO is there any benefit to knowing that we've modified data instead of replaced all?
        // I think just set list.data

        this.redraw = function () {
            filterData();
            sortData();
            pageData();
            renderVisibleItems();
        };

        this.sort = function (sortBy, sortDirection) {
            if (self.options.sorting === undefined) {
                self.options.sorting = { sortBy: sortBy, sortDirection: sortDirection };
            } else {
                self.options.sorting.sortBy = sortBy;
                self.options.sorting.sortDirection = sortDirection;
            }

            sortData();
            pageData();
            renderVisibleItems();
        };

        this.changePage = function (pageNum) {
            var toPage = parseInt(pageNum);
            if (toPage === self.options.paging.currentPage) {
                return;
            }

            self.options.paging.currentPage = parseInt(pageNum);
            pageData();

            // TODO paging should simply update and not be redrawn
            renderVisibleItems();
        }

        //this.render = function () { // used?
        //    renderVisibleItems();
        //};

        init(items, options);
    }

    function createListing(items, grouping) {
        var container = document.getElementById('asset-' + grouping + '-container');
        var mapped = _.map(items, function (item) {
            return mapActivityItemToListItem(item);
        });

        // TODO pull filter from somewhere
        if (tracking.notificationLists[grouping + 'List'] === null) {
            tracking.notificationLists[grouping + 'List'] = new FilteredList(mapped, container, renderListItem, { paging: { currentPage: 1 }, groupBy: (grouping === 'activity' ? 'EpochGroup' : null) });
        } else {
            // change data and re-render
            var listing = tracking.notificationLists[grouping + 'List'];
            listing.data.items = mapped;
            listing.reindex();
            listing.redraw();
        }

        // TODO deciding which item should be in meta
        var first = _.first(_.sortBy(mapped, 'Epoch').reverse());
        renderMetaItem(first, grouping);
    }

    function renderMetaItem(item, grouping) {
        var meta = document.getElementById('panel-secondary-meta').querySelector('.meta-' + grouping);
        var metaDetails = meta.querySelector('table');
        if (item !== undefined) {
            metaDetails.classList.remove('toggle-content');
            if (item.IsChat !== undefined || item.MessageId !== undefined) {
                var itemType = meta.querySelector('.recent-item-type');
                if (itemType !== null) {
                    itemType.querySelector('.meta-item').textContent = item.EventName;
                }
                var itemMessage = meta.querySelector('.recent-item-message');
                if (itemMessage !== null) {
                    itemMessage.querySelector('.meta-item').textContent = item.Text;
                }
            } else if (item.EventId !== undefined) {
                var itemType = meta.querySelector('.recent-item-type');
                itemType.querySelector('.meta-item').textContent = item.EventName;
            } else if (item.PositionId !== undefined) {
            }

            // common to all
            var itemTime = meta.querySelector('.recent-item-time');
            if (itemTime !== null) {
                itemTime.querySelector('.meta-item').textContent = item.Time;
            }
            var itemName = meta.querySelector('.recent-item-name');
            if (itemName !== null) {
                itemName.querySelector('.meta-item').textContent = item.AssetName;
            }
            var itemAddress = meta.querySelector('.recent-item-address');
            if (itemAddress !== null) {
                if (item.Position !== undefined && item.Position !== null && item.Position.Address !== null) {
                    itemAddress.querySelector('.meta-item').textContent = item.Position.Address;
                    itemAddress.classList.remove('toggle-content');
                } else {
                    itemAddress.classList.add('toggle-content');
                }
            }
            var itemLatLng = meta.querySelector('.recent-item-latlng');
            if (itemLatLng !== null) {
                if (item.Position !== undefined && item.Position !== null && item.Position.LatLng !== null) {
                    itemLatLng.querySelector('.meta-item').textContent = item.Position.LatLng;
                    itemLatLng.classList.remove('is-visible');
                } else {
                    itemLatLng.classList.add('toggle-content');
                }
            }
        } else {
            metaDetails.classList.add('toggle-content');
        }
    }

    function mapMessageToCommonFormat(message, isFromMobile) {
        var item = {
            Id: message.Id,
            AssetId: message.AssetId,
            IsFromMobile: isFromMobile,
            CreatedOn: message.CreatedOn,
            CreatedEpoch: message.CreatedEpoch,
            Type: message.Type,
            TypeId: message.TypeId,
            Text: message.Text,
            Source: message.Source,
            Priority: message.Priority,
            Position: message.Position
        };

        // TODO .Position a reference to positionsById instead?
        if (isFromMobile) {
            item.IsAcknowledged = message.IsAcknowledged;
            item.AcknowledgedOn = message.AcknowledgedOn;
        } else {
            item.RawText = message.RawText;
            item.SentOn = message.SentOn;
            item.IsSent = message.IsSent;
            item.IsError = message.IsError;
            item.Status = message.Status;
            item.Response = message.Response;
        }
        return item;
    }

    function setListItemIndex(listItem, index) {
        var indexDom = listItem.querySelector('.item-index');
        if (indexDom !== null) {
            indexDom.textContent = '#' + (index + 1);
        }
    }

    function getListItemGroupForEvent(item) {
        // TODO store this on the mapped item instead
        var itemType = 'positions';
        if (item.IsChat === true) {
            itemType = 'chat';
        } else if (item.MessageId !== undefined) {
            itemType = 'messages';
        } else if (item.EventId !== undefined) {
            itemType = 'events';
            if (tracking.data.EVENTS_STATUS.indexOf(item.Type) !== -1) {
                itemType = 'status';
            } else if (tracking.data.EVENTS_ALERT.indexOf(item.Type) !== -1) {
                itemType = 'alerts';
            } else if (tracking.data.EVENTS_EMERGENCY.indexOf(item.Type) !== -1) {
                itemType = 'alerts';
            } else {
                itemType = 'events';
            }
        }
        return itemType;
    }

    function renderEventListItem(item, index) {
        if (item === undefined) {
            return undefined;
        }
        var dataSource = (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW ? tracking.data.domNodes.sharedView : tracking.data.domNodes);
        var existing = dataSource.eventListingById[item.EventId];
        if (existing !== undefined) {
            setListItemIndex(existing, index);
            return existing;
        }

        var isEmergencyAlert = false;
        var listItemGroup = 'events';
        if (tracking.data.EVENTS_STATUS.indexOf(item.Type) !== -1) {
            listItemGroup = 'status';
        } else if (tracking.data.EVENTS_ALERT.indexOf(item.Type) !== -1) {
            listItemGroup = 'alerts';
        } else if (tracking.data.EVENTS_EMERGENCY.indexOf(item.Type) !== -1) {
            listItemGroup = 'alerts';
            isEmergencyAlert = true;
        } else {
            listItemGroup = 'events';
        }

        
        var isHidden = item.Position !== undefined && item.Position !== null && item.Position.IsHidden;
        var li = document.createElement('li');
        if (isEmergencyAlert) {
            li.classList.add('is-emergency');
        }
        if (isHidden) {
            li.classList.add('is-hidden');
        }

        var header = renderListItemHeader(item.EventName, item.Position, listItemGroup, item.AssetId, index);

        var contents = document.createElement('div');
        contents.className = "list-contents";
        // asset icon if group listing

        var row = renderListItemCommonMeta(item, true, true, true);
        contents.appendChild(row);

        // details header (for alerts and grouped events/activity)
        var eventDetailsHeader = renderListItemDetailsHeader(item, false);
        if (eventDetailsHeader !== undefined) {
            contents.appendChild(eventDetailsHeader);
        }
        var eventDetails = renderListItemDetails(item, false);
        if (eventDetails !== undefined) {
            contents.appendChild(eventDetails);
        }

        li.appendChild(header);
        li.appendChild(contents);

        dataSource.eventListingById[item.EventId] = li;
        return li;
    }

    function renderPositionListItem(item, index) {
        if (item === undefined) {
            return undefined;
        }

        var dataSource = (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW ? tracking.data.domNodes.sharedView : tracking.data.domNodes);
        var existing = dataSource.positionListingById[item.PositionId];
        if (existing !== undefined) {
            setListItemIndex(existing, index);
            return existing;
        }        

        var li = document.createElement('li');
        var isHidden = item.Position !== undefined && item.Position !== null && item.Position.IsHidden;
        if (isHidden) {
            li.classList.add('is-hidden');
        }

        var hasAddress = (item.Position.Address !== null);
        var itemName = hasAddress ? item.Position.Address : item.Position.LatLng;
        var header = renderListItemHeader(itemName, item.Position, 'positions', item.AssetId, index);

        var contents = document.createElement('div');
        contents.className = "list-contents";

        var row = renderListItemCommonMeta(item, false, hasAddress, true);
        contents.appendChild(row);

        li.appendChild(header);
        li.appendChild(contents);

        dataSource.positionListingById[item.PositionId] = li;
        return li;
    }


    function renderActivityListItem(groupItem, index) {
        // special handling of activity list items which are grouped by Epoch and formatted differently
        if (groupItem === undefined) {
            return undefined;
        }

        var dataSource = (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW ? tracking.data.domNodes.sharedView : tracking.data.domNodes);
        var existing = dataSource.activityListingById[groupItem.EpochGroup];
        if (existing !== undefined) {
            setListItemIndex(existing, index);
            return existing;
        }

        var firstItem = _.first(groupItem.Items);
        var itemWithPosition = _.find(groupItem.Items, function (item) { return item.Position !== undefined && item.Position !== null; });

        var li = document.createElement('li');
        var position = undefined;
        if (itemWithPosition !== undefined) {
            position = itemWithPosition.Position;
        }
        var isHidden = position !== undefined && position !== null && position.IsHidden;
        if (isHidden) {
            li.classList.add('is-hidden');
        }

        var header = renderListItemHeader(firstItem.Time, position, 'activity', groupItem.AssetId, index);
        li.appendChild(header);

        var contents = document.createElement('div');
        contents.className = "list-contents";

        // common items for the position, but what if we don't have a position?
        // and we don't need time!
        var isGrouped = true;
        var showAddress = true;
        var showLatLng = true;
        var showTime = false;
        var row = renderListItemCommonMeta(itemWithPosition !== undefined ? itemWithPosition : firstItem, showAddress, showLatLng, showTime);
        if (row !== undefined) {
            contents.appendChild(row);
        }

        if (itemWithPosition !== undefined) {
            // additional position details
            var positionDetails = renderListItemPositionDetails(itemWithPosition);
            if (positionDetails !== undefined) {
                contents.appendChild(positionDetails);
            }
        }

        _.each(groupItem.Items, function(item) {
            var eventDetailsHeader = renderListItemDetailsHeader(item, isGrouped);
            if (eventDetailsHeader !== undefined) {
                contents.appendChild(eventDetailsHeader);
            }
            var eventDetails = renderListItemDetails(item, isGrouped);
            if (eventDetails !== undefined) {
                contents.appendChild(eventDetails);
            }
        });
        li.appendChild(contents);

        dataSource.activityListingById[groupItem.EpochGroup] = li;
        return li;
    }

    function renderListItemIcon(type) {
        var iconTypes = {
            'positions': 'map-marker-alt',
            'events': 'exclamation-square',
            'status': 'exclamation-circle',
            'alerts': 'exclamation-triangle',
            'chat': 'comments-alt',
            'messages': 'code',
            'activity': 'clock'
        }
        var typeIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        var typeIconContents = document.createElementNS('http://www.w3.org/2000/svg', 'use');
        typeIconContents.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#' + iconTypes[type]);
        typeIcon.appendChild(typeIconContents);
        return typeIcon;
    }

    function renderListItemHeader(name, position, type, assetId, index) {
        var header = document.createElement('div');
        header.className = 'list-header';

        var typeIcon = renderListItemIcon(type);
        header.appendChild(typeIcon);

        if (position !== undefined && position !== null) {
            var listItemTypeNameLink = document.createElement('a');
            listItemTypeNameLink.textContent = name;
            listItemTypeNameLink.className = 'event-name location';
            listItemTypeNameLink.setAttribute('href', '#');
            listItemTypeNameLink.setAttribute('data-marker', position.Id);
            listItemTypeNameLink.setAttribute('data-time', position.Time);
            listItemTypeNameLink.setAttribute('data-asset', assetId);
            listItemTypeNameLink.setAttribute('data-hidden', position.IsHidden);
            header.appendChild(listItemTypeNameLink);
        } else {
            var listItemTypeName = document.createElement('span');
            listItemTypeName.textContent = name;
            listItemTypeName.className = 'event-name';
            header.appendChild(listItemTypeName);
        }

        if (position !== undefined && position !== null) {
            if (tracking.user.canEditAssets) {
                var mapVisibility = document.createElement('a');
                mapVisibility.className = 'map-toggle';
                mapVisibility.setAttribute('href', '#');
                mapVisibility.setAttribute('data-marker', position.Id);
                mapVisibility.setAttribute('data-asset', assetId);
                mapVisibility.setAttribute('data-hidden', position.IsHidden);
                mapVisibility.title = position.IsHidden ? tracking.strings.SHOW_ON_MAP : tracking.strings.HIDE_ON_MAP;

                var mapVisibilityIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
                var mapVisibilityIconContents = document.createElementNS('http://www.w3.org/2000/svg', 'use');
                mapVisibilityIconContents.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#' + (position.IsHidden ? 'invisible' : 'visible'));
                mapVisibilityIcon.appendChild(mapVisibilityIconContents);
                mapVisibility.appendChild(mapVisibilityIcon);
                header.appendChild(mapVisibility);
            }

            var mapLink = document.createElement('a');
            mapLink.className = 'location map-hilight';
            mapLink.setAttribute('href', '#');
            mapLink.setAttribute('data-marker', position.Id);
            mapLink.setAttribute('data-time', position.Time);
            mapLink.setAttribute('data-asset', assetId);

            var mapIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            var mapIconContents = document.createElementNS('http://www.w3.org/2000/svg', 'use');
            mapIconContents.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#globe-americas');
            mapIcon.appendChild(mapIconContents);
            mapLink.appendChild(mapIcon);
            header.appendChild(mapLink);
        }

        // position in list
        var itemIndex = document.createElement('span');
        itemIndex.className = 'item-index';
        itemIndex.textContent = '#' + (index + 1);
        header.appendChild(itemIndex);
        return header;
    }

    function renderDetailsItem(label, text) {
        if (text === undefined || text === null || text === '') {
            return undefined;
        }
        var itemLine = document.createElement('div');

        var itemLabel = document.createElement('span');
        itemLabel.className = 'item-label';
        itemLabel.textContent = label + ': ';

        var itemText = document.createElement('span')
        itemText.textContent = text;
        itemLine.appendChild(itemLabel);
        itemLine.appendChild(itemText);
        return itemLine;
    }

    function renderListItemPositionDetails(item) {
        if (item === undefined || item.Position === undefined) {
            return undefined;
        }

        var items = [
            renderDetailsItem(tracking.strings.SPEED, item.Position.Speed),
            renderDetailsItem(tracking.strings.ALTITUDE, item.Position.Altitude),
            renderDetailsItem(tracking.strings.HEADING, item.Position.Heading),
            renderDetailsItem(tracking.strings.ACCURACY, item.Position.Accuracy),
            renderDetailsItem(tracking.strings.DATA_SOURCE, item.Position.Source),
            renderDetailsItem(tracking.strings.STATUS, item.Position.Status),
            renderDetailsItem(tracking.strings.GEOFENCES, item.Position.InsideFences),
            renderDetailsItem(tracking.strings.ODOMETER, item.Position.Odometer)
        ];
        var actualItems = _.filter(items, function(row) { return row !== undefined; });
        if (actualItems.length === 0) {
            return undefined;
        }

        var itemDetails = document.createDocumentFragment();
        var header = document.createElement('div')
        header.className = 'event-details-header';

        var headerButton = document.createElement('button');
        headerButton.className = 'btn btn-link';
        headerButton.setAttribute('data-toggle', 'collapse');
        headerButton.setAttribute('data-target', '#position-details-' + item.Id);
        headerButton.setAttribute('aria-expanded', 'false');

        headerButton.appendChild(renderListItemIcon('positions'));

        var headerTitle = document.createElement('span');
        headerTitle.className = 'mr-auto';
        headerTitle.textContent = tracking.strings.DETAILS;
        headerButton.appendChild(headerTitle);

        var infoExpandIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        infoExpandIcon.setAttribute('class', 'list-item-action list-item-details');
        var infoExpandIconTitle = document.createElementNS('http://www.w3.org/2000/svg', 'title');
        infoExpandIconTitle.textContent = tracking.strings.DETAILS;
        infoExpandIcon.appendChild(infoExpandIconTitle);
        var infoExpandIconContents = document.createElementNS('http://www.w3.org/2000/svg', 'use');
        infoExpandIconContents.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#expand');
        infoExpandIcon.appendChild(infoExpandIconContents);
        headerButton.appendChild(infoExpandIcon);

        header.appendChild(headerButton);

        var details = document.createElement('div');
        details.id = 'position-details-' + item.Id;
        details.className = 'event-details break-text collapse';

        var meta = document.createElement('div');
        meta.className = 'item-metadata';

        _.each(actualItems, function (rowItem) {
            meta.appendChild(rowItem);
        });
        
        details.appendChild(meta);

        itemDetails.appendChild(header);
        itemDetails.appendChild(details);
        return itemDetails;
    }

    function renderListItemCommonMeta(item, includeAddress, includeLatLng, includeTime) {
        if (item === undefined) {
            return undefined;
        }
        var row = document.createElement('div');
        row.className = 'list-item-row';

        var assetIcon = document.createElement('div');
        assetIcon.className = 'item-icon item-asset-name';
        assetIcon.style.backgroundImage = 'url(' + item.IconUrl + ')';
        row.appendChild(assetIcon);

        var meta = document.createElement('div');
        meta.className = 'item-metadata';

        // details
        var assetLine = document.createElement('div');
        assetLine.className = 'item-asset-name';

        var assetNameLabel = document.createElement('span');
        assetNameLabel.className = 'item-label';
        assetNameLabel.textContent = tracking.strings.ASSET + ': ';

        var assetName = document.createElement('span')
        assetName.className = 'event-asset-name'
        assetName.textContent = item.AssetName;
        assetLine.appendChild(assetNameLabel);
        assetLine.appendChild(assetName);
        meta.appendChild(assetLine);

        //if (includeLatLng && item.Position !== undefined && item.Position !== null) {
        //    // lat/lng
        //    var latLngLine = document.createElement('div');
        //    var latLngLabel = document.createElement('span');
        //    latLngLabel.className = 'item-label';
        //    latLngLabel.textContent = tracking.strings.LAT_LNG + ': ';
        //    latLngLine.appendChild(latLngLabel);

        //    var latLngLink = document.createElement('a');
        //    latLngLink.setAttribute('href', '#');
        //    latLngLink.className = 'event-latlng location';
        //    latLngLink.setAttribute('data-marker', item.Position.Id);
        //    latLngLink.setAttribute('data-time', item.Position.Time);
        //    latLngLink.setAttribute('data-asset', item.AssetId);
        //    latLngLink.textContent = item.Position.LatLng;
        //    latLngLine.appendChild(latLngLink);
        //    meta.appendChild(latLngLine);
        //}

        if (includeAddress && item.Position !== undefined && item.Position !== null && item.Position.Address !== null) {
            var addressLine = document.createElement('div');
            var addressLabel = document.createElement('span');
            addressLabel.className = 'item-label';
            addressLabel.textContent = tracking.strings.ADDRESS + ': ';
            var address = document.createElement('a');
            address.setAttribute('href', '#');
            address.className = 'event-location location';
            address.setAttribute('data-marker', item.Position.Id);
            address.setAttribute('data-time', item.Position.Time);
            address.setAttribute('data-asset', item.AssetId);
            address.textContent = item.Position.Address;
            addressLine.appendChild(addressLabel);
            addressLine.appendChild(address);
            meta.appendChild(addressLine);
        }

        if (includeLatLng && item.Position !== undefined && item.Position !== null && item.Position.LatLng !== null) {
            var latLngLine = document.createElement('div');
            var latLngLabel = document.createElement('span');
            latLngLabel.className = 'item-label';
            latLngLabel.textContent = tracking.strings.LAT_LNG + ': ';
            latLngLine.appendChild(latLngLabel);

            if (!includeAddress || item.Position.Address === null) {
                var latLngLink = document.createElement('a');
                latLngLink.setAttribute('href', '#');
                latLngLink.className = 'event-latlng location';
                latLngLink.setAttribute('data-marker', item.Position.Id);
                latLngLink.setAttribute('data-time', item.Position.Time);
                latLngLink.setAttribute('data-asset', item.AssetId);
                latLngLink.textContent = item.Position.LatLng;
                latLngLine.appendChild(latLngLink);
            } else {
                var latLngValue = document.createElement('span');
                latLngValue.className = 'event-latlng';
                latLngValue.textContent = item.Position.LatLng;
                latLngLine.appendChild(latLngValue);
            }
            meta.appendChild(latLngLine);
        }

        // time
        if (includeTime) {
            var timeLine = document.createElement('div');
            var timeLabel = document.createElement('span');
            timeLabel.className = 'item-label';
            timeLabel.textContent = tracking.strings.TIME + ': ';
            timeLine.appendChild(timeLabel);
            var timeValue = document.createElement('span');
            timeValue.className = 'item-time index';
            timeValue.textContent = item.Time;
            timeLine.appendChild(timeValue);
            //timeValue.setAttribute('data-epoch', position.epoch);
        
            var epochValue = document.createElement('span');
            epochValue.className = 'hidden item-epoch';
            epochValue.textContent = item.Epoch;
            timeLine.appendChild(epochValue);
            meta.appendChild(timeLine);
        }

        // position-specific
        if (item.EventTypes !== undefined && item.EventTypes !== null && item.EventTypes !== '') {
            var typesLine = document.createElement('div');
            var typesLabel = document.createElement('span');
            typesLabel.className = 'item-label';
            typesLabel.textContent = tracking.strings.TYPE + ': ';
            typesLine.appendChild(typesLabel);
            var typesValue = document.createElement('span');
            typesValue.className = 'item-types index';
            typesValue.textContent = item.EventTypes;
            typesLine.appendChild(typesValue);
            meta.appendChild(typesLine);
        }

        // message-specific
        if (item.Source !== undefined && item.Source !== null) {
            var sourceLine = document.createElement('div');
            var sourceLabel = document.createElement('span');
            sourceLabel.className = 'item-label';
            sourceLabel.textContent = tracking.strings.DATA_SOURCE + ': ';
            sourceLine.appendChild(sourceLabel);
            var sourceValue = document.createElement('span');
            sourceValue.className = 'item-source';
            sourceValue.textContent = item.Source;
            sourceLine.appendChild(sourceValue);
            meta.appendChild(sourceLine);
        }

        if (item.Method !== undefined && item.Method !== null) {
            var methodLine = document.createElement('div');
            var methodLabel = document.createElement('span');
            methodLabel.className = 'item-label';
            methodLabel.textContent = tracking.strings.METHOD + ': ';
            methodLine.appendChild(methodLabel);
            var methodValue = document.createElement('span');
            methodValue.className = 'item-method';
            methodValue.textContent = item.Method;
            methodLine.appendChild(methodValue);
            meta.appendChild(methodLine);
        }

        if (item.IsFromMobile !== undefined && !item.IsFromMobile) { // && item.Status !== undefined && item.Status !== null) {
            var statusLine = document.createElement('div');
            var statusLabel = document.createElement('span');
            statusLabel.className = 'item-label';
            statusLabel.textContent = tracking.strings.STATUS + ': ';
            statusLine.appendChild(statusLabel);
            if (item.IsError) {
                var statusValue = document.createElement('a');
                statusValue.className = 'item-status error';
                statusValue.setAttribute('href', '#');
                statusValue.setAttribute('data-message-id', item.Id);
                if (item.Response !== null) {
                    statusValue.setAttribute('title', item.Response);
                    $(statusValue).bsTooltip();
                }
                statusValue.textContent = item.Status;
                statusLine.appendChild(statusValue);
            } else {
                var statusValue = document.createElement('span');
                statusValue.className = 'item-status';
                statusValue.textContent = item.Status;
                statusLine.appendChild(statusValue);
            }
            meta.appendChild(statusLine);
        }

        // other info
        row.appendChild(meta);
        return row;
    }

    function renderListItemDetails(item, isGrouped) {
        var alert = item.Alert;
        if (alert === null) {
            alert = undefined;
        }
        if ((item.Details === undefined && alert === undefined) ||
         (item.Details === null && alert === undefined)
         || (item.Details === '' && alert === undefined)) {
            return undefined;
        }
        var details = document.createElement('div');
        details.id = 'event-details-' + item.Id;
        details.className = 'event-details break-text';
        if (item.Details !== null) {
            details.classList.add('is-inline');
            setChildren(details, item.Details);
        }

        if (isGrouped || (alert !== undefined)) {
            // will have a header to expand/contract this section
            details.classList.add('collapse');
            details.classList.remove('is-inline');
        }

        if (alert !== undefined) {
            // alert triggered/no longer triggered event
            if (item.Type === 14) { // alert triggered
                if (alert.Acknowledged === 0) {
                    // add alert acknowledgement form elements
                    var form = document.createElement('form');
                    form.id = 'asset-alert-acknowledge-' + item.EventId;
                    form.setAttribute('data-alert-id', alert.Id);
                    form.setAttribute('data-asset-id', item.AssetId);
                    form.setAttribute('data-event-id', item.EventId);

                    var status = document.createElement('div');
                    status.id = 'asset-alert-acknowledge-status-' + item.EventId;
                    status.className = 'dialog-status alert toggle-content';

                    var resolution = document.createElement('div');
                    resolution.className = 'alert-resolution form-group';
                    var resolutionLabel = document.createElement('label');
                    resolutionLabel.setAttribute('for', 'asset-alert-resolution-' + item.EventId);
                    resolutionLabel.textContent = tracking.strings.RESOLUTION;
                    resolution.appendChild(resolutionLabel);
                    var resolutionText = document.createElement('textarea');
                    resolutionText.id = 'asset-alert-resolution-' + item.EventId;
                    resolutionText.className = 'form-control required';
                    resolution.appendChild(resolutionText);

                    var textAcknowledge = alert.LabelAcknowledge !== null ? alert.LabelAcknowledge : tracking.strings.ACKNOWLEDGE;
                    var textAcknowledgeAlt = alert.LabelAcknowledgeAlt !== null ? alert.LabelAcknowledgeAlt : tracking.strings.ACKNOWLEDGE_ALT;
                    var acknowledge = document.createElement('div');
                    acknowledge.className = 'alert-acknowledge dialog-buttons';
                    var ackButton = document.createElement('button');
                    ackButton.className = 'btn btn-primary alert-acknowledge';
                    ackButton.textContent = textAcknowledge;
                    var altAckButton = document.createElement('button');
                    altAckButton.className = 'btn btn-secondary alert-acknowledge-alt';
                    altAckButton.textContent = textAcknowledgeAlt;
                    acknowledge.appendChild(ackButton);
                    acknowledge.appendChild(altAckButton);

                    form.appendChild(status);
                    form.appendChild(resolution);
                    form.appendChild(acknowledge);
                    details.appendChild(form);
                } else {
                    // include acknowledged by details
                    var resolution = document.createElement('div');
                    resolution.className = 'alert-resolution form-group';
                    var resolutionLabel = document.createElement('label');
                    resolutionLabel.setAttribute('for', 'asset-alert-resolution-' + item.EventId);
                    resolutionLabel.textContent = tracking.strings.RESOLUTION;
                    resolution.appendChild(resolutionLabel);
                    var resolutionText = document.createElement('span');
                    resolutionText.id = 'asset-alert-resolution-' + item.EventId;
                    resolutionText.className = 'form-control-plaintext';
                    resolutionText.textContent = alert.AcknowledgedText;
                    resolution.appendChild(resolutionText);

                    var status = document.createElement('div');
                    status.className = 'alert-status form-group';
                    var statusLabel = document.createElement('label');
                    statusLabel.setAttribute('for', 'asset-alert-status-' + item.EventId);
                    statusLabel.textContent = tracking.strings.STATUS;
                    status.appendChild(statusLabel);
                    var statusText = document.createElement('span');
                    statusText.id = 'asset-alert-status-' + item.EventId;
                    statusText.className = 'form-control-plaintext';
                    statusText.textContent = tracking.strings.ACKNOWLEDGE_STATUS.replace('{Status}', alert.AcknowledgedStatus).replace('{Time}', alert.AcknowledgedOn).replace('{User}', alert.AcknowledgedBy);
                    status.appendChild(statusText);

                    details.appendChild(resolution);
                    details.appendChild(status);
                }
            }
        }
        return details;
    }

    function renderListItemDetailsHeader(item, isGrouped) {
        var alert = item.Alert;
        if (item.EventName !== undefined && (isGrouped || (alert !== undefined && alert !== null))) {
            var hasDetails = item.Details !== undefined && item.Detials !== null && item.Details !== '';
            var hasAlert = alert !== undefined && alert !== null;

            var eventDetailsHeader = document.createElement('div')
            eventDetailsHeader.className = 'event-details-header';

            var isCollapse = hasDetails || hasAlert;
            if (isCollapse) {
                // for collapsible panels use buttons and data-toggle
                var headerButton = document.createElement('button');
                headerButton.className = 'btn btn-link';
                headerButton.setAttribute('data-toggle', 'collapse');
                headerButton.setAttribute('data-target', '#event-details-' + item.Id);
                headerButton.setAttribute('aria-expanded', 'false');

                // if grouped, include event type icon
                if (isGrouped) {
                    var itemType = getListItemGroupForEvent(item);
                    var typeIcon = renderListItemIcon(itemType);
                    if (typeIcon !== undefined) {
                        //typeIcon.className = 'list-item-icon';
                        typeIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'class', 'list-item-icon');
                        headerButton.appendChild(typeIcon);
                    }
                }

                var eventDetailsTitle = document.createElement('span');
                eventDetailsTitle.className = 'mr-auto';
                eventDetailsTitle.textContent = !isGrouped ? tracking.strings.DETAILS : item.EventName;
                headerButton.appendChild(eventDetailsTitle);

                if (item.Type === 14) { // alert triggered
                    var alertAcknowledgedIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
                    alertAcknowledgedIcon.classList.add('list-item-action');
                    alertAcknowledgedIcon.classList.add('alert-acknowledge-icon');
                    if (alert.Acknowledged === 0) {
                        if (alert.RequiresAcknowledgement) {
                            alertAcknowledgedIcon.classList.add('requires-acknowledgement');
                        } else {
                            alertAcknowledgedIcon.classList.add('pending-acknowledgement');
                        }
                    } else {
                        alertAcknowledgedIcon.classList.add('is-acknowledged');
                    }
                    alertAcknowledgedIcon.setAttribute('data-event-id', item.EventId);
                    var alertAcknowledgedTitle = document.createElementNS('http://www.w3.org/2000/svg', 'title');
                    alertAcknowledgedTitle.textContent = tracking.strings.ACKNOWLEDGE_ALERT;
                    alertAcknowledgedIcon.appendChild(alertAcknowledgedTitle);

                    var alertAcknowledgedIconType = document.createElementNS('http://www.w3.org/2000/svg', 'use');
                    if (alert.Acknowledged === 0) {
                        alertAcknowledgedIconType.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#times-circle-solid');
                    } else {
                        alertAcknowledgedIconType.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#check-circle-solid');
                    }

                    alertAcknowledgedIcon.appendChild(alertAcknowledgedIconType);
                    headerButton.appendChild(alertAcknowledgedIcon);
                }

                var infoExpandIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
                infoExpandIcon.setAttribute('class', 'list-item-action list-item-details');
                var infoExpandIconTitle = document.createElementNS('http://www.w3.org/2000/svg', 'title');
                infoExpandIconTitle.textContent = tracking.strings.DETAILS;
                infoExpandIcon.appendChild(infoExpandIconTitle);
                var infoExpandIconContents = document.createElementNS('http://www.w3.org/2000/svg', 'use');
                infoExpandIconContents.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#expand');
                infoExpandIcon.appendChild(infoExpandIconContents);
                headerButton.appendChild(infoExpandIcon);

                eventDetailsHeader.appendChild(headerButton);
            } else {
                // if grouped, include event type icon
                if (isGrouped) {
                    var itemType = getListItemGroupForEvent(item);
                    var typeIcon = renderListItemIcon(itemType);
                    if (typeIcon !== undefined) {
                        eventDetailsHeader.appendChild(typeIcon);
                    }
                }

                var eventDetailsTitle = document.createElement('span');
                eventDetailsTitle.textContent = item.EventName;
                eventDetailsHeader.appendChild(eventDetailsTitle);
            }
            return eventDetailsHeader;
        } else {
            return undefined;
        }
    }

    function renderMessageListItem(item, index) {
        if (item === undefined) {
            return undefined;
        }

        var dataSource = (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW ? tracking.data.domNodes.sharedView : tracking.data.domNodes);
        var existing = dataSource.messageListingById[item.MessageId];
        if (existing !== undefined) {
            setListItemIndex(existing, index);
            return existing;
        }

        var li = document.createElement('li');
        li.className = item.IsFromMobile ? 'from-mobile' : 'to-mobile';
        var isHidden = item.Position !== undefined && item.Position !== null && item.Position.IsHidden;
        if (isHidden) {
            li.classList.add('is-hidden');
        }

        var header = renderListItemHeader(item.EventName, item.Position, 'messages', item.AssetId, index);

        //// info button to view raw message, if available
        //if (!item.IsFromMobile && item.RawText !== undefined && item.RawText !== null && item.RawText !== '') {
        //    var infoLink = document.createElement('a');
        //    infoLink.className = 'message-info';
        //    infoLink.setAttribute('href', '#');
        //    infoLink.setAttribute('title', item.RawText);

        //    var infoIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        //    var infoIconContents = document.createElementNS('http://www.w3.org/2000/svg', 'use');
        //    infoIconContents.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#info-circle');
        //    infoIcon.appendChild(infoIconContents);
        //    infoLink.appendChild(infoIcon);
        //    header.appendChild(infoLink);
        //    $(infoLink).bsTooltip();
        //}

        var contents = document.createElement('div');
        contents.className = "list-contents";

        var row = renderListItemCommonMeta(item, true, true, true);
        contents.appendChild(row);

        // details - if alert, have this be toggleable
        var details = document.createElement('div');
        details.className = 'event-details break-text';
        if (item.Details !== null && item.Details !== undefined) {
            details.classList.add('is-inline');
            setChildren(details, item.Details);
        }

        contents.appendChild(details);
        li.appendChild(header);
        li.appendChild(contents);        

        dataSource.messageListingById[item.MessageId] = li;
        return li;
    }

    function renderListItem(item, index) {
        // TODO filter out text message events that are not Chats before this is called
        var li = undefined;
        if (item.IsChat !== undefined) {
            li = renderMessageListItem(item, index); // TODO different chat rendering
        } else if (item.MessageId !== undefined) {
            li = renderMessageListItem(item, index);
        } else if (item.EventId !== undefined) {
            li = renderEventListItem(item, index);
        } else if (item.PositionId !== undefined) {
            li = renderPositionListItem(item, index);
        } else if (item.Items !== undefined) {
            li = renderActivityListItem(item, index);
        }
        return li;
    }

    function createHistoryPositionResults() {
        _.each(tracking.data.assets, function (asset) {
            updateAssetFunctionBadges(getAssetDataGroupForCurrentViewMode(), asset.Id);
        });
        updateGroupFunctionBadges(getAssetDataGroupForCurrentViewMode(), null, 'asset');

        _.each(tracking.data.history.positionsByAssetId, function (assetHistoryPositions, assetId) {
            var isWithinSearchRange = (assetHistoryPositions !== undefined && assetHistoryPositions.ResultsForSearch === true);
            if (isWithinSearchRange) {
                _.each(tracking.data.domNodes.assets[assetId], function (assetNode) {
                    var timeIndicator = assetNode.querySelector('.notifications');
                    timeIndicator.classList.add('recent-positions');
                });
            }
        });

        updateActiveAssetInformation(tracking.viewModes.NORMAL);

        createFilteredEventsList();
    }

	function editFenceSegments(fence) {
		console.log(fence);
	    for (var j = 0; j < fence.Segments.length; j++) {
	        var segment = fence.Segments[j];
	        switch (segment.Type) {
	        	case 0: // line
	        		var points = [];
	        		for (var k = 0; k < segment.Points.length; k++) {
	        			var point = segment.Points[k];
	        			var latlng = L.latLng(point.Lat, point.Lng);
	        			//MapToolbar.addPoint(latlng, MapToolbar.currentFeature);
						points.push(latlng);
	        		}
	                MapToolbar.initFeature('line', segment.Id, points);

	                break;
	        	case 1: // shape
	        		//tracking.data.drawing.manager.edit()
	        		var points = [];
	                for (var k = 0; k < segment.Points.length - 1; k++) { // ignore the last point for a polygon as it's the same as the first
	                    var point = segment.Points[k];
	                    var latlng = L.latLng(point.Lat, point.Lng);
						points.push(latlng);
	                }
	                MapToolbar.initFeature('shape', segment.Id, points);
	                break;
	            case 2: // circle


	                var center = L.latLng(segment.Center.Lat, segment.Center.Lng);
	                var radius = L.latLng(segment.Radius.Lat, segment.Radius.Lng);
	                //MapToolbar.addPoint(center, MapToolbar.currentFeature);
	                //MapToolbar.addPoint(radius, MapToolbar.currentFeature);
	                MapToolbar.initFeature('circle', segment.Id, [center, radius]);
	                break;
	        }
	    }
	}

	function addFencePath(fence) {
        if (fence == null) {
            return;
        }
        var addToMap = !isItemIncluded(tracking.user.displayPreferences.hiddenFences, fence.Id);
	    var fenceColor = '#' + getFenceColor(fence);
	    for (var j = 0; j < fence.Segments.length; j++) {
	        var segment = fence.Segments[j];
	        switch (segment.Type) {
	            case 0: // line
	                var path = [];
	                // point order is important
	                for (var k = 0; k < segment.Points.length; k++) {
	                    var point = segment.Points[k];
	                    var latlng = L.latLng(point.Lat, point.Lng);
	                    path.push(latlng);
	                }
	                var line = L.polyline(path, {
						color: fenceColor,
						weight: 4
                    });
                    line.data = {
                        fenceId: null
                    };
	                line.on('mouseover', function (e) {
	                	//var evt = e.originalEvent;
	                	//evt.preventDefault = true;
	                	//$j('#fence-tooltip').tooltip('option', 'position', { of: evt, my: 'left+15 bottom+15', at: 'right center', }).tooltip('option', 'content', fence.Name).tooltip('open');
                        showFenceTooltip(fence);
	                });
	                line.on('mouseout', function (e) {
	                	//$j('#fence-tooltip').tooltip('close');
                        hideMouseTooltip();
	                });
	                line.on('click', function (e) {
						//tracking.map.fire('click', e);
	                	//fenceClick(fence, e.latlng);
                        L.DomEvent.stopPropagation(e);
                        markerClick(line, 'fence', e.latlng, true);
	                });
                    if (addToMap) {
                        addItemToMap(line);
	                }
                    line.data.fenceId = fence.Id;
	                tracking.data.fenceMarkers.push(line);
	                break;
	            case 1: // shape
	                var path = [];
	                for (var k = 0; k < segment.Points.length - 1; k++) { // ignore the last point for a polygon as it's the first
	                    var point = segment.Points[k];
	                    var latlng = L.latLng(point.Lat, point.Lng);
	                    path.push(latlng);
	                }
	                var shape = L.polygon(path, {
						weight: 2,
						color: fenceColor,
						opacity: 0.35,
						fillOpacity: 0.15,
						dashArray: '4'
                    });
                    shape.data = {
                        fenceId: null
                    };
                    if (addToMap) {
                        addItemToMap(shape);
                    }
	                shape.on('mouseover', function (e) {
	                	//var evt = e.originalEvent;
						//evt.preventDefault = true;
	                	//$j('#fence-tooltip').tooltip('option', 'position', { of: evt, my: 'left+15 bottom+15', at: 'right center', }).tooltip('option', 'content', fence.Name).tooltip('open');
                        showFenceTooltip(fence);
	                });
	                shape.on('mouseout', function (e) {
	                	//$j('#fence-tooltip').tooltip('close');
                        hideMouseTooltip();
	                });
                    //shape.on('mousemove', function (e) {
                    //    console.log('shape move');
                    //});
	                shape.on('click', function (e) {
	                    //fenceClick(fence, e.latlng);
                        L.DomEvent.stopPropagation(e);
                        markerClick(shape, 'fence', e.latlng, true);
                        //markerClick(shape);
	                });

                    shape.data.fenceId = fence.Id;
	                tracking.data.fenceMarkers.push(shape);
	                break;
	            case 2: // circle
	                var center = L.latLng(segment.Center.Lat, segment.Center.Lng);
	                var radius = L.latLng(segment.Radius.Lat, segment.Radius.Lng);
					var radiusMeters = center.distanceTo(radius);
	                var circle = L.circle(center, radiusMeters, {
	                	weight: 2,
						color: fenceColor,
						dashArray: '4',
						opacity: 0.35,
                        fillOpacity: 0.15,
                        interactive: true
                    });
                    circle.data = {
                        fenceId: null
                    };
	                circle.on('mouseover', function (e) {
	              //  	var evt = e.originalEvent;
	              //  	evt.preventDefault = true;
	            		//$j('#fence-tooltip').tooltip('option', 'position', { of: evt, my: 'left+15 bottom+15', at: 'right center', }).tooltip('option', 'content', fence.Name).tooltip('open');
                        showFenceTooltip(fence);
	                });
	                circle.on('mouseout', function (e) {
	                	//$j('#fence-tooltip').tooltip('close');
                        hideMouseTooltip();
	                });
	                circle.on('click', function (e) {
	                	//findAssetIdsInGeofence(fence);
	                	//fenceClick(fence, e.latlng);
                        //e.originalEvent.stopPropagation();
                        L.DomEvent.stopPropagation(e);
                        markerClick(circle, 'fence', e.latlng, true);
	                });
                    if (addToMap) {
                        addItemToMap(circle);
                    }
                    circle.data.fenceId = fence.Id;
	                tracking.data.fenceMarkers.push(circle);
	                break;
	        }
	    }
	}

    function getFenceBounds(fenceId) {
        var fenceBounds = L.latLngBounds();
        var fenceMarkers = _.filter(tracking.data.fenceMarkers, function (item) { return item.data.fenceId === fenceId; });
        _.each(fenceMarkers, function (poly) {
            fenceBounds.extend(poly.getBounds());
        });

        if (fenceBounds.isValid()) {
            return fenceBounds;
        }
        return null;
    }

	function centerOnFence(fenceId) {
		// calculate bounds from all fence segments
		// center on first fence segment shape (for infobox) instead of center of all
		var fencePolys = [];
		var fenceBounds = L.latLngBounds();
	    var singlePolygonCenter = null;
        var fenceMarkers = _.filter(tracking.data.fenceMarkers, function(item) { return item.data.fenceId === fenceId;});
        _.each(fenceMarkers, function (poly) {
            fencePolys.push(poly);
            fenceBounds.extend(poly.getBounds());

            if (singlePolygonCenter == null) {
                singlePolygonCenter = poly.getBounds().getCenter();
            }
        });

	    if (fenceBounds.isValid()) {
            tracking.map.fitBounds(fenceBounds, { padding: [10, 10] });
		}
	    //return singlePolygonCenter;
        return { markers: fencePolys, center: singlePolygonCenter};
	}

	function addFencePaths() {
	    if (tracking.data.fences == null) {
	        return;
        }
        _.each(tracking.data.fences, function (fence) {
            addFencePath(fence);
        });
	}

	function removeFencePaths(fenceId) {
	    if (tracking.data.fences == null) {
	        return;
        }
	    var fence = findFenceById(fenceId);
	    if (fence == null) {
	        return;
        }
	    var after = [];
	    for (var i = 0; i < tracking.data.fenceMarkers.length; i++) {
	        var poly = tracking.data.fenceMarkers[i];
            var polyFenceId = poly.data.fenceId;
	        if (polyFenceId == fenceId) {
	        	// remove poly
	        	removeItemFromMap(poly);
	            poly = null;
	        } else {
	            after.push(poly);
	        }
	    }
	    tracking.data.fenceMarkers = after;
	}

	function updateLoadingMarkerProgress(current, total) {
	    var percentage = Math.ceil((current / total) * 100);
    }

    function processSharedViewData(sharedViewId, data, clusterMarkers) {
        //console.log('processSharedViewData ' + sharedViewId + ', cluster? ' + clusterMarkers);
        var asset = findAssetById(data.Id);
        if (asset === null) {
            return;
        }
        _.each(data.Positions, function (position) {
            // we probably shouldn't check data.positions
            if (tracking.data.sharedView.positionsById[position.Id] === undefined) {
                tracking.data.sharedView.positionsById[position.Id] = normalizeAssetData(asset.Id, 'position', position);
            }
            
            if (tracking.data.sharedView.normalizedPositionsByAssetId[asset.Id] === undefined) {
                tracking.data.sharedView.normalizedPositionsByAssetId[asset.Id] = [];
            }
            tracking.data.sharedView.normalizedPositionsByAssetId[asset.Id].push(tracking.data.sharedView.positionsById[position.Id]);
            tracking.data.sharedView.normalizedPositions.push(tracking.data.sharedView.positionsById[position.Id]);
        });

        tracking.data.sharedView.positionsByAssetId[asset.Id] = data;

        if (data.MessageCounts !== null) {
            tracking.data.sharedView.messageCountsByAssetId[asset.Id] = data.MessageCounts;
        }

        var visiblePositions = _.filter(data.Positions, function (item) { return !item.IsHidden; });
        var lastVisiblePositionId = null;
        var firstVisiblePositionId = null;
        if (visiblePositions.length > 0) {
            lastVisiblePositionId = visiblePositions[0].Id;
            firstVisiblePositionId = visiblePositions[visiblePositions.length - 1].Id;
        }
        //console.log('Asset ' + asset.Id + ' visible positions: ' + visiblePositions.length);
        if (data.Positions.length > 0) {
            if (clusterMarkers && tracking.data.sharedView.markerClustersByAssetId[asset.Id] === undefined) {
                tracking.data.sharedView.markerClustersByAssetId[asset.Id] = createMarkerCluster(asset, null, sharedViewId);
            }

            var alpha = 255;
            var alphaIncrement = 185 / visiblePositions.length;
            // positions are sorted from newest->last
            var totalAssetPositions = visiblePositions.length;
            var markersForCluster = [];
            for (var j = 0; j < data.Positions.length; j++) {
                // add position to map and position filters
                var position = data.Positions[j];
                var point = L.latLng(position.Lat, position.Lng);
                var isFirst = (totalAssetPositions > 1 && position.Id === firstVisiblePositionId);
                var isLast = (totalAssetPositions > 1 && position.Id === lastVisiblePositionId);
                var marker = addPositionMarkerToPoint(point, false, position, asset, alpha, clusterMarkers, isFirst, isLast, tracking.dataGroups.SHARED_VIEW_HISTORY, null, sharedViewId);
                if (!position.IsHidden) {
                    markersForCluster.push(marker);
                }
                alpha -= alphaIncrement;
            }

            tracking.data.sharedView.markersByAssetId = _.groupBy(tracking.data.sharedView.markers, function (marker) { return marker.data.assetId; });

            if (clusterMarkers && tracking.data.sharedView.markerClustersByAssetId[asset.Id] !== undefined) {
                tracking.data.sharedView.markerClustersByAssetId[asset.Id].clearLayers();
                tracking.data.sharedView.markerClustersByAssetId[asset.Id].addLayers(markersForCluster);
                addItemToMap(tracking.data.sharedView.markerClustersByAssetId[asset.Id], null, tracking.viewModes.SHARED_VIEW);
            }

            createPositionLinesForAsset(asset, visiblePositions, tracking.viewModes.SHARED_VIEW);
        }
    }

    function processTripData(journey, trip, data) {
        var asset = findAssetById(journey.AssetId);
        if (asset === null) {
            return;
        }

        _.each(data.Positions, function (position) {
            if (tracking.data.positionsById[position.Id] === undefined) {
                tracking.data.positionsById[position.Id] = normalizeAssetData(asset.Id, 'position', position);
            }
            //tracking.data.trips.normalizedPositions.push(tracking.data.positionsById[position.Id]);
            if (tracking.data.trips.normalizedPositionsByTripId[trip.Id] === undefined) {
                tracking.data.trips.normalizedPositionsByTripId[trip.Id] = [];
            }
            tracking.data.trips.normalizedPositionsByTripId[trip.Id].push(tracking.data.positionsById[position.Id]);
        });

        if (data.ResultsForSearch) {
            tracking.data.trips.tripIdsWithResults[trip.Id] = true;
        }

       // tracking.data.trips.positions.push(data); // needed?
        tracking.data.trips.positionsByTripId[trip.Id] = data;

        if (data.MessageCounts !== null) {
            //tracking.data.trips.messageCounts.push(data.MessageCounts); // needed?
            tracking.data.trips.messageCountsByTripId[trip.Id] = data.MessageCounts;
        }

        var clusterTripMarkers = tracking.preferences.PREFERENCE_GROUP_POSITIONS && trip.IncludeAllPositions;
        var visiblePositions = _.filter(data.Positions, function(item) { return !item.IsHidden; });
        var lastVisiblePositionId = null;
        var firstVisiblePositionId = null;
        if (visiblePositions.length > 0) {
            lastVisiblePositionId = visiblePositions[0].Id;
            firstVisiblePositionId = visiblePositions[visiblePositions.length - 1].Id;
        }
        if (visiblePositions.length > 0) {
            if (clusterTripMarkers && tracking.data.trips.markerClustersByTripId[trip.Id] === undefined) {
                tracking.data.trips.markerClustersByTripId[trip.Id] = createMarkerCluster(asset, trip);
            }

            var alpha = 255;
            var alphaIncrement = 185 / visiblePositions.length;
            // positions are sorted from newest->last
            var totalTripPositions = visiblePositions.length;
            var clusterMarkers = [];
            for (var j = 0; j < totalTripPositions; j++) {
                // add position to map and position filters
                var position = visiblePositions[j];
                var point = L.latLng(position.Lat, position.Lng);
                var isFirst = (totalTripPositions > 1 && position.Id === firstVisiblePositionId);
                var isLast = (totalTripPositions > 1 && position.Id === lastVisiblePositionId);
                if (!position.IsHidden && (trip.IncludeAllPositions || isFirst || isLast)) {
                    var marker = addPositionMarkerToPoint(point, false, position, asset, alpha, clusterTripMarkers, isFirst, isLast, tracking.dataGroups.JOURNEY_HISTORY, trip);
                    clusterMarkers.push(marker);
                    alpha -= alphaIncrement;
                }
            }

            tracking.data.trips.markersByTripId = _.groupBy(tracking.data.trips.markers, function (marker) { return marker.data.tripId; });

            if (clusterTripMarkers && tracking.data.trips.markerClustersByTripId[trip.Id] !== undefined) {
                tracking.data.trips.markerClustersByTripId[trip.Id].clearLayers();
                tracking.data.trips.markerClustersByTripId[trip.Id].addLayers(clusterMarkers);
                addItemToMap(tracking.data.trips.markerClustersByTripId[trip.Id]);
            }

            createPositionLinesForTrip(trip, asset, visiblePositions);
        }
    }

    function processAssetHistoryPositionsResult(assetResult) {
        // add the results to the bounds
        var asset = findAssetById(assetResult.Id);
        tracking.data.history.positions.push(assetResult);
        tracking.data.history.positionsByAssetId[asset.Id] = assetResult;

        _.each(assetResult.Positions, function(position) {
            if (tracking.data.positionsById[position.Id] === undefined) {
                tracking.data.positionsById[position.Id] = normalizeAssetData(asset.Id, 'position', position);
            }
            tracking.data.history.normalizedPositions.push(tracking.data.positionsById[position.Id]);
            if (tracking.data.history.normalizedPositionsByAssetId[asset.Id] === undefined) {
                tracking.data.history.normalizedPositionsByAssetId[asset.Id] = [];
            }
            tracking.data.history.normalizedPositionsByAssetId[asset.Id].push(tracking.data.positionsById[position.Id]);
        });

        if (assetResult.MessageCounts !== null) {
            tracking.data.history.messageCounts.push(assetResult.MessageCounts);
            tracking.data.history.messageCountsByAssetId[asset.Id] = assetResult.MessageCounts;
        }

        if (assetResult.ResultsForSearch) {
            tracking.data.history.assetIdsWithResults[asset.Id] = true;
        }

        var assetPositions = assetResult.Positions;
        var assetVisiblePositions = _.filter(assetResult.Positions, function(item) { return !item.IsHidden; });
        var lastVisiblePositionId = null;
        var firstVisiblePositionId = null;
        if (assetVisiblePositions.length > 0) {
            lastVisiblePositionId = assetVisiblePositions[0].Id;
            firstVisiblePositionId = assetVisiblePositions[assetVisiblePositions.length - 1].Id;
        }
        console.log('visible positions: ' + lastVisiblePositionId + ', ' + firstVisiblePositionId);
        if (assetPositions.length > 0) {
            if (tracking.preferences.PREFERENCE_GROUP_POSITIONS && tracking.data.history.markerClustersByAssetId[asset.Id] === undefined) {
                tracking.data.history.markerClustersByAssetId[asset.Id] = createMarkerCluster(asset, null);
            }

            var alpha = 255;
            var alphaIncrement = 185 / assetVisiblePositions.length;
            // positions are sorted from newest->last
            var totalAssetVisiblePositions = assetVisiblePositions.length;
            var clusterMarkers = [];
            for (var j = 0; j < assetPositions.length; j++) {
                // add position to map and position filters
                var position = assetPositions[j];

                if (tracking.data.positionsById[position.Id] === undefined) {
                    tracking.data.positionsById[position.Id] = normalizeAssetData(asset.Id, 'position', position);
                }

                if (tracking.data.history.latestPosition == null
                    || tracking.data.history.latestPosition.Epoch < position.Epoch) {
                    tracking.data.history.latestPosition = position;
                }

                var point = L.latLng(position.Lat, position.Lng);
                var isFirst = (totalAssetVisiblePositions > 1 && firstVisiblePositionId === position.Id);
                var isLast = (totalAssetVisiblePositions > 1 && lastVisiblePositionId === position.Id);
                var marker = addPositionMarkerToPoint(point, false, position, asset, alpha, tracking.preferences.PREFERENCE_GROUP_POSITIONS, isFirst, isLast, tracking.mapModes.HISTORY);
                if (!position.IsHidden) {
                    clusterMarkers.push(marker);
                }
                //markersAdded++;
                //updateLoadingMarkerProgress(markersAdded, totalMarkers);
                alpha -= alphaIncrement;
            }

            tracking.data.history.markersByAssetId = _.groupBy(tracking.data.history.markers, function (marker) { return marker.data.assetId; });
            tracking.data.history.markersByPositionId = _.keyBy(tracking.data.history.markers, function(marker) { return marker.data.location.Id; });

            if (tracking.preferences.PREFERENCE_GROUP_POSITIONS && tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined) {
                tracking.data.history.markerClustersByAssetId[asset.Id].clearLayers();
                tracking.data.history.markerClustersByAssetId[asset.Id].addLayers(clusterMarkers);
                if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
                    addItemToMap(tracking.data.history.markerClustersByAssetId[asset.Id]);
                }
            }

            createPositionLinesForAsset(asset, assetVisiblePositions, tracking.viewModes.NORMAL);
        }
    }

    function getPositionForEvent(item) {
        if (item.Position !== undefined && item.Position !== null) {
            // live events have .Position defined, why don't historic? TODO remove .Position from live event
            return item.Position;
        }
        if (tracking.data.positionsById[item.PositionId] !== undefined) {
            return tracking.data.positionsById[item.PositionId].Position;
        }
        return undefined;
    }

    function mapPositionForEvent(item, asset) {
        var eventPosition = {
            id: null,
            latLng: null,
            address: null
        };

        if (asset === undefined || asset === null) {
            asset = findAssetById(item.AssetId);
        }
        var position = getPositionForEvent(item);
        if (position !== undefined && position !== null) {
            eventPosition.id = position.Id;
            if (!asset.HideAddress) {
                eventPosition.address = position.Address;
            }
            eventPosition.latLng = convertToLatLngPreference(position.DisplayLat, position.DisplayLng, position.Grid);
        }
        return eventPosition;
    }

	function getPositionLinkForEvent(item) {
	    var asset = findAssetById(item.AssetId);
	    var eventposition = null;
        var position = getPositionForEvent(item);
        if (position !== undefined) {
            eventposition = position.Address;
            if (eventposition == null || (eventposition.trim() == '') || (asset.HideAddress)) {
                eventposition = convertToLatLngPreference(position.DisplayLat, position.DisplayLng, position.Grid);
            }
            eventposition = el('a.location', { href: '#', dataset: { marker: position.Id, time: item.Time, asset: item.AssetId } }, eventposition);
	    }
	    return eventposition;
    }

    function createIconForAssetEvent(asset, eventType) {
        var text = _.find(tracking.data.eventTypes, { Id: eventType }).Text;
        switch (eventType) {
            case 21:    // ignition on
                return createAssetStatusIconNode(text, 'ignition-on');
            case 22:    // ignition off
                return createAssetStatusIconNode(text, 'ignition-off');
            case 47:    // moving start
            case 1:     // start
                return createAssetStatusIconNode(text, 'moving');
            case 48:    // moving stop
            case 2:     // stop
                return createAssetStatusIconNode(text, 'stationary');
            case 32: // 98 // idling
                return createAssetStatusIconNode(text, 'idling');
            case 5: // 6
                return createAssetStatusIconNode(text, 'speeding');
            case 3:     // power on
                return createAssetStatusIconNode(text, 'power-on');
            case 4:     // power off
                return createAssetStatusIconNode(text, 'power-off');
            case 45: // 46  // towing start
                return createAssetStatusIconNode(text, 'towing');
            case 20: // 19  // on main power
                return createAssetStatusIconNode(text, 'backup-power');
            case 35: // 129 // antenna cut start
                return createAssetStatusIconNode(text, 'antenna-cut');
            case 36: // 37  // gps jamming start
                return createAssetStatusIconNode(text, 'gps-jammed');
            case 38: // 39  // cell jamming start
                return createAssetStatusIconNode(text, 'cell-jammed');
            case 133:       // low battery
                return createAssetStatusIconNode(text, 'low-battery');
            case 14:        // alert triggered
                return createAssetStatusIconNode(text, 'alert');
            case 16:    // alert no longer triggered
                return createAssetStatusIconNode(text, 'alert-cancel');
            case 15:        // check-in
                return createAssetStatusIconNode(text, 'check-in');
            default:
                return null;
        }
    }

    function mapActivityItemToListItem(item) {
        if (item.Chat !== undefined) {
            return mapMessageToListItem(item);
        } else if (item.Message !== undefined) {
            return mapMessageToListItem(item);
        } else if (item.Event !== undefined) {
            return mapEventToListItem(item);
        } else if (item.Position !== undefined) {
            return mapPositionToListItem(item);
        }
    }

    function mapPositionDetailsForListItem(asset, item) {
        if (item == undefined || item === null) {
            return undefined;
        }
        var address = null;
        if (!asset.HideAddress) {
            address = item.Address;
        }
        var latLng = convertToLatLngPreference(item.DisplayLat, item.DisplayLng, item.Grid);

        var course = undefined;
        if (!asset.HideCourse) {
            course = item.Course;
        }

        var status = getStatusTextForLocation(item);

        var altitude = null;
        if (!asset.HideAltitude && item.Altitude !== undefined && item.Altitude !== null) {
            altitude = convertAltitudeToPreference(item.Altitude);
        }

        var fences = null;
        if (item.InsideFences !== undefined && item.InsideFences !== null) {
            var fenceNames = [];
            _.each(item.InsideFences, function (fenceId) {
                var fence = findFenceById(fenceId);
                if (fence !== null) {
                    fenceNames.push(fence.Name);
                }
            });
            fences = fenceNames.join(', ');
        }

        var speed = null;
        if (!asset.HideSpeed) {
            speed = convertSpeedToPreference(item.Speed);
        }
        var accuracy = null;
        if (!asset.HideAccuracy) {
            accuracy = item.Accuracy;
        }

        var odometer = null;
        if (item.Odometer !== undefined && item.Odometer !== null) {
            odometer = convertFromMetresToUserDistancePreference(item.Odometer);
        }

        // core properties
        var mapped = {
            Id: item.Id,
            LatLng: latLng,
            Address: address,
            Time: item.Time, // duplicated for consistency
            IsHidden: item.IsHidden,
            // extra details
            Source: item.Source,
            Odometer: odometer,
            Heading: course,
            Status: status,
            Altitude: altitude,
            InsideFences: fences,
            Speed: speed,
            IsSpeedEstimated: item.IsEst,
            IsTimeAccurate: item.IsAcc,
            Accuracy: accuracy
        };
        return mapped;
    }

    function mapPositionToListItem(positionItem) {
        if (positionItem.Position === undefined || positionItem.Position === null) {
            return undefined;
        }

        var dataSource = (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW ? tracking.data.sharedView : tracking.data);

        var existing = dataSource.activityById['p-' + positionItem.Position.Id];
        if (existing !== undefined) {
            return existing;
        }

        var asset = findAssetById(positionItem.AssetId);
        var item = positionItem.Position;
        var position = mapPositionDetailsForListItem(asset, item);
        var iconUrl = createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false);

        //var address = null;
        //if (!asset.HideAddress) {
        //    address = item.Address;
        //}

        
        //var latLng = convertToLatLngPreference(item.DisplayLat, item.DisplayLng, item.Grid);

        var eventTypes = _.uniq(_.map(item.Events, 'TypeName')).join(', ');
        var mapped = {
            Id: 'p-' + item.Id,
            PositionId: item.Id,
            AssetId: asset.Id,
            IconUrl: iconUrl,
            Epoch: item.Epoch,
            EpochGroup: item.Epoch + '-' + asset.Id,
            IsHidden: item.IsHidden,
            // to index
            AssetName: asset.Name,
            Time: item.Time,
            EventTypes: eventTypes,
            Position: position
        };
        dataSource.activityById['p-' + positionItem.Position.Id] = mapped;
        return mapped;
    }

    function mapEventToListItem(eventItem) {
        if (eventItem.Event === undefined || eventItem.Event === null) {
            return undefined;
        }

        var dataSource = (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW ? tracking.data.sharedView : tracking.data);

        var existing = dataSource.activityById['e-' + eventItem.Event.Id];
        if (existing !== undefined) {
            return existing;
        }

        var asset = findAssetById(eventItem.AssetId);
        var position = eventItem.Position;
        var item = eventItem.Event;

        var svgIcon = createIconForAssetEvent(asset, item.Type);
        var iconUrl = createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false, item.Type);
        var eventIcon = el('div.event-icon', { style: { backgroundImage: 'url(' + iconUrl + ')' } });
        var detailsElement = formattedTextToDiv(item.Details);
        switch (item.Type) {
            case 127:   // garmin form submitted
                if (item.Details != null) {
                    var formDetails = item.Details.split('|');
                    var formId = formDetails[0];
                    var formName = tracking.strings.VIEW;
                    if (formDetails.length > 1) {
                        formName = formDetails[1];
                    }
                    detailsElement = el('button.ViewGarminSubmission.command.details.btn.btn-sm.btn-secondary', { dataset: { id: formId, assetId: asset.Id} }, formName);
                }
                break;
            case 270: // beacon read
                if (item.Details != null) {
                    detailsElement = null;
                    var detailsItems = [];
                    var beacons = item.Details.split("\n");
                    for (var k = 0; k < beacons.length; k++) {
                        var beacon = beacons[k].split('|');
                        if (beacon.length > 1) {
                            var beaconUniqueKey = beacon[0];
                            var beaconRSSI = beacon[1];
                            var beaconTxPower = beacon[2];
                            if (beaconUniqueKey == null) {
                                continue;
                            }
                            var beaconPlace = findPlaceByUniqueKey(beaconUniqueKey);
                            if (beaconPlace != null) {
                                detailsItems.push(el('a.beacon-place', { href: '#', dataset: { placeId: beaconPlace.Id } }, beaconPlace.Name));
                                detailsItems.push(text(' @ ' + beaconRSSI + ' RSSI'));
                            } else {
                                detailsItems.push(text(beaconUniqueKey + ' @ ' + beaconRSSI + ' RSSI'));
                            }
                            if (beaconTxPower != '') {
                                detailsItems.push(text(', ' + beaconTxPower + ' Tx'));
                            }
                            detailsItems.push(el('br'));
                        }
                    }
                    if (detailsItems.length > 0) {
                        detailsElement = el('div', detailsItems);
                    }
                }
                break;
            case 272:
            case 273:
            case 274:
                // driver fatigue
                if (item.Details != null) {
                    detailsElement = el('button.ViewPhoto.command.details.btn.btn-sm.btn-secondary', { title: tracking.strings.VIEW_PHOTO, dataset: { photo: item.Details } }, tracking.strings.VIEW_PHOTO);
                }
                break;
            case 275:
                if (item.Details != null) {
                    var parts = item.Details.split('|||');
                    detailsElement = el('button.ViewPhoto.command.details.btn.btn-sm.btn-secondary', { title: tracking.strings.VIEW_PHOTO, dataset: { photo: parts[1] } }, tracking.strings.VIEW_PHOTO);
                }
                break;
        }

        var fullName = item.TypeName;
        if (item.Alert !== undefined && item.Alert !== null) {
            var alertName = item.Alert.Type;
            if (item.Alert.Name !== null) {
                alertName = item.Alert.Name + ' [ ' + item.Alert.Type + ' ]';
            }
            fullName += ', ' + alertName;
        }

        var positionId = undefined;
        position = mapPositionDetailsForListItem(asset, position);
        if (position !== undefined) {
            positionId = position.Id;
        }

        var mapped = {
            Id: 'e-' + item.Id,
            EventId: item.Id,
            PositionId: positionId,
            AssetId: asset.Id,
            Icon: eventIcon,        // unused
            IconUrl: iconUrl,
            SvgIcon: svgIcon,
            IsAccurate: item.IsAcc,
            Type: item.Type,
            Epoch: item.Epoch,
            EpochGroup: item.Epoch + '-' + asset.Id,

            // to index
            AssetName: asset.Name,
            EventName: fullName,
            Position: position,
            Details: detailsElement,
            Time: item.Time,
            Alert: item.Alert
        };
        dataSource.activityById['e-' + eventItem.Event.Id] = mapped;
        return mapped;
    }

    function mapMessageToListItem(item) {
        if ((item.Message === undefined || item.Message === null) && (item.Chat === undefined || item.Chat === null)) {
            return undefined;
        }
        var dataSource = (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW ? tracking.data.sharedView : tracking.data);
        var assetId = item.AssetId;
        var position = item.Position;
        var isChat = item.Message === undefined;
        var item = !isChat ? item.Message : item.Chat;

        var existing = dataSource.activityById['m-' + item.Id];
        if (existing !== undefined) {
            return existing;
        }

        var asset = findAssetById(assetId);
        var iconUrl = createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false);
        var positionId = undefined;
        position = mapPositionDetailsForListItem(asset, position);
        if (position !== undefined) {
            positionId = position.Id;
        }

        var mapped = {
            Id: 'm-' + item.Id,
            MessageId: item.Id,
            PositionId: positionId,
            AssetId: asset.Id,
            IsFromMobile: item.IsFromMobile,
            Epoch: item.CreatedEpoch,
            EpochGroup: item.CreatedEpoch + '-' + asset.Id,
            IsError: item.IsError,
            IsAcknowledged: item.IsAcknowledged,
            IconUrl: iconUrl,
            IsChat: isChat,
            // to index
            AssetName: asset.Name,
            Method: item.IsFromMobile ? tracking.strings.FROM_MOBILE : tracking.strings.TO_MOBILE,
            EventName: item.Type,
            RawText: item.RawText,
            Source: item.Source,
            Time: item.CreatedOn,
            Response: item.Response,
            Details: text(item.Text),
            Status: (!item.IsError && !item.IsSent) ? tracking.strings.PENDING : item.Status,
            Position: position //Position.Address, Position.LatLng
        };
        dataSource.activityById['m-' + item.Id] = mapped;
        return mapped;
    }

    function filterPositionsAlreadyIncluded(positions, others) {
        var positionIds = _.filter(_.map(others, function (item) {
            if (item.Event !== undefined && item.Position !== undefined) {
                return item.Position.Id;
            } else if (item.Message !== undefined && item.Position !== undefined) {
                return item.Position.Id;
            } else if (item.Chat !== undefined && item.Position !== undefined) {
                return item.Position.Id;
            }
        }), function (item) { return item !== undefined; });
        var positionIdLookup = {};
        _.each(positionIds, function (id) {
            positionIdLookup[id] = true;
        });
        return _.filter(positions, function (item) { return positionIdLookup[item.Position.Id] === undefined; });
    }

    function getDisplayFilterForEventType(type) {
        switch (type) {
            case 'activity':
                return function (item) {
                    // basically a duplicate of the below filters, but I don't see a way around it
                    if (item.Chat !== undefined) {
                        var isChatDisabled = tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging;
                        return !isChatDisabled && item.Chat !== undefined;
                    } else if (item.Event !== undefined) {
                        if (item.Event.Hide) {
                            return false;
                        }
                        var hideAlertTriggeredEvents = (tracking.options.hideAlertTriggeredEvents || tracking.user.isAnonymous);
                        var hideEmergencyEvents = (tracking.options.hideEmergencyEvents || tracking.user.isAnonymous);
                        var isAlertEvent = tracking.data.EVENTS_ALERT.indexOf(item.Event.Type) !== -1;
                        if (isAlertEvent && hideAlertTriggeredEvents) {
                            return false;
                        }
                        var isEmergencyEvent = tracking.data.EVENTS_EMERGENCY.indexOf(item.Event.Type) !== -1;
                        if (isEmergencyEvent && hideEmergencyEvents) {
                            return false;
                        }
                        if (item.Event.Type === 268) { // SL Summary
                            return false;
                        }
                        if (tracking.data.EVENTS_TEXT_MESSAGE.indexOf(item.Event.Type) !== -1) {
                            return false;
                        }
                    }
                    return true;
                }
            case 'chat':
                return function (item) { 
                    var isChatDisabled = tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging;
                    return !isChatDisabled && item.Chat !== undefined;
                };
            case 'messages':
                return function (item) { return item.Chat === undefined && item.Message !== undefined; };
            case 'positions':
                return function (item) { return item.Position !== undefined; };
            case 'status':
                return function (item) { return !item.Event.Hide && tracking.data.EVENTS_STATUS.indexOf(item.Event.Type) !== -1; };
            case 'alerts':
                return function (item) {
                    if (item.Event.Hide) {
                        return false;
                    }
                    var hideAlertTriggeredEvents = (tracking.options.hideAlertTriggeredEvents || tracking.user.isAnonymous);
                    var hideEmergencyEvents = (tracking.options.hideEmergencyEvents || tracking.user.isAnonymous);
                    var isAlertEvent = tracking.data.EVENTS_ALERT.indexOf(item.Event.Type) !== -1;
                    if (isAlertEvent && hideAlertTriggeredEvents) {
                        return false;
                    }
                    var isEmergencyEvent = tracking.data.EVENTS_EMERGENCY.indexOf(item.Event.Type) !== -1;
                    if (isEmergencyEvent && hideEmergencyEvents) {
                        return false;
                    }
                    return isAlertEvent || isEmergencyEvent;
                };
            case 'events':
            default:
                return function (item) { 
                    return !item.Event.Hide 
                        && item.Event.Type !== 268 // SL Summary
                        && tracking.data.EVENTS_STATUS.indexOf(item.Event.Type) === -1 
                        && tracking.data.EVENTS_ALERT.indexOf(item.Event.Type) === -1 
                        && tracking.data.EVENTS_EMERGENCY.indexOf(item.Event.Type) === -1 
                        && tracking.data.EVENTS_TEXT_MESSAGE.indexOf(item.Event.Type) === -1; // because it will be a chat item?
                };
        }
    }

    function mapAssetEventToListing(item) {
        // unused
        // TODO move this out of rendering
        if (tracking.options.hideAlertTriggeredEvents
            || item.Hide
            || tracking.user.isAnonymous) {
            if (tracking.data.EVENTS_ALERT.indexOf(item.Type) !== -1) {
                return null;
            }
        }
        if (tracking.options.hideEmergencyEvents
            || tracking.user.isAnonymous) {
            if (tracking.data.EVENTS_EMERGENCY.indexOf(item.Type) !== -1) {
                return null;
            }
        }

        if (item.Type === 268) { // SL Summary
            return null;
        }

        var asset = findAssetById(item.AssetId);
        var svgIcon = createIconForAssetEvent(asset, item.Type);
        var eventIcon = '<div class="event-icon" style="background-image: url(' + createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false, item.Type) + ');"></div>';
        var details = (item.Details != null) ? htmlEscape(item.Details).replace(/\r?\n/g, '<br />') : '';
        switch (item.Type) {
            case 127:   // garmin form submitted
                if (item.Details != null) {
                    var formDetails = item.Details.split('|');
                    var formId = formDetails[0];
                    var formName = tracking.strings.VIEW;
                    if (formDetails.length > 1) {
                        formName = formDetails[1];
                    }
                    details = '<button class="ViewGarminSubmission command details btn btn-sm btn-secondary" data-id="' + formId + '" data-asset-id="' + asset.Id + '">' + formName + '</button>';
                }
                break;
            case 270: // beacon read
                if (item.Details != null) {
                    details = '';
                    var beacons = item.Details.split("\n");
                    for (var k = 0; k < beacons.length; k++) {
                        var beacon = beacons[k].split('|');
                        if (beacon.length > 1) {
                            var beaconUniqueKey = beacon[0];
                            var beaconRSSI = beacon[1];
                            var beaconTxPower = beacon[2];
                            if (beaconUniqueKey == null) {
                                continue;
                            }
                            var beaconPlace = findPlaceByUniqueKey(beaconUniqueKey);
                            if (beaconPlace != null) {
                                details += '<a class="beacon-place" data-place-id="' + beaconPlace.Id + '" href="#">' + beaconPlace.Name + '</a> @ ' + beaconRSSI + ' RSSI';
                            } else {
                                details += beaconUniqueKey + ' @ ' + beaconRSSI + ' RSSI';
                            }
                            if (beaconTxPower != '') {
                                details += ', ' + beaconTxPower + ' Tx';
                            }
                            details += '<br />';
                        }
                    }
                }
                break;
            case 272:
            case 273:
            case 274:
                // driver fatigue
                if (item.Details != null) {
                    details = '<button class="ViewPhoto command details btn btn-sm btn-secondary" data-photo="' + item.Details + '" title="' + tracking.strings.VIEW_PHOTO + '">' + tracking.strings.VIEW_PHOTO + '</button>';
                }
                break;
            case 275:
                if (item.Details != null) {
                    var parts = item.Details.split('|||');
                    details = '<button class="ViewPhoto command details btn btn-sm btn-secondary" data-photo="' + parts[1] + '" title="' + tracking.strings.VIEW_PHOTO + '">' + tracking.strings.VIEW_PHOTO + '</button>';
                }
                break;
        }

        var fullName = item.TypeName;
        if (item.Alert !== undefined && item.Alert !== null) {
            var alertName = item.Alert.Type;
            if (item.Alert.Name !== null) {
                alertName = item.Alert.Name + ' [ ' + item.Alert.Type + ' ]';
            }
            fullName += ', ' + alertName;
        }

        return {
            id: item.Id,
            icon: eventIcon,
            svgIcon: svgIcon,
            name: item.TypeName,
            fullName: fullName,
            position: mapPositionForEvent(item, asset),
            details: details,
            time: item.Time,
            epoch: item.Epoch,
            type: item.Type,
            isAccurate: item.IsAcc,
            alert: item.Alert,
            assetId: item.AssetId
        };
    }

	function mapAssetEventToTableRow(item, isAlert) {
        // unused
	    var asset = findAssetById(item.AssetId);
        var itemData = null;

        if (isAlert) {
            var eventicon = '';
            if (asset != null) {
                eventicon = '<div class="event-icon" style="background-image: url(' + createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false, item.Type) + ');"></div>';
            }
            var eventposition = getPositionLinkForEvent(item);

            if (item.Alert.Hide) {
                return;
            }
	        var type = item.Alert.Type;
	        if (item.Alert.Name != null) {
	            type = item.Alert.Name + ': ' + type;
	        }
	        itemData = [
                '',
                item.Alert.Priority,
                (asset != null) ? asset.Name : '',
                type,
                (item.Alert.Description != null) ? item.Alert.Description.replace(/\r?\n/g, '<br />') : '',
                (item.Alert.ResolutionProcedure != null) ? item.Alert.ResolutionProcedure.replace(/\r?\n/g, '<br />') : '',
                eventposition, // Position
                //(item.Details != null) ? item.Details.replace(/\r?\n/g, '<br />') : '',
                item.Time,
                '<button class="AlertAcknowledge btn btn-primary btn-sm" data-alert="' + item.Id + '">' + tracking.strings.ACKNOWLEDGE + '</button>', // ack button
                asset.Id,
                item.Type,
                item.Alert.Color
	        ];
        } else {
            var item = mapAssetEventToListing(item);
            if (item === null) {
                return null;
            }

            itemData = [
                (asset != null) ? asset.Name : '',
                item.icon + ' <span class="event-type">' + item.name + '</span>',
                '', //item.position,
                item.details,
                item.time,
                asset.Id,
                item.type,
                item.isAccurate,
                item.id
            ];
        }
        return itemData;
	}

	function addAssetAlerts(events) {
	    for (var i = 0; i < events.length; i++) {
	        var item = events[i];

	        if (item.Event != null)
	            item = item.Event;
	        if (events[i].Position != null)
	            item.Position = events[i].Position;

	        // only add an item if its not already in the list
	        var skipItem = false;
	        for (var j = 0; j < tracking.data.alerts.length; j++) {
	            if (tracking.data.alerts[j].Id == item.Id) {
	                skipItem = true;
	                break;
	            }
	        }
            if (skipItem) {
                continue;
            }

	        // added to global events
	        tracking.data.alerts.push(item);

	        var thisId = parseInt(item.Id);
	        if (tracking.data.lastAlertId == null) {
	            tracking.data.lastAlertId = thisId;
            }
	        if (tracking.data.lastAlertId < thisId) {
	            tracking.data.lastAlertId = thisId;
            }

	        // add pop-up notification, if necessary
            var event = item;
	        if (event.Alert.PopUpNotification) {
	            var asset = findAssetById(event.AssetId);
	            var name = '';
	            if (event.Alert.Name != null) {
	                name += event.Alert.Name + ': ';
	            }
	            name += event.Alert.Type;
	            var description = null;
	            if (event.Alert.Description != null) {
                    description = [ text(event.Alert.Description), el('br') ];
	            }
	            var buttons = null;
                var positionElement = null;
                if (event.PositionId != null) {
                    // show position button, will likely have to load from ajax or include lat/lng here
                    positionElement = el('button.notification-position.btn.btn-link.btn-sm', { dataset: { assetId: asset.Id, positionId: event.PositionId } }, tracking.strings.SHOW_POSITION);
                }
                var photo = '';
                if (event.Alert.PhotoType != null && event.Alert.PhotoType != '') {
                    photo = '/uploads/images/alerts/' + event.Alert.Id + '_thumb.' + event.Alert.PhotoType;
                }
                var msg = [ 
                    el('span.time', event.Time), 
                    el('div.message', [ name, el('br'), description ])
                ];
                buttons = _.compact([
                    el('button.acknowledge.btn.btn-sm', { dataset: { eventId: event.Id } }, tracking.strings.ACKNOWLEDGE),
                    positionElement
                ]);
	            $j.jGrowl(msg, {
	                header: event.TypeName + ': ' + asset.Name,
	                sticky: true,
                    color: event.Alert.Color,
	                theme: 'alert-triggered',
	                closer: false,
	                mustConfirm: false,
	                eventId: event.Id,
	                assetId: event.AssetId,
                    type: 'alert',
                    text: msg,
                    photo: photo,
                    buttons: buttons,
                    appendTo: '#map-mode-container'
	            });
	        }
	    }

	    updateAlertsListing();
	}

    function addAssetEvents(events, forMapMode) {
        var addedEvents = [];

	    for (var i = 0; i < events.length; i++) {
	        var item = events[i];

            if (item.Event != null) {
                item = item.Event;
            }

            if (events[i].Position != null) { // what
                item.Position = events[i].Position;
            }

            // bounce the existing position when a new bounce event comes in without a position attached
            if ((tracking.state.activeMapMode === tracking.mapModes.LIVE)
            && (tracking.options.bounceOnEvents.indexOf(item.Type) !== -1)
            && (item.Position == null)) {
                var asset = tracking.findAssetById(item.AssetId);
                var assetLivePosition = tracking.data.live.latestPositionsByAssetId[asset.Id];
                if (assetLivePosition !== undefined && assetLivePosition.Position != null) {
                    var positionMarker = tracking.data.live.markersByPositionId[assetLivePosition.Position.Id];
                    if (positionMarker !== undefined) {
                        checkMarkerBounce(positionMarker);
                    }
                }
            }

            if (tracking.data.eventsById[item.Id] === undefined) {
                tracking.data.eventsById[item.Id] = normalizeAssetData(item.AssetId, 'event', item);
            }

	        // only add an item if its not already in the list
            if (forMapMode === tracking.mapModes.LIVE) {
                if (tracking.data.live.normalizedEventIds[item.Id] !== undefined) {
                    continue;
                }
                tracking.data.live.normalizedEventIds[item.Id] = true;
                tracking.data.live.events.push(item);
                tracking.data.live.eventIds.push(item.Id);
                tracking.data.live.normalizedEvents.push(tracking.data.eventsById[item.Id]);

                var thisId = parseInt(item.Id);
                if (tracking.data.lastEventId === null) {
                    tracking.data.lastEventId = thisId;
                }
                if (tracking.data.lastEventId < thisId) {
                    tracking.data.lastEventId = thisId;
                }

                updateAssetEventNotificationTime(item);
            } else if (forMapMode === tracking.mapModes.HISTORY) {
                if (tracking.data.history.normalizedEventIds[item.Id] !== undefined) {
                    continue;
                }
                tracking.data.history.events.push(item);
                tracking.data.history.normalizedEvents.push(tracking.data.eventsById[item.Id]);
                tracking.data.history.normalizedEventIds[item.Id] = true;
            }

            addedEvents.push(item);
        }

        if (addedEvents.length > 0) {
            if (forMapMode === tracking.mapModes.LIVE) {
                tracking.data.live.normalizedEventsByAssetId = _.groupBy(tracking.data.live.normalizedEvents, 'AssetId');
            } else if (forMapMode === tracking.mapModes.HISTORY) {
                tracking.data.history.normalizedEventsByAssetId = _.groupBy(tracking.data.history.normalizedEvents, 'AssetId');
            }
        }

        var assetIdsUpdated = _.chain(addedEvents).map('AssetId').uniq().value();
        var assetsUpdated = [];
        if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
            _.each(assetIdsUpdated, function (assetId) {
                updateAssetFunctionBadges(getAssetDataGroupForCurrentViewMode(), assetId);
                assetsUpdated.push(findAssetById(assetId));
            });
            updateGroupFunctionBadges(getAssetDataGroupForCurrentViewMode(), assetIdsUpdated, 'asset');
            tracking.log('Update event/status/alert notifications for assets: ' + assetIdsUpdated.join(', '));
        }

        // refresh the events, alerts, or status dialogs if it is currently opened for a related asset or group
        if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
            && tracking.data.domNodes.panels.secondary.getAttribute('data-item-type') === 'assets'
            && document.getElementById('dialog-functions').querySelector('.dialog') === tracking.data.domNodes.dialogs.assetEvents) {
            // todo: only append to the existing listing, don't recreate
            var assetId = parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id'));
            if (assetIdsUpdated.indexOf(assetId) !== -1) {
                var updatedAsset = findAssetById(assetId);
                openEventsForAsset(updatedAsset);
            }
        } else if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
            && tracking.data.domNodes.panels.secondary.getAttribute('data-item-type') === 'groups'
            && document.getElementById('dialog-functions').querySelector('.dialog') === tracking.data.domNodes.dialogs.assetEvents) {
            // todo: only append to the existing listing, don't recreate
            var groupId = tracking.data.domNodes.panels.secondary.getAttribute('data-item-id');
            var assetInGroup = _.find(assetsUpdated, function (asset) { return asset.ParentGroupIds.indexOf(groupId) !== -1; });
            if (assetInGroup !== undefined) {
                openEventsForGroup(findGroupById(groupId));
            }
        } else if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
            && tracking.data.domNodes.panels.secondary.getAttribute('data-item-type') === 'assets'
            && document.getElementById('dialog-functions').querySelector('.dialog') === tracking.data.domNodes.dialogs.assetStatus) {
            // todo: only append to the existing listing, don't recreate
            var assetId = parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id'));
            if (assetIdsUpdated.indexOf(assetId) !== -1) {
                var updatedAsset = findAssetById(assetId);
                openStatusForAsset(updatedAsset);
            }
        } else if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
            && tracking.data.domNodes.panels.secondary.getAttribute('data-item-type') === 'groups'
            && document.getElementById('dialog-functions').querySelector('.dialog') === tracking.data.domNodes.dialogs.assetStatus) {
            // todo: only append to the existing listing, don't recreate
            var groupId = tracking.data.domNodes.panels.secondary.getAttribute('data-item-id');
            var assetInGroup = _.find(assetsUpdated, function (asset) { return asset.ParentGroupIds.indexOf(groupId) !== -1; });
            if (assetInGroup !== undefined) {
                openStatusForGroup(findGroupById(groupId));
            }
        } else if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
            && tracking.data.domNodes.panels.secondary.getAttribute('data-item-type') === 'assets'
            && document.getElementById('dialog-functions').querySelector('.dialog') === tracking.data.domNodes.dialogs.assetAlerts) {
            // todo: only append to the existing listing, don't recreate
            var assetId = parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id'));
            if (assetIdsUpdated.indexOf(assetId) !== -1) {
                var updatedAsset = findAssetById(assetId);
                openAlertsForAsset(updatedAsset);
            }
        } else if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
            && tracking.data.domNodes.panels.secondary.getAttribute('data-item-type') === 'groups'
            && document.getElementById('dialog-functions').querySelector('.dialog') === tracking.data.domNodes.dialogs.assetAlerts) {
            // todo: only append to the existing listing, don't recreate
            var groupId = tracking.data.domNodes.panels.secondary.getAttribute('data-item-id');
            var assetInGroup = _.find(assetsUpdated, function (asset) { return asset.ParentGroupIds.indexOf(groupId) !== -1; });
            if (assetInGroup !== undefined) {
                openAlertsForGroup(findGroupById(groupId));
            }
        } else if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
            && tracking.data.domNodes.panels.secondary.getAttribute('data-item-type') === 'groups'
            && document.getElementById('dialog-functions').querySelector('.dialog') === tracking.data.domNodes.dialogs.assetActivity) {
            // todo: only append to the existing listing, don't recreate
            var groupId = tracking.data.domNodes.panels.secondary.getAttribute('data-item-id');
            var assetInGroup = _.find(assetsUpdated, function (asset) { return asset.ParentGroupIds.indexOf(groupId) !== -1; });
            if (assetInGroup !== undefined) {
                openActivityForGroup(findGroupById(groupId));
            }
        } else if (tracking.data.domNodes.panels.secondary.getAttribute('data-group-for') === 'dialog'
            && tracking.data.domNodes.panels.secondary.getAttribute('data-item-type') === 'assets'
            && document.getElementById('dialog-functions').querySelector('.dialog') === tracking.data.domNodes.dialogs.assetActivity) {
            // todo: only append to the existing listing, don't recreate
            var assetId = parseInt(tracking.data.domNodes.panels.secondary.getAttribute('data-item-id'));
            if (assetIdsUpdated.indexOf(assetId) !== -1) {
                var updatedAsset = findAssetById(assetId);
                openActivityForAsset(updatedAsset);
            }
        }

        if (tracking.state.activeMapMode === forMapMode) {
            addEventsToActiveNotificationList(addedEvents); // todo: seems unused
            //addAssetEventsToPanelListing(addedEvents);
        }
	}

    $.fn.dataTable.ext.search.push(
        function (settings, data, dataIndex) {
            if (settings.sInstance === 'event-data') {
                // filter active assets via a hidden column
                var assetId = parseInt(data[5]);
                if (_.indexOf(tracking.data.visible.assets, assetId) === -1) {
                    return false;
                }

                var eventFilters = tracking.data.live.eventFilters;
                if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
                    eventFilters = tracking.data.history.eventFilters;
                }

                if (eventFilters.length === 0 || _.indexOf(eventFilters, 'all') !== -1) {
                    return true;
                }

                var eventTypeId = parseInt(data[6]);
                if (_.indexOf(eventFilters, eventTypeId) === -1) {
                    return false;
                }
                return true;
            }
            return true;
        }
    );

    function updateAlertDialog(event) {
        var form = document.getElementById('acknowledge-form');
        var currentEventId = parseInt(form.getAttribute('data-event-id'));
        var currentAssetId = parseInt(form.getAttribute('data-asset-id'));
        var currentIndex = parseInt(form.getAttribute('data-index'));

        var asset = findAssetById(currentAssetId);
        if (asset === null) {
            return;
        }

        var assetAlerts = _.filter(tracking.data.alerts, function (evt) {
            return evt.AssetId === asset.Id;
        });
        if (event.Id === currentEventId) {
            // the currently opened event was acknowledged
            // move to the next one, if available, with an acknowledged status
            // and show a no more if not
            if (assetAlerts.length === 0) {
                // no more events requiring acknowledgement for the asset
                var none = document.getElementById('asset-acknowledge-alerts-none');
                none.classList.add('is-visible');
                form.classList.remove('is-visible');
            } else {
                var newIndex = currentIndex;
                if (currentIndex > assetAlerts.length) {
                    newIndex = assetAlerts.length;
                }
                var event = assetAlerts[newIndex - 1];
                if (event !== undefined && event !== null) {
                    openAcknowledgeAlertDialog(asset, event);

                    // persist acknowledged message
                    formShowSuccessMessage(form.querySelector('.dialog-status'), tracking.strings.MSG_ALERT_ACKNOWLEDGE_SUCCESS);
                }
            }
        } else {
            if (event.AssetId === currentAssetId) {
                // number of events is updated, update the indexes shown
                var currentEvent = _.find(assetAlerts, { Id: currentEventId });
                populateAssetAlertIndex(asset, currentEvent);
            }
        }
	}

	function confirmAlert(event) {
	    tracking.log('Confirming alert.');
        var isInAlertsListing = false;
	    for (var i = 0; i < tracking.data.alerts.length; i++) {
	        if (event.Id === tracking.data.alerts[i].Id) {
	            tracking.data.alerts.splice(i, 1);
	            isInAlertsListing = true;
                break;
	        }
	    }

	    // update dialogs, if they are open
	    updateAlertDialog(event);

        // update position information dialog (if exists)
	    var form = $('#AcknowledgeAlert' + event.Id + ',#asset-alert-acknowledge-' + event.Id);
	    if (form.length) {
            $('div.alert-resolution', form).empty()
                .append($('<div class="form-group">')
                    .append($('<label>').text(tracking.strings.RESOLUTION))
                    .append($('<span class="form-control-plaintext">').text(event.Alert.AcknowledgedText))
                )
                .append($('<div class="form-group">')
                    .append($('<label>').text(tracking.strings.STATUS))
                    .append($('<span class="form-control-plaintext">').text(tracking.strings.ACKNOWLEDGE_STATUS.replace('{Status}', event.Alert.AcknowledgedStatus).replace('{Time}', event.Alert.AcknowledgedOn).replace('{User}', event.Alert.AcknowledgedBy)))
                );

	        $('div.alert-acknowledge', form).remove();
        }

        var alertIcons = $('svg.alert-acknowledge-icon[data-event-id="' + event.Id + '"]');
        _.each(alertIcons, function (icon) {
            $(icon).removeClass('pending-acknowledgement').addClass('is-acknowledged').find('use').get(0).setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#check-circle-solid');
        });
        // todo: reindex the list, if it's visible

	    // close notification (if any)
	    $('.jGrowl-notification[data-event-id=' + event.Id + ']').trigger('jGrowl.close');

	    if (isInAlertsListing) {
            // todo: call fnDeleteRow instead of redrawing whole table?
	        updateAlertsListing();
	    }
	}

    function initializeMouseTooltip() {
        var tip = document.getElementById('mouse-tooltip');
        //tracking.data.domNodes.map.querySelector('.leaflet-marker-pane').appendChild(tip);
        tracking.data.domNodes.mouseTooltip.content = tip;
        tracking.data.domNodes.mouseTooltip.title = tip.querySelector('.tooltip-inner');
        tracking.data.domNodes.mouseTooltip.show = false;
        tracking.data.domNodes.mouseTooltip.position = { x: 0, y: 0 };
        tracking.data.domNodes.mouseTooltip.reference = {
            getBoundingClientRect: function() {
                return {
                    top: tracking.data.domNodes.mouseTooltip.position.y,
                    right: tracking.data.domNodes.mouseTooltip.position.x,
                    bottom: tracking.data.domNodes.mouseTooltip.position.y,
                    left: tracking.data.domNodes.mouseTooltip.position.x,
                    width: 0,
                    height: 0,
                };
            },
            clientWidth: 0,
            clientHeight: 0,
        };
        var arrow = tracking.data.domNodes.mouseTooltip.content.querySelector('.arrow');
        tracking.data.domNodes.mouseTooltip.popper = new Popper(tracking.data.domNodes.mouseTooltip.reference, tracking.data.domNodes.mouseTooltip.content, {
            modifiers: {
                offset: {},
                arrow: {
                    element: arrow
                }
            },
            placement: 'right'
        });
    }

    function getMapLeftOffset() {
        if (tracking.data.domNodes.content.base.classList.contains('is-collapsed')) {
            return 770;
        } else if (tracking.data.domNodes.content.base.classList.contains('is-expanded')) {
            return 50;
        } else {
            return 370;
        }
    }

    function showFenceTooltip(fence) {
        var fenceBounds = getFenceBounds(fence.Id);
        if (fenceBounds !== null) {
            var latLng = L.latLng(fenceBounds.getCenter().lat, fenceBounds.getEast());
            var tooltipPoint = tracking.map.latLngToContainerPoint(latLng);
            showMouseTooltip(tooltipPoint.x + getMapLeftOffset(), tooltipPoint.y, fence.Name);
        }
    }

    function showMouseTooltip(x, y, title) {
        tracking.data.domNodes.mouseTooltip.position = { x: x, y: y };
        tracking.data.domNodes.mouseTooltip.show = true;
        tracking.data.domNodes.mouseTooltip.title.textContent = title;
        tracking.data.domNodes.mouseTooltip.content.classList.add('show');
        tracking.data.domNodes.mouseTooltip.content.classList.remove('hide');
        tracking.data.domNodes.mouseTooltip.popper.scheduleUpdate();
        // two calls required as popper has incorrect starting arrow on very first show... maybe due to css transition?
        tracking.data.domNodes.mouseTooltip.popper.scheduleUpdate();
    }

    function hideMouseTooltip() {
        tracking.data.domNodes.mouseTooltip.show = false;
        tracking.data.domNodes.mouseTooltip.content.classList.remove('show');
        tracking.data.domNodes.mouseTooltip.content.classList.add('hide');
    }

	function updateAlertsListing() {
        //if ($j('#alert-data').data('init') != true) {
        //    return;
        //}

	    var events = tracking.data.alerts;
	    var assetAlerts = [];
	    //var eventTable = $j('#alert-data').DataTable();

        //eventTable.clear();
        //var tableData = [];
	    for (var i = 0; i < events.length; i++) {
	        var item = events[i];
            //var row = mapAssetEventToTableRow(item, true);
            //if (row !== undefined && row !== null) {
            //    tableData.push(row);
            //}
	        if ($j.inArray(item.AssetId, assetAlerts) == -1) {
	            assetAlerts.push(parseInt(item.AssetId));
	        }
	    }
        //eventTable.rows.add(tableData).draw();
        //var rowCount = eventTable.rows({ search: 'applied' }).eq(0).length;
        //if (rowCount > 0) {
        //    $j('#alerts-control-btn a .badge,#event-panel-tab-alerts .badge').text(rowCount).addClass('active').removeClass('inactive');
        //} else {
        //    $j('#alerts-control-btn a .badge,#event-panel-tab-alerts .badge').text(rowCount).removeClass('active').addClass('inactive');
        //}

        var assetAlert = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        assetAlert.classList.add('asset-alert');
        assetAlert.classList.add('notify-alert');
        var assetAlertTitle = document.createElementNS('http://www.w3.org/2000/svg', 'title');
        assetAlertTitle.textContent = tracking.strings.ACKNOWLEDGE_ALERT;
        assetAlert.appendChild(assetAlertTitle);
        var assetAlertType = document.createElementNS('http://www.w3.org/2000/svg', 'use');
        assetAlertType.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#notify-alert');
        assetAlert.appendChild(assetAlertType);

        _.each(tracking.data.domNodes.assets, function (assetNodes, index, list) {
            _.each(assetNodes, function (assetNode, nodeIndex, nodeList) {
                var alertIndicator = assetNode.querySelector('.asset-alert');
                if (alertIndicator !== null) {
                    alertIndicator.parentNode.removeChild(alertIndicator);
                }
                if (_.indexOf(assetAlerts, parseInt(index)) !== -1) {
                    var indicators = assetNode.querySelector('.asset-indicators');
                    var alertIndicator = assetAlert.cloneNode(true);
                    alertIndicator.setAttribute('data-asset-id', index);
                    indicators.appendChild(alertIndicator);
                }
            });
        });
        assetAlert = null;
        assetAlertLink = null;
        assetAlertIcon = null;
	}

	function initAlerts() {
        // unused TODO: remove
	    $('#alert-data').DataTable({
	        'destroy': true, 'filter': false, 'info': true, 'jQueryUI': true, 'autoWidth': false,
	        'lengthChange': false, 'paging': true, 'pagingType': 'full_numbers', 'deferRender': true, 'processing': false,
	        'order': [[1, 'desc']],
	        'columns': [
                { sortable: false, width: '40px' }, // Color Display
                { sortable: true }, // Priority
                { sortable: true }, // Asset
                { sortable: true }, // Type
                { sortable: false }, // Description
                { sortable: false }, // Resolution Procedure
                { sortable: false }, // Position/lat/lng
                //{ sortable: false }, // Details/link
                { width: '75px' }, // Time
                { sortable: false, class: 'center' }, // Acknowledge
                { visible: false }, // AssetId (hidden)
	            { visible: false }, // Event Type Id
                { visible: false }, // Color
	        ],
	        'dom': '<"H"lfr>t<"F"ip>',
            'language': tracking.strings.DATATABLE,
            'initComplete': function (oSettings, json) {
                var api = this.api();
	            tracking.log('Alerts table initialized.');
	            $j('#alert-data').data('init', true);
	            tracking.data.alerts = [];
	            queryAlertsRequiringAcknowledgement(null);
	            api.clear();
	        },
            'drawCallback': function (oSettings) {
                var api = this.api();
                var rowCount = api.rows({ search: 'applied' }).eq(0).length;
                if (rowCount > 0) {
                    $j('#alerts-control-btn a .badge,#event-panel-tab-alerts .badge').text(rowCount).addClass('active').removeClass('inactive');
                } else {
                    $j('#alerts-control-btn a .badge,#event-panel-tab-alerts .badge').text(rowCount).removeClass('active').addClass('inactive');
                }

	            //$j('#alerts-control-btn a,#event-panel-tab-alerts').text(tracking.strings.ALERTS_HEADER.replace('{0}', api.rows({ search: 'applied' }).eq(0).length));

	            //$j('.AlertAcknowledge').button({ text: null, icons: { primary: 'ui-icon-check' } });
	        },
	        'rowCallback': function (row, data, index) {
	            if (data[11] != '') {
	                $j('td:eq(0)', row).css('background-color', data[11]);
	            }
	        }
	    });
    }

    function createEventFilterNode(evt, isAllChecked) {
        var eventFilters = tracking.data.live.eventFilters;
        if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
            eventFilters = tracking.data.history.eventFilters;
        }
        var isChecked = _.includes(eventFilters, evt.Id) || isAllChecked;
        var isDisabled = isAllChecked;
        var filterLi = document.createElement('div');
        filterLi.className = 'col-md-4';
        var filterDiv = document.createElement('div');
        filterDiv.className = 'custom-control custom-checkbox';
        var filterInput = document.createElement('input');
        filterInput.className = 'custom-control-input';
        filterInput.setAttribute('type', 'checkbox');
        filterInput.setAttribute('id', 'event-filter-' + evt.Id);
        filterInput.setAttribute('name', 'event-filter')
        filterInput.setAttribute('value', evt.Id);
        if (isChecked) {
            filterInput.setAttribute('checked', 'checked');
        }
        if (isDisabled) {
            filterInput.setAttribute('disabled', 'disabled');
        }
        var filterLabel = document.createElement('label');
        filterLabel.className = 'custom-control-label';
        filterLabel.setAttribute('for', 'event-filter-' + evt.Id);
        filterLabel.textContent = evt.Text;
        filterDiv.appendChild(filterInput);
        filterDiv.appendChild(filterLabel);
        filterLi.appendChild(filterDiv);
        return filterLi;
    }

    ///
    function addAssetEventsToPanelListing(events) {
        // unused
        if ($j('#event-data').data('init') !== true) {
            return;
        }

        // todo: only update the table if the shown events have actually changed
        var eventTable = $('#event-data').DataTable();

        var tableData = [];
        _.each(events, function (item) {
            var row = mapAssetEventToTableRow(item, false);
            if (row !== undefined && row !== null) {
                tableData.push(row);
            }
        });

        eventTable.rows.add(tableData).draw();
        tracking.data.pendingEvents = [];
    }

    function createFilteredEventsList() {
        // in live mode, you can filter all events
        // in history mode, you can only filter events that were submitted

        // update filtered list based on event data (this is good for the history view but not the live view)
        var includedEventIds = [];
        if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
            includedEventIds = _.uniq(_.map(tracking.data.live.normalizedEvents, function(item) { return item.Event.Type; }));
        } else if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
            includedEventIds = _.uniq(_.map(tracking.data.history.normalizedEvents, function (item) { return item.Event.Type; }));
        }

        var cont = document.getElementById('event-filter-items');
        // empty it first!
        cont.innerHTML = '';
        if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
            cont.classList.add('multiple');
        } else {
            cont.classList.remove('multiple');
        }
        var eventFilters = tracking.data.live.eventFilters;
        if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
            eventFilters = tracking.data.history.eventFilters;
        }
        var isAllChecked = _.includes(eventFilters, 'all');
        var filterOptions = document.createDocumentFragment();
        var filterAll = document.createElement('div');
        filterAll.className = "col-md-4";
        var filterAllDiv = document.createElement('div');
        filterAllDiv.className = 'custom-control custom-checkbox';
        var filterAllCheckbox = document.createElement('input');
        filterAllCheckbox.className = 'custom-control-input';
        filterAllCheckbox.setAttribute('type', 'checkbox');
        filterAllCheckbox.setAttribute('id', 'event-filter-all');
        filterAllCheckbox.setAttribute('name', 'event-filter');
        if (isAllChecked) {
            filterAllCheckbox.setAttribute('checked', 'checked');
        }
        var filterAllLabel = document.createElement('label');
        filterAllLabel.className = 'custom-control-label';
        filterAllLabel.setAttribute('for', 'event-filter-all');
        filterAllLabel.textContent = tracking.strings.EVENTS_ALL;
        filterAllDiv.appendChild(filterAllCheckbox);
        filterAllDiv.appendChild(filterAllLabel);
        filterAll.appendChild(filterAllDiv);
        filterOptions.appendChild(filterAll);
        var isAVL = hasAVLAssets();

        var standardEvents = _.sortBy(_.filter(tracking.data.eventTypes, function (item) { return item.Id <= 16; }), 'Text');
        var avlEvents = _.sortBy(_.filter(tracking.data.eventTypes, function (item) { return item.Id > 16; }), 'Text');

        // output standard events
        _.each(standardEvents, function (evt) {
            // in history mode, only have options for actual events recorded
            if (tracking.state.activeMapMode !== tracking.mapModes.LIVE && !_.includes(includedEventIds, evt.Id)) {
                return;
            }
            var filterLi = createEventFilterNode(evt, isAllChecked);
            filterOptions.appendChild(filterLi);
        });

        if (isAVL) {
            var hr = document.createElement('div');
            hr.className = 'w-100 py-2';
            //hr.style.cssText = 'float: none; width: auto; padding: 5px;';

            var border = document.createElement('div');
            border.style.cssText = 'border-bottom: 2px solid #ccc;';
            hr.appendChild(border);

            filterOptions.appendChild(hr);

            _.each(avlEvents, function (evt) {
                if (tracking.state.activeMapMode !== tracking.mapModes.LIVE && !_.includes(includedEventIds, evt.Id)) {
                    return;
                }
                var filterLi = createEventFilterNode(evt, isAllChecked);
                filterOptions.appendChild(filterLi);
            });
        }
        cont.appendChild(filterOptions);
    }

    function cleanupExpiredLiveData() {
        if (tracking.state.activeMapMode !== tracking.mapModes.LIVE) {
            return;
        }
        var expirationEpoch = moment().subtract(4, 'hours').toDate().getTime() / 1000;
        console.log(expirationEpoch);
        var oldEvents = _.filter(tracking.data.live.events, function (item) {
            return item.Epoch !== undefined && item.Epoch < expirationEpoch;
        });

        var oldPositions = _.filter(tracking.data.live.positions, function (item) {
            return item.Position.Epoch !== undefined && item.Position.Epoch < expirationEpoch;
        });

        var expiredEventsById = _.groupBy(oldEvents, function (item) { return item.Id; });
        var expiredPositionsById = _.groupBy(oldPositions, function (item) { return item.Position.Id; });
        for (var i = tracking.data.live.positions.length - 1; i >= 0; i--) {
            var position = tracking.data.live.positions[i];
            var latestPositionForAsset = tracking.data.live.latestPositionsByAssetId[position.AssetId];
            if (latestPositionForAsset !== undefined && latestPositionForAsset.Position.Id === position.Position.Id) {
                // don't remove the most recent position for an asset
                continue;
            }
            if (expiredPositionsById[position.Position.Id] !== undefined) {
                tracking.data.live.positions.splice(i, 1);
            }
            if (tracking.data.domNodes.positionListingById[position.Position.Id] !== undefined) {
                delete tracking.data.domNodes.positionListingById[position.Position.Id];
            }
            if (tracking.data.domNodes.sharedView.positionListingById[position.Position.Id] !== undefined) {
                delete tracking.data.domNodes.sharedView.positionListingById[position.Position.Id];
            }
        }

        tracking.data.live.normalizedPositions = _.reject(tracking.data.live.normalizedPositions, function (item) {
            var latestPositionForAsset = tracking.data.live.latestPositionsByAssetId[item.AssetId];
            return latestPositionForAsset !== undefined && latestPositionForAsset.Position.Id !== item.Position.Id && expiredPositionsById[item.Position.Id] !== undefined;
        });

        for (var i = tracking.data.live.events.length - 1; i >= 0; i--) {
            var event = tracking.data.live.events[i];
            if (expiredEventsById[event.Id] !== undefined) {
                tracking.data.live.events.splice(i, 1);
            }
            if (tracking.data.domNodes.eventListingById[event.Id] !== undefined) {
                delete tracking.data.domNodes.eventListingById[event.Id];
            }
            if (tracking.data.domNodes.sharedView.eventListingById[event.Id] !== undefined) {
                delete tracking.data.domNodes.sharedView.eventListingById[event.Id];
            }
        }

        tracking.data.live.normalizedEvents = _.reject(tracking.data.live.normalizedEvents, function (item) {
            var isRelatedToLatestPosition = false;
            if (item.Position !== undefined && item.Position !== null) {
                var latestPositionForAsset = tracking.data.live.latestPositionsByAssetId[item.AssetId];
                if (latestPositionForAsset !== undefined && latestPositionForAsset.Position.Id === item.Position.Id) {
                    isRelatedToLatestPosition = true;
                }
            }
            return !isRelatedToLatestPosition && expiredEventsById[item.Event.Id] !== undefined;
        });

        tracking.data.live.positionsByAssetId = _.groupBy(tracking.data.live.positions, 'AssetId');
        tracking.data.live.normalizedPositionsByAssetId = _.groupBy(tracking.data.live.normalizedPositions, 'AssetId');
        tracking.data.live.normalizedEventsByAssetId = _.groupBy(tracking.data.live.normalizedEvents, 'AssetId');

        // todo: messages

        // todo: remove from actual data listings
        // tracking.data.live.events (should always keep latest for asset)
        //  .Epoch
        // tracking.data.live.positions (should always keep latest for asset = tracking.data.live.latestPositionsByAssetId)
        //  .Position.Epoch
        // regen tracking.data.live.positionsByAssetId
        // tracking.data.domNodes.eventListingById
        // tracking.data.domNodes.positionListingById
        // tracking.data.domNodes.messageListingById
    }

	function populateEvents(events) {
	    $('#event-data').DataTable({
	        'destroy': true, 'filter': true, 'info': true, 'jQueryUI': false, 'autoWidth': false,
	        'lengthChange': false, 'paging': true, 'pagingType': 'full_numbers', 'deferRender': true, 'processing': false,
	        'order': [[4, 'desc']],
	        'columns': [
                { sortable: true }, // Asset
                { sortable: true }, // Event Type
                { sortable: false }, // Position/lat/lng
                { sortable: false, className: 'break-text' }, // Details/link
                { width: '75px' }, // Time
                { visible: false }, // AssetId (hidden)
	            { visible: false }, // Event Type Id
                { visible: false }, // Is Time Accurate
                { visible: false }  // Event Id
	        ],
	        'dom': '<"H"lfr>t<"F"ip>',
            'language': tracking.strings.DATATABLE,
            'initComplete': function (oSettings, json) {
                console.log('init complete for populateEvents');
                var api = this.api();
	            $j('#event-data').data('init', true);
                api.clear();

                addAssetEventsToPanelListing(events);
                //addAssetEvents(events);

	            // move clear events button to toolbar
                var filterDiv = $j('#event-data_filter').parent().eq(0);
	            var btn = $j('<button class="btn btn-secondary btn-sm" />').attr('id', 'ClearEvents').text(tracking.strings.BUTTON_CLEAR_EVENTS);
                if (tracking.state.activeMapMode !== tracking.mapModes.LIVE) {
                    btn.attr('disabled', true);
                }
                btn.appendTo(filterDiv);

                // create filter events link
                var filter = $j('<a />').attr('id', 'FilterEvents').attr('href', '#').text(tracking.strings.EVENTS_FILTER).appendTo(filterDiv);
                $j('#event-filter-types').appendTo(filter);

                createFilteredEventsList();
	        },
	        'rowCallback': function (row, data, index) {
	            if (data[7] != null && data[7] == false) {
	                $j('td:eq(4)', row).addClass('inaccurate');
	            }
	        },
	        'drawCallback': function (oSettings) {
                var api = this.api();
                var rowCount = api.rows({ search: 'applied' }).eq(0).length;
                if (rowCount > 0) {
                    $j('#event-control-btn a .badge,#event-panel-tab-events .badge').text(rowCount).addClass('active').removeClass('inactive');
                } else {
                    $j('#event-control-btn a .badge,#event-panel-tab-events .badge').text(rowCount).removeClass('active').addClass('inactive');
                }
	        }
	     });
	}

    function hasAVLAssets() {
        for (var i = 0; i < tracking.data.assets.length; i++) {
            if ($j.inArray(tracking.data.assets[i].Configuration, tracking.data.avlConfigurations) !== -1) {
                return true;
            }
        }
        return false;
    }

    function highlightInitialPosition() {
        if (tracking.options.showPositionId != '') {
            highlightPosition(tracking.options.showPositionId, null);
            tracking.options.showPositionId = '';
        }
    }

    function findDeviceById(id) {
        var item = _.find(tracking.data.devices, { Id: parseInt(id) });
        return item === undefined ? null : item;
    }

    function findWaypointById(id) {
        var item = _.find(tracking.data.waypoints, { Id: parseInt(id) });
        return item === undefined ? null : item;
    }

    function findWaypointByAsset(asset) {
        var item = _.find(tracking.data.waypoints, { AssetId: asset.Id });
        return item === undefined ? null : item;
    }

    function findPlaceByLatLng(latLng) {
        var item = _.find(tracking.data.places, function (itm) {
            return itm.Location.Lat === latLng.lat && itm.Location.Lng === latLng.lng;
        });
        return item === undefined ? null : item;
    }

    function findPlaceByUniqueKey(uniqueKey) {
        var item = _.find(tracking.data.places, { UniqueKey: uniqueKey });
        return item === undefined ? null : item;
    }

    function findPlaceById(id) {
        var item = _.find(tracking.data.places, { Id: parseInt(id) });
        return item === undefined ? null : item;
    }

    function findJourneyById(id) {
        var item = _.find(tracking.data.journeys, { Id: parseInt(id) });
        return item === undefined ? null : item;
    }

    function findSharedViewById(id) {
        var item = _.find(tracking.data.sharedViews, { Id: parseInt(id) });
        return item === undefined ? null : item;
    }

    function findTripById(id) {
        var tripId = parseInt(id);
        for (var i = 0; i < tracking.data.journeys.length; i++) {
            for (var j = 0; j < tracking.data.journeys[i].Trips.length; j++) {
                if (tracking.data.journeys[i].Trips[j].Id === tripId) {
                    return tracking.data.journeys[i].Trips[j];
                }
            }
        }
        return null;
    }

    function findFenceById(id) {
        var item = _.find(tracking.data.fences, { Id: id });
        return item === undefined ? null : item;
    }

    function getAttributesForType(type, prefix) {
        return _.filter(tracking.data.attributes, { Type: type }).map(function (item) {
            return {
                Id: item.Id,
                Value: $j('#' + prefix + item.Id).val()
            };
        });
    }

    function findAttributeGroupById(id) {
        var item = _.find(tracking.data.attributeGroups, { Id: parseInt(id) });
        return item === undefined ? null : item;
    }

    function findAttributeById(id) {
        var item = _.find(tracking.data.attributes, { Id: parseInt(id) });
        return item === undefined ? null : item;
    }

    function findAttributesForAttributeGroup(group, type) {
        return findAttributesForAttributeGroupId(group.Id, type);
    }

    function findAttributesForAttributeGroupId(id, type) {
        return _.filter(tracking.data.attributes, { GroupId: parseInt(id), Type: type });
    }

    function findGroupById(id) {
        var item = _.find(tracking.data.groups, { Id: id });
        return item === undefined ? null : item;
    }

    function findAssetByUniqueId(uniqueId) {
        var item = _.find(tracking.data.assets, { UniqueId: uniqueId });
        return item === undefined ? null : item;
    }

    function findAssetById(id) {
        var item = tracking.data.assetsById[parseInt(id)];
        return item === undefined ? null : item;
    }

    function findDriverById(id) {
        var item = _.find(tracking.data.drivers, { Id: parseInt(id) });
        return item === undefined ? null : item;
    }

    function findDriverStatusesByTemplateId(id) {
        var item = _.find(tracking.data.driverStatuses, { Id: parseInt(id) });
        return item === undefined ? null : item.DriverStatuses;
    }

    function findAssetDriverIdsByAssetIdSorted(id) {
        // tracking.data.drivers is sorted
        if (tracking.data.assetDrivers == null) {
            return null;
        }
        var ids = null;
        var assetDrivers = _.find(tracking.data.assetDrivers, { AssetId: parseInt(id) });
        if (assetDrivers !== undefined) {
            ids = assetDrivers.DriverIds.concat(assetDrivers.GroupDriverIds);
        }
        if (ids != null) {
            var sortedIds = [];
            for (var i = 0, len = tracking.data.drivers.length; i < len; i++) {
                var driverId = tracking.data.drivers[i].Id;
                for (var j = 0; j < ids.length; j++) {
                    var includedId = ids[j];
                    if (driverId == includedId) {
                        sortedIds.push(includedId);
                        break;
                    }
                }
            }
            return sortedIds;
        }

        return null;
    };

    function findAssetDriverGroupIdsByAssetId(id) {
        var item = _.find(tracking.data.assetDriverGroups, { AssetId: parseInt(id) });
        return item === undefined ? null : item.DriverGroupIds;
    }

    function findAssetDriversByAssetId(id) {
        var item = _.find(tracking.data.assetDrivers, { AssetId: parseInt(id) });
        return item === undefined ? null : item.DriverIds;
    }

    function addDriverToAsset(driverId, assetId) {
        if (tracking.data.assetDrivers == null)
            return;
        for (var i = 0; i < tracking.data.assetDrivers.length; i++) {
            if (tracking.data.assetDrivers[i].AssetId == assetId) {
                tracking.data.assetDrivers[i].DriverIds.push(driverId);
                break;
            }
        }
    }

    function calculateGroupDriverIdsForAsset(assetId) {
        var assetDriverGroup = _.find(tracking.data.assetDriverGroups, { AssetId: parseInt(assetId) });
        if (assetDriverGroup === undefined) {
            return;
        }

        var groupDriverIds = [];
        _.each(assetDriverGroup.DriverGroupIds, function (groupId) {
            var driverGroup = _.find(tracking.data.driverGroups, { Id: groupId });
            if (driverGroup === undefined) {
                return;
            }
            groupDriverIds = groupDriverIds.concat(driverGroup.ItemIds);
        });

        var assetDrivers = _.find(tracking.data.assetDrivers, { AssetId: parseInt(assetId) });
        if (assetDrivers === undefined) {
            tracking.data.assetDrivers.push({ AssetId: parseInt(assetId), DriverIds: [], GroupDriverIds: groupDriverIds });
        } else {
            assetDrivers.GroupDriverIds = groupDriverIds;
        }
    }

    function addDriverGroupToAsset(driverGroupId, assetId) {
        var assetDriverGroup = _.find(tracking.data.assetDriverGroups, { AssetId: parseInt(assetId) });
        if (assetDriverGroup === undefined) {
            tracking.data.assetDriverGroups.push({ AssetId: parseInt(assetId), DriverGroupIdsIds: [ driverGroupId ]});
        } else {
            if (_.indexOf(assetDriverGroup.DriverGroupIds, driverGroupId) === -1) {
                assetDriverGroup.DriverGroupIds.push(driverGroupId);
            }
        }

        // recalculate GroupDriverIds for the asset
        calculateGroupDriverIdsForAsset(assetId);
    }

    function removeDriverGroupFromAsset(driverGroupId, assetId) {
        var assetDriverGroup = _.find(tracking.data.assetDriverGroups, { AssetId: parseInt(assetId) });
        if (assetDriverGroup === undefined) {
            return;
        }
        var groupIndex = _.indexOf(assetDriverGroup.DriverGroupIds, driverGroupId);
        if (groupIndex !== -1) {
            // remove group
            assetDriverGroup.DriverGroupIds.splice(groupIndex, 1);
        }

        // recalculate GroupDriverIds for the asset based on remaining group(s) assigned
        calculateGroupDriverIdsForAsset(assetId);
    }

    function removeDriverFromAsset(driverId, assetId) {
        if (tracking.data.assetDrivers == null)
            return;
        for (var i = 0; i < tracking.data.assetDrivers.length; i++) {
            if (tracking.data.assetDrivers[i].AssetId == assetId) {
                for (var j = 0; j < tracking.data.assetDrivers[i].DriverIds.length; j++) {
                    if (driverId == tracking.data.assetDrivers[i].DriverIds[j]) {
                        tracking.data.assetDrivers[i].DriverIds.splice(j, 1);
                        break;
                    }
                }
                break;
            }
        }
    }

    function findAssetUsersByAssetId(id) {
        if (tracking.data.assetUsers == null)
            return null;
        for (var i = 0, len = tracking.data.assetUsers.length; i < len; i++) {
            if (tracking.data.assetUsers[i].AssetId == id) {
                return tracking.data.assetUsers[i].UserIds;
            }
        }
    }

    function addUserToAsset(userId, assetId) {
        if (tracking.data.assetUsers == null)
            return;
        for (var i = 0; i < tracking.data.assetUsers.length; i++) {
            if (tracking.data.assetUsers[i].AssetId == assetId) {
                tracking.data.assetUsers[i].UserIds.push(userId);
                break;
            }
        }
    }

    function removeUserFromAsset(userId, assetId) {
        if (tracking.data.assetUsers == null)
            return;
        for (var i = 0; i < tracking.data.assetUsers.length; i++) {
            if (tracking.data.assetUsers[i].AssetId == assetId) {
                for (var j = 0; j < tracking.data.assetUsers[i].UserIds.length; j++) {
                    if (userId == tracking.data.assetUsers[i].UserIds[j]) {
                        tracking.data.assetUsers[i].UserIds.splice(j, 1);
                        break;
                    }
                }
                break;
            }
        }
    }

    function findPlaceUsersByPlaceId(id) {
        if (tracking.data.placeUsers == null)
            return null;
        for (var i = 0, len = tracking.data.placeUsers.length; i < len; i++) {
            if (tracking.data.placeUsers[i].PlaceId == id) {
                return tracking.data.placeUsers[i].UserIds;
            }
        }
    }

    function addUserToPlace(userId, placeId) {
        if (tracking.data.placeUsers == null)
            return;
        for (var i = 0; i < tracking.data.placeUsers.length; i++) {
            if (tracking.data.placeUsers[i].PlaceId == placeId) {
                tracking.data.placeUsers[i].UserIds.push(userId);
                break;
            }
        }
    }

    function removeUserFromPlace(userId, placeId) {
        if (tracking.data.placeUsers == null)
            return;
        for (var i = 0; i < tracking.data.placeUsers.length; i++) {
            if (tracking.data.placeUsers[i].PlaceId == placeId) {
                for (var j = 0; j < tracking.data.placeUsers[i].UserIds.length; j++) {
                    if (userId == tracking.data.placeUsers[i].UserIds[j]) {
                        tracking.data.placeUsers[i].UserIds.splice(j, 1);
                        break;
                    }
                }
                break;
            }
        }
    }

    function findFenceUsersByFenceId(id) {
        if (tracking.data.fenceUsers == null)
            return null;
        for (var i = 0, len = tracking.data.fenceUsers.length; i < len; i++) {
            if (tracking.data.fenceUsers[i].FenceId == id) {
                return tracking.data.fenceUsers[i].UserIds;
            }
        }
    }

    function addUserToFence(userId, fenceId) {
        if (tracking.data.fenceUsers == null)
            return;
        for (var i = 0; i < tracking.data.fenceUsers.length; i++) {
            if (tracking.data.fenceUsers[i].FenceId == fenceId) {
                tracking.data.fenceUsers[i].UserIds.push(userId);
                break;
            }
        }
    }

    function removeUserFromFence(userId, fenceId) {
        if (tracking.data.fenceUsers == null)
            return;
        for (var i = 0; i < tracking.data.fenceUsers.length; i++) {
            if (tracking.data.fenceUsers[i].FenceId == fenceId) {
                for (var j = 0; j < tracking.data.fenceUsers[i].UserIds.length; j++) {
                    if (userId == tracking.data.fenceUsers[i].UserIds[j]) {
                        tracking.data.fenceUsers[i].UserIds.splice(j, 1);
                        break;
                    }
                }
                break;
            }
        }
    }

    function findAssetGroupUsersByAssetGroupId(id) {
        if (tracking.data.assetGroupUsers == null)
            return null;
        for (var i = 0, len = tracking.data.assetGroupUsers.length; i < len; i++) {
            if (tracking.data.assetGroupUsers[i].AssetGroupId == id) {
                return tracking.data.assetGroupUsers[i].UserIds;
            }
        }
    }

    function addUserToAssetGroup(userId, assetGroupId) {
        if (tracking.data.assetGroupUsers == null)
            return;
        for (var i = 0; i < tracking.data.assetGroupUsers.length; i++) {
            if (tracking.data.assetGroupUsers[i].AssetGroupId == assetGroupId) {
                tracking.data.assetGroupUsers[i].UserIds.push(userId);
                break;
            }
        }
    }

    function removeUserFromAssetGroup(userId, assetGroupId) {
        if (tracking.data.assetGroupUsers == null)
            return;
        for (var i = 0; i < tracking.data.assetGroupUsers.length; i++) {
            if (tracking.data.assetGroupUsers[i].AssetGroupId == assetGroupId) {
                for (var j = 0; j < tracking.data.assetGroupUsers[i].UserIds.length; j++) {
                    if (userId == tracking.data.assetGroupUsers[i].UserIds[j]) {
                        tracking.data.assetGroupUsers[i].UserIds.splice(j, 1);
                        break;
                    }
                }
                break;
            }
        }
    }

    function isLatLngInsidePolygon(bounds, latLng) {
        //https://rosettacode.org/wiki/Ray-casting_algorithm
        var count = 0;
        for (var b = 0; b < bounds.length; b++) {
            var vertex1 = bounds[b];
            var vertex2 = bounds[(b + 1) % bounds.length];
            if (west(vertex1, vertex2, latLng.lng, latLng.lat))
                ++count;
        }
        return count % 2;

		/**
		 * @return {boolean} true if (x,y) is west of the line segment connecting A and B
		 */
        function west(A, B, x, y) {
            if (A.lat <= B.lat) {
                if (y <= A.lat || y > B.lat ||
                    x >= A.lng && x >= B.lng) {
                    return false;
                } else if (x < A.lng && x < B.lng) {
                    return true;
                } else {
                    return (y - A.lat) / (x - A.lng) > (B.lat - A.lat) / (B.lng - A.lng);
                }
            } else {
                return west(B, A, x, y);
            }
        }
    }

    function findAssetsInsidePolygon(polygon) {
        var assets = [];
        if (tracking.data.live.latestPositions == null) {
            return assets;
        }
        var isCircle = (polygon instanceof L.Circle);
        var radius = null;
        var circleCenter = null;
        var latLngs = [];
        if (isCircle) {
            circleCenter = polygon.getLatLng();
            radius = polygon.getRadius();
        } else {
            latLngs = polygon.getLatLngs();
        }
        _.each(tracking.data.live.latestPositions, function (pos) {
            if (pos.Position != null) {
                var latLng = L.latLng(pos.Position.Lat, pos.Position.Lng);
                if (isCircle) {
                    // circles check if latlng is within circle radius
                    if (Math.abs(circleCenter.distanceTo(latLng)) <= radius) {
                        assets.push(pos.AssetId);
                    }
                } else {
                    // have to use ray casting algorithm for polygons
                    if (isLatLngInsidePolygon(latLngs[0], latLng)) {
                        assets.push(pos.AssetId);
                    }
                }
            }
        });
        return assets;
    }

	function findAssetIdsInGeofence(fence) {
	    var assets = [];

	    for (var i = 0, len = tracking.data.fenceMarkers.length; i < len; i++) {
            var marker = tracking.data.fenceMarkers[i];
            if (marker.data.fenceId == fence.Id) {
                assets = assets.concat(findAssetsInsidePolygon(marker));
            }
	    }
        return assets;
	}

    function getAssetIdsFromAssets(assets) {
        var assetIds = [];
        for (var i = 0, len = assets.length; i < len; i++) {
            assetIds.push(assets[i].Id);
        }
        return assetIds;
    }

    function findAssetsWithGroup(group) {
        var assets = [];
        for (var i = 0, len = tracking.data.assets.length; i < len; i++) {
            var asset = tracking.data.assets[i];
            for (var j = 0, glen = asset.GroupIds.length; j < glen; j++) {
                if (asset.GroupIds[j] === group.Id) {
                    assets.push(asset);
                }
            }
        }
        return assets;
    }

    function findAssetIdsWithGroup(group) {
        return getAssetIdsFromAssets(findAssetsWithGroup(group));
    }

    function findAssetsUnderGroup(group) {
        var groupAssets = [];
        if (group.Id === 'all-assets') {
            return tracking.data.assets;
        }
        _.each(tracking.data.assets, function (asset, index, list) {
            if (_.indexOf(asset.GroupIds, group.Id) !== -1) {
                groupAssets.push(asset);
            }
        });

        var subGroups = _.filter(tracking.data.groups, { ParentGroupId: group.Id });
        _.each(subGroups, function (subGroup, index, list) {
            _.each(findAssetsUnderGroup(subGroup), function(asset) {
                groupAssets.push(asset);
            });
        });

        return _.uniq(groupAssets);
    }

    function findAssetIdsUnderGroup(group) {
        return getAssetIdsFromAssets(findAssetsUnderGroup(group));
    }

    function deleteSharedView(sharedView) {
        tracking.data.domNodes.sharedViews[sharedView.Id].parentNode.removeChild(tracking.data.domNodes.sharedViews[sharedView.Id]);
        var panel = tracking.data.domNodes.panels.secondary;
        if (panel.getAttribute('data-group-for') === 'shared-views' && parseInt(panel.getAttribute('data-item-id')) === sharedView.Id) {
            closeSecondaryPanel(); // no longer closes out the shared view
        }

        if (tracking.data.sharedView.current === sharedView) {
            selectSharedView(null);
        }

        // remove from tracking.data.sharedViews
        for (var i = 0; i < tracking.data.sharedViews.length; i++) {
            if (tracking.data.sharedViews[i].Id == sharedView.Id)
                tracking.data.sharedViews.splice(i, 1);
        }

        // if last item, show no shared views
        if (tracking.data.sharedViews.length === 0) {
            document.getElementById('no-shared-views').classList.add('is-visible');
            document.getElementById('shared-views-all').classList.remove('is-visible');
            document.getElementById('filter-shared-views').querySelector('.filter-box').classList.remove('is-visible');
        }
        updateGroupVisibilityStatus('all-shared-views');
        indexSharedViewsForSearch();
    }

    function addSharedView(sharedView, groupId) {
        sharedView.ColorSorted = convertHexToSortable(sharedView.Color);
        sharedView.Link = getSharedViewLink(sharedView); // TODO remove this
        tracking.data.sharedViews.push(sharedView);

        var group = findGroupById(groupId);
        if (group === null) {
            groupId = 'all-shared-views';
        }

        var itemNode = createSharedViewNode(sharedView);
        tracking.data.domNodes.sharedViews[sharedView.Id] = itemNode;
        var groupContentsNode = tracking.data.domNodes.groupContents[groupId].querySelector('ul.group-list-list');

        var groupItems = sortItemsByMode('shared-views', tracking.data.sharedViews, groupId, 'shared-views');

        // add it where it belongs instead of at the end
        if (groupItems.length > 1) {
            var itemIndex = _.indexOf(groupItems, sharedView);
            var subgroups = (group !== null) ? group.GroupIds.length : 0;
            groupContentsNode.insertBefore(itemNode, groupContentsNode.children[itemIndex + subgroups + 1]); // +1 due to the no-items first node
        } else {
            groupContentsNode.appendChild(itemNode);
        }

        updateGroupVisibilityStatus(groupId);
        toggleItemSorting('shared-views', tracking.user.displayPreferences.sortMode['shared-views'] === tracking.sortModes.CUSTOM);
        document.getElementById('no-shared-views').classList.remove('is-visible');
        document.getElementById('shared-views-all').classList.add('is-visible');
        document.getElementById('filter-shared-views').querySelector('.filter-box').classList.add('is-visible');
        groupContentsNode.querySelector('.no-items').classList.remove('is-visible');
    }

    function addPlace(place, groupId) {
        var group = findGroupById(groupId);
        if (group === null) {
            groupId = 'all-places';
        }
        var itemNode = createPlaceNode(place);
        tracking.data.domNodes.places[place.Id] = itemNode;
        var groupContentsNode = tracking.data.domNodes.groupContents[groupId].querySelector('ul.group-list-list');

        var groupItems = tracking.data.places;
        //if (group != null) {
        //    groupItems = _.filter(groupItems, function (item) { return _.indexOf(item.GroupIds, groupId) !== -1; });
        //}
        groupItems = sortItemsByMode('places', groupItems, groupId, 'places');

        // add it where it belongs instead of at the end
        if (groupItems.length > 1) {
            var itemIndex = _.indexOf(groupItems, place);
            var subgroups = (group !== null) ? group.GroupIds.length : 0;
            groupContentsNode.insertBefore(itemNode, groupContentsNode.children[itemIndex + subgroups + 1]); // +1 due to the no-places first node
        } else {
            groupContentsNode.appendChild(itemNode);
        }

        var point = L.latLng(place.Location.Lat, place.Location.Lng);
        addPlaceMarker(point, place.Location, place);
        updateGroupVisibilityStatus(groupId);
        toggleItemSorting('places', tracking.user.displayPreferences.sortMode.places === tracking.sortModes.CUSTOM);

        groupContentsNode.querySelector('.no-items').classList.remove('is-visible');
        document.getElementById('filter-places').querySelector('.filter-box').classList.add('is-visible');
    }

    function getFenceColor(fence) {
        if (tracking.options.useFenceDocuments && (fence.IsValid != null) && (fence.IsAboutToExpire != null)) {
            if (!fence.IsValid) {
                return 'd10000';
            } else if (fence.IsAboutToExpire) {
                return 'eea000';
            } else {
                return '01d125';
            }
        }

        return getItemHexColor(fence);
    }

    function getItemHexColor(item) {
        if (item.Color === undefined || item.Color === null || item.Color.length < 7) {
            return 'ff0000';
        }

        return item.Color.substring(1);
    }

    function addFence(fence, groupId) {
        var group = findGroupById(groupId);
        if (group == null) {
            groupId = 'all-fences';
        }

        var itemNode = createFenceNode(fence);
        tracking.data.domNodes.fences[fence.Id] = itemNode;
        var groupContentsNode = tracking.data.domNodes.groupContents[groupId].querySelector('ul.group-list-list');

        var groupFences = tracking.data.fences;
        //if (group != null) {
        //    groupFences = _.filter(groupFences, function (fence) { return _.indexOf(fence.GroupIds, groupId) !== -1; });
        //}
        groupFences = sortItemsByMode('fences', groupFences, groupId, 'fences');

        // add it where it belongs instead of at the end
        if (groupFences.length > 1) {
            var itemIndex = _.indexOf(groupFences, fence);
            var subgroups = (group !== null) ? group.GroupIds.length : 0;
            groupContentsNode.insertBefore(itemNode, groupContentsNode.children[itemIndex + subgroups + 1]);// +1 due to the no-fences first node
        } else {
            groupContentsNode.appendChild(itemNode);
        }
        toggleItemSorting('fences', tracking.user.displayPreferences.sortMode.fences === tracking.sortModes.CUSTOM);

        if (tracking.data.fences.length == 0) {
            $('#no-all-fences').addClass('is-visible');
            document.getElementById('filter-fences').querySelector('.filter-box').classList.remove('is-visible');
        } else {
            $('#no-all-fences').removeClass('is-visible');
            document.getElementById('filter-fences').querySelector('.filter-box').classList.add('is-visible');
        }
    }

    function addAssetToGroup(asset, groupId) {
        var group = findGroupById(groupId);
        if (group == null) {
            groupId = 'all-assets';
        }

        var groupContentsNode = tracking.data.domNodes.groupContents[groupId].querySelector('ul.group-list-list');

        // assetNode may not be created
        var groupAssetNode;
        if (tracking.data.domNodes.assets[asset.Id] === undefined) {
            groupAssetNode = createAssetNode(asset);
            tracking.data.domNodes.assets[asset.Id] = [];
        } else {
            groupAssetNode = tracking.data.domNodes.assets[asset.Id][0].cloneNode(true);
        }
        tracking.data.domNodes.assets[asset.Id].push(groupAssetNode);

        // find the appropriate point in the groups asset list to add the item
        var groupAssets = tracking.data.assets;
        if (group != null) {
            groupAssets = _.filter(groupAssets, function (asset) { return _.indexOf(asset.GroupIds, groupId) !== -1; });
        }
        groupAssets = sortItemsByMode('assets', groupAssets, groupId, 'assets');
        if (groupAssets.length > 1) {
            var assetIndex = _.indexOf(groupAssets, asset);
            var subgroups = (group !== null) ? group.GroupIds.length : 0;
            groupContentsNode.insertBefore(groupAssetNode, groupContentsNode.children[assetIndex + subgroups]);
        } else {
            groupContentsNode.appendChild(groupAssetNode);
        }
        updateGroupVisibilityStatus(groupId);

        document.getElementById('filter-assets').querySelector('.filter-box').classList.add('is-visible');
    }

    function removeAssetFromGroup(asset, groupId) {
        // remove assetNode from group contents and cache
        var groupContents = tracking.data.domNodes.groupContents[groupId];
        if (groupContents === undefined) {
            return;
        }

        groupContents = groupContents.querySelector('ul.group-list-list');
        if (groupContents === null) {
            return;
        }

        var foundIndex = -1;
        _.each(tracking.data.domNodes.assets[asset.Id], function (assetNode, index) {
            foundIndex = _.indexOf(groupContents.childNodes, assetNode);
            if (foundIndex !== -1) {
                groupContents.removeChild(assetNode);
                assetNode = null;
                return;
            }
        });

        if (foundIndex !== -1) {
            tracking.data.domNodes.assets[asset.Id].splice(foundIndex, 1);
        }

        updateGroupVisibilityStatus(groupId);
    }

    function expandGroup(groupId) {
        var groupContainer = tracking.data.domNodes.groups[groupId];
        // render the group contents
        if (groupId === 'all-assets') {
            tracking.user.displayPreferences.hideAllAssets = false;
        }
        displayPreferencesAdd('expandedGroups', groupId);
        groupContainer.appendChild(tracking.data.domNodes.groupContents[groupId]);
        if (groupId === 'all-fences' || groupId === 'all-places') {
            if (tracking.data.domNodes.groupContents[groupId].childNodes.length === 0) {
                var noExtraItems = groupContainer.querySelector('#no-' + groupId);
                noExtraItems.classList.add('is-visible');
            }
        }
        toggleGroupExpandedIcon(groupId, true);
    }

    function collapseGroup(groupId) {
        var groupContainer = tracking.data.domNodes.groups[groupId];
        // remove the group contents
        if (groupId === 'all-assets') {
            tracking.user.displayPreferences.hideAllAssets = true;
        }
        displayPreferencesRemove('expandedGroups', groupId);
        groupContainer.removeChild(tracking.data.domNodes.groupContents[groupId]);
        if (groupId === 'all-fences' || groupId === 'all-places') {
            var noItems = groupContainer.querySelector('#no-' + groupId);
            if(noItems !== null) {
                noItems.classList.remove('is-visible');
            }
        }
        toggleGroupExpandedIcon(groupId, false);
    }

    function toggleGroupExpanded(groupId) {
        if (isItemIncluded(tracking.user.displayPreferences.expandedGroups, groupId)) {
            collapseGroup(groupId);
        } else {
            expandGroup(groupId);
        }
    }

    function toggleGroupExpandedIcon(groupId, isVisible) {
        var group = tracking.data.domNodes.groups[groupId];
        if (group === undefined) {
            return;
        }
        toggleIcon = group.querySelector('.group-toggle');
        if (toggleIcon === null) {
            return;
        }
        if (isVisible) {
            group.classList.add('is-expanded');
            toggleIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#folder-open-solid');
            toggleIcon.classList.add('active');
        } else {
            group.classList.remove('is-expanded');
            toggleIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#folder-solid');
            toggleIcon.classList.remove('active');
        }
    }

    function indexAssetGroupsForSearch() {
        tracking.data.search.assetGroups = new JsSearch.Search('Id');
        tracking.data.search.assetGroups.indexStrategy = new JsSearch.AllSubstringsIndexStrategy();
        var attributes = ['Name', 'DestinationId'];
        _.each(attributes, function (attribute) {
            tracking.data.search.assetGroups.addIndex(attribute);
        });
        tracking.data.search.assetGroups.addDocuments(tracking.data.groups);
        tracking.log('Asset Groups indexed for search filtering.');
    }

    function indexAssetsForSearch() {
        tracking.data.search.assets = new JsSearch.Search('Id');
        tracking.data.search.assets.indexStrategy = new JsSearch.AllSubstringsIndexStrategy();
        // primary attributes
        var primaryAttributes = ['Name', 'UniqueId', 'SecondaryUniqueId'];
        _.each(primaryAttributes, function (attribute) {
            tracking.data.search.assets.addIndex(attribute);
        });


        // add Device Name to assets for searching
        _.each(tracking.data.assets, function (asset) {
            var device = findDeviceById(asset.DeviceId);
            asset.DeviceName = device.Manufacturer + ': ' + device.Name;
        });

        // secondary attributes (extra fields, custom fields)
        var secondaryAttributes = [
            'DeviceName', 'Mission', 'Driver', 'PhoneNumber', 'PlateNumber', 'PhotoType', 'FuelEfficiency',
            'DrawLinesBetweenPositions', 'SnapLinesToRoads', 'MicroUseCorrectedCourse', 'PrioritizeIncomingMessages',
            'SoftwareVersion', 'VehicleMakeAndModel', 'VehiclePurchaseDate', 'VehicleVIN', 'Notes', 'VesselCallSign',
            'VesselClass', 'VesselFlagRegistry', 'VesselIMONumber', 'VesselName', 'VesselSkipper', 'VesselTonnage',
            'VesselMMSI', 'Configuration', 'CustomMessage', 'DestinationId', 'IMEI', 'SerialNumber', 'SIMICCID',
            'SIMPhoneNumber', 'SIMPIN', 'SIMPINUnlock', 'SIMProvider', 'LicenseNumber', 'NationalIdentityCardNumber',
            'MobileUsername',
            ['Attributes', 'Value'] // custom attributes
        ];
        _.each(secondaryAttributes, function (attribute) {
            tracking.data.search.assets.addIndex(attribute);
        });

        tracking.data.search.assets.addDocuments(tracking.data.assets);
        tracking.log('Assets indexed for search filtering.');
    }

    function indexPlacesForSearch() {
        tracking.data.search.places = new JsSearch.Search('Id');
        tracking.data.search.places.indexStrategy = new JsSearch.AllSubstringsIndexStrategy();
        var attributes = ['Name', 'Description', 'UniqueKey', 'Contact'];
        // todo: location?
        _.each(attributes, function (attribute) {
            tracking.data.search.places.addIndex(attribute);
        });
        tracking.data.search.places.addDocuments(tracking.data.places);
        tracking.log('Places indexed for search filtering.');
    }

    function indexFencesForSearch() {
        tracking.data.search.fences = new JsSearch.Search('Id');
        tracking.data.search.fences.indexStrategy = new JsSearch.AllSubstringsIndexStrategy();
        var attributes = ['Name', 'Description', 'UniqueKey', ['Attributes', 'Value']];
        // todo: location?
        _.each(attributes, function (attribute) {
            tracking.data.search.fences.addIndex(attribute);
        });
        tracking.data.search.fences.addDocuments(tracking.data.fences);
        tracking.log('Geofences indexed for search filtering.');
    }

    function indexSharedViewsForSearch() {
        tracking.data.search.sharedViews = new JsSearch.Search('Id');
        tracking.data.search.sharedViews.indexStrategy = new JsSearch.AllSubstringsIndexStrategy();
        var attributes = ['Name', 'Description', 'UniqueKey'];
        // todo: asset or group names?
        _.each(attributes, function (attribute) {
            tracking.data.search.sharedViews.addIndex(attribute);
        });
        tracking.data.search.sharedViews.addDocuments(tracking.data.sharedViews);
        tracking.log('Shared Views indexed for search filtering.');
    }

    function sortByName(a, b) {
        // if .localeCompare is not available?
        return a.Name.localeCompare(b.Name, tracking.language);
    }

    function sortByColorAndName(a, b) {
        // color
        if (a.ColorSorted !== undefined && b.ColorSorted !== undefined) {
            if (a.ColorSorted.h < b.ColorSorted.h)
            {
                return -1;
            } else if (a.ColorSorted.h > b.ColorSorted.h) {
                return 1;
            } else if (a.ColorSorted.lum < b.ColorSorted.lum) {
                return -1;
            } else if (a.ColorSorted.lum > b.ColorSorted.lum) {
                return 1;
            } else if (a.ColorSorted.l < b.ColorSorted.l) {
                return -1;
            } else if (a.ColorSorted.l > b.ColorSorted.l) {
                return 1;
            }
        }

        // name
        return sortByName(a, b);
    }

    function sortBySortOrderAndName(items, sortOrders) {
        return items.sort(function (a, b) {
            var aIndex = sortOrders.indexOf(a.Id);
            if (aIndex === -1) {
                aIndex = 99999;
            }
            var bIndex = sortOrders.indexOf(b.Id);
            if (bIndex === -1) {
                bIndex = 99999;
            }
            if (aIndex < bIndex) {
                return -1;
            } else if (aIndex > bIndex) {
                return 1;
            }

            return sortByName(a, b);
        });
    }

    function sortItemsWithDirection(items, sortFunction, sortDirection) {
        if (sortDirection === undefined || sortDirection === tracking.sortDirections.ASC) {
            return items.sort(function (a, b) {
                return sortFunction(a, b);
            });
        } else {
            return items.sort(function (a, b) {
                return sortFunction(b, a);
            });
        }
    }

    function sortByManufacturer(a, b) {
        var aDevice = findDeviceById(a.DeviceId);
        var bDevice = findDeviceById(b.DeviceId);
        var manufacturerSort = aDevice.Manufacturer.localeCompare(bDevice.Manufacturer, tracking.language);
        if (manufacturerSort === 0) {
            return sortByName(a, b);
        }
        return manufacturerSort;
    }

    function sortByDeviceType(a, b) {
        var aDevice = findDeviceById(a.DeviceId);
        var bDevice = findDeviceById(b.DeviceId);
        var deviceSort = aDevice.Name.localeCompare(bDevice.Name, tracking.language);
        if (deviceSort === 0) {
            return sortByName(a, b);
        }
        return deviceSort;
    }

    function sortItemsByMode(modeType, items, parentGroupId, itemType) {
        var mode = tracking.user.displayPreferences.sortMode[modeType];
        var direction = tracking.user.displayPreferences.sortDirection[modeType];
        switch (mode) {
            case tracking.sortModes.COLOR:
                return sortItemsWithDirection(items, sortByColorAndName, direction);
            case tracking.sortModes.CUSTOM:
                var itemSortOrders = tracking.user.displayPreferences.customSort[parentGroupId + '-' + itemType];
                if (itemSortOrders !== undefined) {
                    return sortBySortOrderAndName(items, itemSortOrders);
                } else {
                    return items.sort(sortByName);
                }
                break;
            case tracking.sortModes.MANUFACTURER:
                if (itemType === 'assets') {
                    return sortItemsWithDirection(items, sortByManufacturer, direction);
                } else {
                    return sortItemsWithDirection(items, sortByName, direction);
                }
                break;
            case tracking.sortModes.DEVICE_TYPE:
                if (itemType === 'assets') {
                    return sortItemsWithDirection(items, sortByDeviceType, direction);
                } else {
                    return sortItemsWithDirection(items, sortByName, direction);
                }
                break;
            case tracking.sortModes.ALPHABETICAL:
            default:
                return sortItemsWithDirection(items, sortByName, direction);
        }
    }

    function saveItemCustomSortOrder() {
        var customSortOrder = {};

        var groupIds = _.map(tracking.data.groups, 'Id');

        var panelTypes = ['assets', 'places', 'fences'];
        _.each(panelTypes, function (panelType) {
            customSortOrder[panelType + '-root-groups'] = [];
            var rootNode = document.getElementById(panelType + '-all');
            for (var i = 0; i < rootNode.childNodes.length; i++) {
                var groupId = rootNode.childNodes[i].id.substring(6);
                customSortOrder[panelType + '-root-groups'].push(groupId);
            }
            groupIds.push('all-' + panelType);
        });

        _.each(groupIds, function(groupId) {
            var groupContents = tracking.data.domNodes.groupContents[groupId];
            customSortOrder[groupId + '-groups'] = [];
            customSortOrder[groupId + '-assets'] = [];
            customSortOrder[groupId + '-places'] = [];
            customSortOrder[groupId + '-fences'] = [];
            var groupList = _.find(groupContents.childNodes, function (node) {
                return node.classList !== undefined && node.classList.contains('group-list-list');
            });
            if (groupList === undefined) {
                return;
            }
            var groupNodes = _.filter(groupList.childNodes, function (node) {
                return node.classList !== undefined && node.classList.contains('group');
            });
            var assetNodes = _.filter(groupList.childNodes, function (node) {
                return node.classList !== undefined && node.classList.contains('assets-item');
            });
            var placeNodes = _.filter(groupList.childNodes, function (node) {
                return node.classList !== undefined && node.classList.contains('places-item');
            });
            var fenceNodes = _.filter(groupList.childNodes, function (node) {
                return node.classList !== undefined && node.classList.contains('fences-item');
            });

            _.each(groupNodes, function(node, index) {
                var id = node.getAttribute('data-group-id');
                customSortOrder[groupId + '-groups'].splice(index, 0, id);
            });

            _.each(assetNodes, function (node, index) {
                var id = parseInt(node.getAttribute('data-asset-id'));
                customSortOrder[groupId + '-assets'].splice(index, 0, id);
            });

            _.each(placeNodes, function (node, index) {
                var id = parseInt(node.getAttribute('data-place-id'));
                customSortOrder[groupId + '-places'].splice(index, 0, id);
            });

            _.each(fenceNodes, function (node, index) {
                var id = node.getAttribute('data-fence-id');
                customSortOrder[groupId + '-fences'].splice(index, 0, id);
            });
        });

        tracking.user.displayPreferences.customSort = customSortOrder;
        saveDisplayPreferences();
    }

    function toggleItemSorting(type, isEnabled) {
        // enable/disable custom item sorting for the passed item type
        // todo: lookup the current state of sorting enabled
        // and do nothing if it has not changed

        // groups are currently asset specific
        if (type === 'assets') {
            _.each(tracking.data.domNodes.groups, function (itemNode) {
                var dragNode = itemNode.querySelector('.group-drag');
                if (isEnabled) {
                    dragNode.classList.remove('disabled');
                    dragNode.parentNode.classList.add('drag-enabled');
                } else {
                    dragNode.classList.add('disabled');
                    dragNode.parentNode.classList.remove('drag-enabled');
                }
            });
        }
        _.each(tracking.data.domNodes[type], function (typeNodes) {
            // assets is an array since it can be in multiple groups, others are not, for now
            if (!_.isArray(typeNodes)) {
                typeNodes = [ typeNodes ];
            }
            _.each(typeNodes, function (itemNode) {
                var dragNode = itemNode.querySelector('.item-drag');
                if (isEnabled) {
                    dragNode.classList.remove('disabled');
                    dragNode.parentNode.classList.add('drag-enabled');
                } else {
                    dragNode.classList.add('disabled');
                    dragNode.parentNode.classList.remove('drag-enabled');
                }
            });
        });
    }

    function sortModeUpdated(typeUpdated) {
        var types = ['assets', 'places', 'fences'];
        if (typeUpdated !== undefined) {
            types = [typeUpdated];
        }

        _.each(types, function(itemType) {
            var list = document.getElementById(itemType + '-filter-options-list');
            var listOptions = list.querySelectorAll('.dropdown-item[data-option-type="sorting"]');
            _.each(listOptions, function (option) {
                var sortMode = parseInt(option.getAttribute('data-sort'));
                if (sortMode !== tracking.user.displayPreferences.sortMode[itemType]) {
                    option.classList.remove('active');
                    option.classList.remove('sort-asc');
                    option.classList.remove('sort-desc');
                } else {
                    option.classList.add('active');
                    option.classList.remove('sort-asc');
                    option.classList.remove('sort-desc');
                    if (tracking.user.displayPreferences.sortDirection[itemType] === tracking.sortDirections.ASC) {
                        option.classList.add('sort-asc');
                    } else {
                        option.classList.add('sort-desc');
                    }
                }
            });

            //var isSortingEnabled = tracking.user.displayPreferences.sortMode.assets === tracking.sortModes.ALPHABETICAL
            //    || tracking.user.displayPreferences.sortMode.assets === tracking.sortModes.CUSTOM;
            var isSortingEnabled = tracking.user.displayPreferences.sortMode[itemType] === tracking.sortModes.CUSTOM;
            toggleItemSorting(itemType, isSortingEnabled);
        });
    }

    function changeItemSort(type, newMode) {
        var currentMode = tracking.user.displayPreferences.sortMode[type];
        var currentDirection = tracking.user.displayPreferences.sortDirection[type];
        if (newMode === currentMode) {
            // toggle the sort direction, if possible (currently everything except custom)
            if (currentMode === tracking.sortModes.CUSTOM) {
                return;
            }
            if (currentDirection === tracking.sortDirections.ASC) {
                tracking.user.displayPreferences.sortDirection[type] = tracking.sortDirections.DESC;
            } else {
                tracking.user.displayPreferences.sortDirection[type] = tracking.sortDirections.ASC;
            }
        } else {
            // always start ascending in new mode
            tracking.user.displayPreferences.sortDirection[type] = tracking.sortDirections.ASC;
        }
        tracking.user.displayPreferences.sortMode[type] = newMode;
        sortModeUpdated(type);

        resortItemGroup('all-' + type);

        if (type === 'assets') {
            // sort items in groups - currently asset specific
            _.each(tracking.data.groups, function (group) {
                resortItemGroup(group.Id);
            });

            // sort root groups - currently asset specific
            var rootGroups = _.filter(tracking.data.groups, function (group) {
                return group.ParentGroupId === null || group.ParentGroupId === undefined;
            });
            var sortedRootGroups = sortItemsByMode('assets', rootGroups, 'assets-root', 'groups');
            _.each(sortedRootGroups, function (group) {
                // root group should always be attached to parent node for this to work
                tracking.data.domNodes.groups[group.Id].parentNode.appendChild(tracking.data.domNodes.groups[group.Id]);
            });
        }
        saveDisplayPreferences();
    }

    function resortItemGroup(groupId) {
        var groupContents = tracking.data.domNodes.groupContents[groupId];
        if (groupContents === undefined) {
            return;
        }

        var groupList = _.find(groupContents.childNodes, function(node) {
            return node.classList !== undefined && node.classList.contains('group-list-list');
        });
        var groupNodes = {};
        var assetNodes = {};
        var fenceNodes = {};
        var placeNodes = {};
        var sharedViewNodes = {};
        var groups = [];
        var assets = [];
        var fences = [];
        var places = [];
        var sharedViews = [];
        if (groupList !== undefined) {
            _.each(groupList.childNodes, function(node) {
                if (node.classList !== undefined && node.classList.contains('group')) {
                    // subgroup
                    var id = node.getAttribute('data-group-id');
                    groupNodes[id] = node;
                    var group = findGroupById(id);
                    if (group !== null) {
                        groups.push(group);
                    }
                } else if (node.classList !== undefined && node.classList.contains('assets-item')) {
                    // asset
                    var id = parseInt(node.getAttribute('data-asset-id'));
                    assetNodes[id] = node;
                    var asset = findAssetById(id);
                    if (asset !== null) {
                        assets.push(asset);
                    }
                } else if (node.classList !== undefined && node.classList.contains('fences-item')) {
                    var id = node.getAttribute('data-fence-id');
                    fenceNodes[id] = node;
                    var fence = findFenceById(id);
                    if (fence !== null) {
                        fences.push(fence);
                    }
                } else if (node.classList !== undefined && node.classList.contains('places-item')) {
                    var id = parseInt(node.getAttribute('data-place-id'));
                    placeNodes[id] = node;
                    var place = findPlaceById(id);
                    if (place !== null) {
                        places.push(place);
                    }
                } else if (node.classList !== undefined && node.classList.contains('shared-views-item')) {
                    var id = parseInt(node.getAttribute('data-shared-view-id'));
                    sharedViewNodes[id] = node;
                    var sharedView = findSharedViewById(id);
                    if (sharedView !== null) {
                        sharedViews.push(sharedView);
                    }
                }
            });
        }

        // the reordering could be improved upon
        // by only moving nodes whose order change
        var groupsSorted = sortItemsByMode('assets', groups, groupId, 'groups');
        _.each(groupsSorted, function(group) {
            // move node to the bottom
            groupList.appendChild(groupNodes[group.Id]);
        });

        var assetsSorted = sortItemsByMode('assets', assets, groupId, 'assets');
        _.each(assetsSorted, function (asset) {
            groupList.appendChild(assetNodes[asset.Id]);
        });

        var fencesSorted = sortItemsByMode('fences', fences, groupId, 'fences');
        _.each(fencesSorted, function (fence) {
            groupList.appendChild(fenceNodes[fence.Id]);
        });

        var placesSorted = sortItemsByMode('places', places, groupId, 'places');
        _.each(placesSorted, function (place) {
            groupList.appendChild(placeNodes[place.Id]);
        });

        var sharedViewsSorted = sortItemsByMode('shared-views', sharedViews, groupId, 'shared-views');
        _.each(sharedViewsSorted, function (sharedView) {
            groupList.appendChild(sharedViewNodes[sharedView.Id]);
        });
    }

    function populateSidePanel() {
        var assetNodes = {};
        var groupNodes = {};

        var groupAssets = {
            'all-assets': {
                groups: [],
                assets: []
            }
        };

        // initialize groups
        _.each(tracking.data.groups, function (group) {
            groupAssets[group.Id] = { groups: [], assets: [] };
        });

        // assign subgroups to groups
        _.each(tracking.data.groups, function (group) {
            if (group.ParentGroupId !== null && groupAssets[group.ParentGroupId] !== undefined) {
                groupAssets[group.ParentGroupId].groups.push(group);
                var parentGroup = findGroupById(group.ParentGroupId);
                if (parentGroup !== null) {
                    parentGroup.GroupIds.push(group.Id);
                }
            }

            // render baseline group for dom
            groupNodes[group.Id] = createGroupNode(group, group.Type);
        });
        // render the All Assets group - now an actual inserted group
        //groupNodes['all-assets'] = createGroupNode({ Id: 'all-assets', Name: tracking.strings.ALL_ASSETS, Color: tracking.options.defaultColors.assets }, 'assets');

        // initialize group nodes cache and set colors for stylesheet
        _.each(groupNodes, function (group, key, list) {
            tracking.data.domNodes.groups[key] = groupNodes[key];
            var actualGroup = findGroupById(key);
            if (actualGroup !== null) {
                tracking.data.domNodes.groupColors[key] = actualGroup.Color;
            }

            // initialize sorting for group
            var groupContentsNode = groupNodes[key].querySelector('.group-contents');
            var groupContentsItems = groupContentsNode.querySelector('ul.group-list-list');
            $(groupContentsItems).sortable({
                axis: 'y',
                items: '> li.group',
                handle: '.group-drag',
                update: function (event, ui) {
                    saveItemCustomSortOrder();
                }
            });
            $(groupContentsNode).sortable({
                axis: 'y',
                items: '> .group-list-list > li.group-item',
                handle: '.item-drag',
                update: function (event, ui) {
                    saveItemCustomSortOrder();
                }
            });
        });

        var sortedAssets = sortItemsByMode('assets', tracking.data.assets, 'all-assets', 'assets');
        var primaryGroups = sortItemsByMode('assets', _.filter(tracking.data.groups, function (group) { return group.ParentGroupId === null || groupAssets[group.ParentGroupId] === undefined; }), 'assets-root', 'groups');

        // assign assets to groups
        groupAssets['all-assets'].assets = sortedAssets;
        _.each(tracking.data.assets, function (asset, index, list) {
            _.each(asset.GroupIds, function (groupId, ind, li) {
                if (groupAssets[groupId] !== undefined) {
                    groupAssets[groupId].assets.push(asset);
                }
            });

            // render baseline asset for dom
            assetNodes[asset.Id] = createAssetNode(asset);
        });

        // sort assets and subgroups within groups
        _.each(groupAssets, function (value, key, list) {
            list[key].assets = sortItemsByMode('assets', value.assets, key, 'assets');
            list[key].groups = sortItemsByMode('assets', value.groups, key, 'groups');
        });

        //console.log(groupAssets);

        _.each(groupAssets, function (group, key, list) {
            var groupContentsNode = groupNodes[key].querySelector('.group-contents');
            var groupContentsItems = groupContentsNode.querySelector('ul.group-list-list');
            var groupData = groupAssets[key];

            // add subgroup nodes to group dom
            _.each(groupData.groups, function (subGroup, index, list) {
                // groups can only be assigned to one parent group, so do not clone their nodes
                groupContentsItems.appendChild(groupNodes[subGroup.Id]);
            });

            // add asset nodes to group dom
            _.each(groupData.assets, function (asset, index, list) {
                // assets can be added to multiple groups, so they must be cloned
                var groupAsset = assetNodes[asset.Id].cloneNode(true);

                groupContentsItems.appendChild(groupAsset);
                if (tracking.data.domNodes.assets[asset.Id] === undefined) {
                    tracking.data.domNodes.assets[asset.Id] = [];
                }
                tracking.data.domNodes.assets[asset.Id].push(groupAsset);
            });

            // cache group content nodes so they can be detached/reattached to the dom
            // based on whether the group is expanded/visible
            tracking.data.domNodes.groupContents[key] = groupContentsNode;

            if (key === 'all-assets') {
                if (tracking.user.displayPreferences.hideAllAssets === true) {
                    groupContentsNode.parentNode.removeChild(groupContentsNode);
                    toggleGroupExpandedIcon(key, false);
                } else {
                    toggleGroupExpandedIcon(key, true);
                }
            } else if (!isItemIncluded(tracking.user.displayPreferences.expandedGroups, key)) {
                // only render group contents when the group is visible (expanded)
                groupContentsNode.parentNode.removeChild(groupContentsNode);
                toggleGroupExpandedIcon(key, false);
            } else {
                toggleGroupExpandedIcon(key, true);
            }
        });

        // toggle active groups within fragment
        // by default groups are inactive unless they contain at least one active asset
        // this isn't done on creation as it would be pretty tricky and not worth the effort
        _.each(groupAssets, function (group, key, list) {
            updateGroupVisibilityStatus(key);
        });

        var assetsGroupsFragment = document.createDocumentFragment();
        assetsGroupsFragment.appendChild(groupNodes['all-assets']);
        _.each(primaryGroups, function (value, key, list) {
            assetsGroupsFragment.appendChild(groupNodes[value.Id]);
        });

        // shared views - just like asset groups
        if (tracking.data.sharedViews.length === 0) {
            // no shared views
        }

        if (tracking.data.assets.length === 0) {
            document.getElementById('filter-assets').querySelector('.filter-box').classList.remove('is-visible');
        } else {
            document.getElementById('filter-assets').querySelector('.filter-box').classList.add('is-visible');
        }

        // journeys - just like asset groups
        if (tracking.data.journeys.length === 0) {
            // no journeys, add to journeys-all?
            var noExtrasNode = document.createElement('li');
            noExtrasNode.className = 'toggle-content no-items is-visible';
            noExtrasNode.setAttribute('id', 'no-journeys');
            var noExtrasText = document.createElement('span');
            noExtrasText.textContent = tracking.strings.NO_JOURNEYS;
            noExtrasNode.appendChild(noExtrasText);
            document.getElementById('journeys-all').appendChild(noExtrasNode);
            //document.getElementById('filter-journeys').querySelector('.filter-box').classList.remove('is-visible');
        }
        _.each(tracking.data.journeys, function (item) {
            var journeyAsset = findAssetById(item.AssetId);
            if (journeyAsset === null) {
                return;
            }
            var groupId = 'journey-' + item.Id;
            var extraGroupNode = createGroupNode({ Id: groupId, Name: item.Name, Color: item.Color}, 'journeys');
            tracking.data.domNodes.groups[groupId] = extraGroupNode;

            var extraGroupContentsNode = extraGroupNode.querySelector('.group-contents');
            var extraGroupContentsItems = extraGroupContentsNode.querySelector('ul.group-list-list');
            var noExtrasNode = document.createElement('li');
            noExtrasNode.className = 'toggle-content no-items';
            if (item.Trips.length === 0) {
                noExtrasNode.className += ' is-visible';
                //document.getElementById('filter-journeys').querySelector('.filter-box').classList.remove('is-visible');
            }
            noExtrasNode.setAttribute('id', 'no-' + groupId);
            var noExtrasText = document.createElement('span');
            noExtrasText.textContent = tracking.strings.NO_TRIPS;
            noExtrasNode.appendChild(noExtrasText);
            extraGroupContentsItems.appendChild(noExtrasNode);

            var journey = item;
            _.each(item.Trips, function (item, index, list) {
                var extraNode = createTripNode(journey, item, journeyAsset);
                tracking.data.domNodes.trips[item.Id] = extraNode;
                extraGroupContentsItems.appendChild(extraNode);
            });

            var extraAllItems = extraGroupNode.getElementsByClassName('journeys-item').length;
            var extraActiveItems = extraGroupNode.getElementsByClassName('journeys-item active').length;
            if (extraActiveItems > 0) {
                var extraVisibility = extraGroupNode.querySelector('.showhide');

                if (extraAllItems !== extraActiveItems) {
                    extraVisibility.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#indeterminate');
                    extraVisibility.classList.add('indeterminate');
                } else {
                    extraVisibility.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#visible');
                    extraVisibility.classList.add('active');
                }
                extraGroupNode.classList.add('active');
            }
            tracking.data.domNodes.groupContents[groupId] = extraGroupContentsNode;
            if (item.Color !== undefined && item.Color !== null) {
                tracking.data.domNodes.groupColors[groupId] = item.Color;
            }
            if (!isItemIncluded(tracking.user.displayPreferences.expandedGroups, groupId)) {
                // only render group contents when the group is visible (expanded)
                extraGroupContentsNode.parentNode.removeChild(extraGroupContentsNode);
                toggleGroupExpandedIcon(groupId, false);
            } else {
                toggleGroupExpandedIcon(groupId, true);
            }

            updateGroupVisibilityStatus(groupId);

            var allJourneys = document.getElementById('journeys-all');
            allJourneys.appendChild(extraGroupNode);
        });

        var extraGroups = [
            {
                type: 'fences',
                createNodeFunction: createFenceNode,
                name: tracking.strings.ALL_GEOFENCES,
                color: null,
                emptyText: tracking.strings.NO_FENCES,
                items:  sortItemsByMode('fences', tracking.data.fences, 'all-fences', 'fences'),
                nodeList: tracking.data.domNodes.fences
            },
            {
                type: 'places',
                createNodeFunction: createPlaceNode,
                name: tracking.strings.ALL_PLACES,
                color: null,
                emptyText: tracking.strings.NO_PLACES,
                items: sortItemsByMode('places', tracking.data.places, 'all-places', 'places'),
                nodeList: tracking.data.domNodes.places
            },
            {
                type: 'shared-views',
                createNodeFunction: createSharedViewNode,
                name: tracking.strings.ALL_SHARED_VIEWS,
                color: null,
                emptyText: tracking.strings.NO_SHARED_VIEWS,
                noItemsText: tracking.strings.NO_SHARED_VIEWS_HINT,
                items: sortItemsByMode('shared-views', tracking.data.sharedViews, 'all-shared-views', 'shared-views'),
                nodeList: tracking.data.domNodes.sharedViews
            }
        ];

        _.each(extraGroups, function (group, index, list) {
            var groupId = 'all-' + group.type;
            var extraGroupNode = createGroupNode({ Id: groupId, Name: group.name, Color: group.color }, group.type);
            tracking.data.domNodes.groups[groupId] = extraGroupNode;
            var extraGroupContentsNode = extraGroupNode.querySelector('.group-contents');
            var extraGroupContentsItems = extraGroupContentsNode.querySelector('ul.group-list-list');
            var noExtrasNode = document.createElement('li');
            noExtrasNode.className = 'toggle-content no-items';
            if (group.items.length === 0) {
                noExtrasNode.className += ' is-visible';
                document.getElementById('filter-' + group.type).querySelector('.filter-box').classList.remove('is-visible');
            } else {
                document.getElementById('filter-' + group.type).querySelector('.filter-box').classList.add('is-visible');
            }
            noExtrasNode.setAttribute('id', 'no-' + groupId);

            if (group.type == 'shared-views' && group.items.length === 0) {
                document.getElementById('no-shared-views').classList.add('is-visible');
                document.getElementById('shared-views-all').classList.remove('is-visible');
                //document.getElementById('filter-shared-views').classList.remove('is-visible');
            }
            
            var noExtrasText = document.createElement('span');
            noExtrasText.textContent = group.emptyText;
            noExtrasNode.appendChild(noExtrasText);
            extraGroupContentsItems.appendChild(noExtrasNode);

            _.each(group.items, function (item, index, list) {
                var extraNode = group.createNodeFunction(item);
                group.nodeList[item.Id] = extraNode;
                extraGroupContentsItems.appendChild(extraNode);
            });

            var extraAllItems = extraGroupNode.getElementsByClassName(group.type + '-item').length;
            var extraActiveItems = extraGroupNode.getElementsByClassName(group.type + '-item active').length;
            if (extraActiveItems > 0) {
                var extraVisibility = extraGroupNode.querySelector('.showhide');

                if (extraAllItems !== extraActiveItems) {
                    extraVisibility.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#indeterminate');
                    extraVisibility.classList.add('indeterminate');
                } else {
                    extraVisibility.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#visible');
                    extraVisibility.classList.add('active');
                }
                extraGroupNode.classList.add('active');
            }
            tracking.data.domNodes.groupContents[groupId] = extraGroupContentsNode;
            if (group.color !== undefined && group.color !== null) {
                tracking.data.domNodes.groupColors[groupId] = group.color;
            }
            if (!isItemIncluded(tracking.user.displayPreferences.expandedGroups, 'all-' + group.type)) {
                // only render group contents when the group is visible (expanded)
                extraGroupContentsNode.parentNode.removeChild(extraGroupContentsNode);
                toggleGroupExpandedIcon(groupId, false);
            } else {
                toggleGroupExpandedIcon(groupId, true);
            }

            updateGroupVisibilityStatus(groupId);

            // insert into separate panel/container
            var container = document.getElementById(group.type + '-all');
            container.appendChild(extraGroupNode);

            $(extraGroupContentsNode).sortable({
                axis: 'y',
                items: '> .group-list-list > li.group-item',
                handle: '.item-drag',
                update: function (event, ui) {
                    saveItemCustomSortOrder();
                }
            });
        });

        var assetGroupsContainer = document.getElementById('assets-all');
        assetGroupsContainer.appendChild(assetsGroupsFragment);
        $(assetGroupsContainer).sortable({
            axis: 'y',
            items: '> li.group',
            handle: '.group-drag',
            update: function (event, ui) {
                saveItemCustomSortOrder();
            }
        });
    }

    function createGroupColorStyles() {
        var existingStyle = document.getElementById('group-colors-style');
        var colors = '';
        _.each(tracking.data.domNodes.groupColors, function (elem, index, list) {
            //colors += '#group-' + index + ' .group-color, #group-' + index + ' > .list-item-action.active { color: ' + elem + '; border-color: ' + elem + '; }' + "\n";
            //colors += '#group-' + index + ' > .group-header, #group-' + index + ' > .group-header > .list-item-action.active { color: ' + elem + '; border-color: ' + elem + '; }' + "\n";
            //colors += '#group-' + index + ' .group-color { color: ' + elem + '; border-color: ' + elem + '; }' + "\n";
            //colors += '#group-' + index + ' > .group-header { color: ' + elem + '; border-color: ' + elem + '; }' + "\n";
            colors += '#group-' + index + ' > .group-header { border-color: ' + elem + '; }' + "\n";
            //colors += '#group-' + index + ' .list-item-action.showhide.active { color: ' + elem + '; }' + "\n";
        });
        if (existingStyle === null) {
            var groupColors = document.createElement('style');
            groupColors.id = "group-colors-style";
            groupColors.type = "text/css";
            groupColors.innerHTML = colors;
            document.getElementsByTagName('head')[0].appendChild(groupColors);
        } else {
            existingStyle.innerHTML = colors;
        }
    }

    function createGroupNode(group, type) {
        var groupDto = _.map([group], function (item, index, groups) {
            var isFavorite = false;
            return {
                id: item.Id,
                isFavorite: false,
                isVisible: isItemIncluded(tracking.user.displayPreferences.expandedGroups, item.Id),
                color: item.Color,
                name: item.Name,
                description: '',
                type: type,
                notifications: {
                    symbol: (tracking.user.isAnonymous ? (tracking.options.allowAnonymousMessaging ? 'notifications-sh' : 'notifications-sh-nc') : 'notifications')
                }
            };
        });
        var html = tracking.templates.assetGroup(groupDto[0]);
        var item = $(html)[0];
        var noSettingsGroups = ['all-fences', 'all-places', 'all-shared-views'];
        if (_.indexOf(noSettingsGroups, group.Id) !== -1) {
            var groupSettings = item.querySelector('.item-settings');
            groupSettings.parentNode.removeChild(groupSettings);
        }
        if (type !== 'assets' && type !== 'journeys') {
            var groupIndicators = item.querySelector('.asset-indicators');
            groupIndicators.parentNode.removeChild(groupIndicators);
        }
        return item;
    }

    function createAssetGroupNode(group) {
        //var li = '<li class="group assets group-list-item" id="group-' + group.Id + '" style="border-color: ' + group.Color + ';">' +
        //    '<input type="checkbox" class="showhide group grouping" data-group-id="' + group.Id + '" />' +
        //    '<a href="#" class="group"><span>' + group.Name + '</span></a>' +
        //    '<div class="edit-group"><a href="#"><span class="t-icon t-icon-context"></span></a></div><ul class="group-list-list toggle-content"></ul></li>'
        //    ;

        var groupLi = document.createElement('li');
        groupLi.className = 'group assets group-list-item';
        groupLi.setAttribute('id', 'group-' + group.Id);
        groupLi.style.cssText = 'border-color: ' + group.Color + ';';
        var groupShowHide = document.createElement('input');
        groupShowHide.setAttribute('type', 'checkbox');
        groupShowHide.className = 'showhide group grouping';
        groupShowHide.setAttribute('data-group-id', group.Id);
        //groupShowHide.setAttribute('checked', 'checked');
        var groupNameLink = document.createElement('a');
        groupNameLink.className = 'group';
        groupNameLink.setAttribute('href', '#');
        var groupName = document.createElement('span');
        groupName.textContent = group.Name;
        //var groupEdit = document.createElement('div');
        //groupEdit.className = 'edit-group';
        var groupEditLink = document.createElement('a');
        groupEditLink.setAttribute('href', '#');
        groupEditLink.className = 'edit-group t-icon t-icon-context';
        //var groupEditIcon = document.createElement('span');
        //groupEditIcon.className = 't-icon t-icon-context';
        var groupContents = document.createElement('ul');
        groupContents.className = 'group-list-list group-sortable';

        //groupEditLink.appendChild(groupEditIcon);
        //groupEdit.appendChild(groupEditLink);
        groupNameLink.appendChild(groupName);

        groupLi.appendChild(groupShowHide);
        groupLi.appendChild(groupNameLink);
        if (group.Id !== 'all') {
            //groupLi.appendChild(groupEdit);
            groupLi.appendChild(groupEditLink);
        } else {
            // special handling for all assets group
            var addAsset = document.createElement('div');
            addAsset.className = 'add-asset';
            var addAssetLink = document.createElement('a');
            addAssetLink.setAttribute('href', '#');
            addAssetLink.setAttribute('id', 'add-asset');
            addAssetLink.className = 'add-asset';
            addAssetLink.textContent = tracking.strings.ADD_ASSET;
            var addAssetIcon = document.createElement('span');
            addAssetIcon.className = 't-icon t-icon-add-brown';
            addAssetLink.appendChild(addAssetIcon);
            addAsset.appendChild(addAssetLink);
            groupLi.appendChild(addAsset);
        }
        groupLi.appendChild(groupContents);

        return groupLi;
    }

    function createPlaceNode(place) {
        var color = 'red';
        if (place.Color != null) {
            color = place.Color;
        }

        var isActive = !isItemIncluded(tracking.user.displayPreferences.hiddenPlaces, place.Id);
        if (isActive) {
            tracking.data.visible.places.push(place.Id);
        }
        var alpha = (isActive ? null : 50);
        var imagePath = createMarkerPath('Generic', color, null, alpha, null, true);
        ////var li = $j('<li class="places-item" data-place-id="' + place.Id + '" style="background-image: url(' + imagePath + '); background-position: 10px -6px; background-repeat: no-repeat;">' +
        ////    '<input type="checkbox" class="showhide" data-place-id="' + place.Id + '" id="place-' + place.Id + '" checked="checked" /><span class="place-name" for="place-' + place.Id + '">' + place.Name + '</span>' +
        ////    '<div class="edit-place"><a href="#"><span class="t-icon t-icon-context"></span></a></div>' +
        ////    '</li>');

        var placeDto = _.map([place], function (item, index, places) {
            return {
                id: item.Id,
                name: item.Name,
                color: item.Color,
                description: item.Description,
                icon: imagePath,
                isVisible: !isItemIncluded(tracking.user.displayPreferences.hiddenPlaces, item.Id),
                visibilityClass: !isItemIncluded(tracking.user.displayPreferences.hiddenPlaces, item.Id) ? 'active' : 'disabled'
            };
        });

        var html = tracking.templates.place(placeDto[0]);
        return $(html)[0];

        var placeLi = document.createElement('li');
        placeLi.className = 'places-item group-item' + (isActive ? ' active' : ' disabled');
        placeLi.setAttribute('data-place-id', place.Id);
        placeLi.style.cssText = 'background-image: url(' + imagePath + ');'; // background-position: 10px -6px; background-repeat: no-repeat;';
        var placeShowHide = document.createElement('input');
        placeShowHide.setAttribute('type', 'checkbox');
        placeShowHide.className = 'showhide';
        placeShowHide.setAttribute('data-place-id', place.Id);
        placeShowHide.setAttribute('id', 'place-' + place.Id);
        if (isActive) {
            placeShowHide.setAttribute('checked', 'checked');
        }
        var placeName = document.createElement('span');
        placeName.className = 'place-name';
        placeName.setAttribute('for', 'place-' + place.Id);
        placeName.textContent = place.Name;
        //var placeEdit = document.createElement('div');
        //placeEdit.className = 'edit-place';
        var placeEditLink = document.createElement('a');
        placeEditLink.setAttribute('href', '#');
        placeEditLink.className = 'edit-place t-icon t-icon-context'
        //var placeEditIcon = document.createElement('span');
        //placeEditIcon.className = 't-icon t-icon-context';
        //placeEditLink.appendChild(placeEditIcon);
        //placeEdit.appendChild(placeEditLink);

        placeLi.appendChild(placeShowHide);
        placeLi.appendChild(placeName);
        //placeLi.appendChild(placeEdit);
        placeLi.appendChild(placeEditLink);

        return placeLi;
    }

    function getSharedViewNodeDescription(sharedView) {
        var description = tracking.strings.ALL_TIME;
        if (sharedView.FromDateEpoch !== null || sharedView.ToDateEpoch !== null) {
            var from = moment(sharedView.FromDateEpoch);
            var to = moment(sharedView.ToDateEpoch);
            if (from.isValid() && to.isValid()) {
                description = from.format(tracking.user.shortDateFormat) + ' - ' + to.format(tracking.user.shortDateFormat);
            } else if (from.isValid()) {
                description = from.format(tracking.user.shortDateFormat) + ' - ';
            } else if (to.isValid()) {
                description = ' - ' + to.format(tracking.user.shortDateFormat);
            }
        }
        return description;
    }

    function createSharedViewNode(sharedView) {
        if (sharedView === undefined || sharedView === null) {
            return null;
        }

        var color = getItemHexColor(sharedView);
        var description = getSharedViewNodeDescription(sharedView);
        var imagePath = createMarkerPath('SharedView', color, null, null, null, false);
        var sharedViewDto = _.map([sharedView], function (item) {
            return {
                id: item.Id,
                name: item.Name,
                color: item.Color,
                description: description,
                icon: imagePath,
                visibilityClass: item.IsEnabled ? 'active' : 'disabled'
            };
        });
        var html = tracking.templates.sharedView(sharedViewDto[0]);
        return $(html)[0];
    }

    function createFenceNode(fence) {
        if (fence == null) {
            return null;
        }

        var isActive = !isItemIncluded(tracking.user.displayPreferences.hiddenFences, fence.Id);
        if (isActive) {
            tracking.data.visible.fences.push(fence.Id);
        }
        var color = getFenceColor(fence);
        var alpha = (isActive ? null : 50);
        var imagePath = createMarkerPath('Fence', color, null, alpha, null, true);

        var fenceDto = _.map([fence], function (item) {
            return {
                id: item.Id,
                name: item.Name,
                color: item.Color,
                description: item.Description,
                icon: imagePath,
                isVisible: !isItemIncluded(tracking.user.displayPreferences.hiddenFences, item.Id),
                visibilityClass: !isItemIncluded(tracking.user.displayPreferences.hiddenFences, item.Id) ? 'active' : 'disabled'
            };
        });

        var html = tracking.templates.geofence(fenceDto[0]);
        return $(html)[0];


        var fenceLi = document.createElement('li');
        fenceLi.className = 'fences-item group-item' + (isActive ? ' active' : ' disabled');
        fenceLi.setAttribute('data-fence-id', fence.Id);
        fenceLi.style.cssText = 'background-image: url(' + imagePath + ');'; // background-position: 10px -6px; background-repeat: no-repeat;';
        var fenceShowHide = document.createElement('input');
        fenceShowHide.setAttribute('type', 'checkbox');
        fenceShowHide.className = 'showhide';
        fenceShowHide.setAttribute('data-fence-id', fence.Id);
        fenceShowHide.setAttribute('id', 'fence-' + fence.Id);
        if (isActive) {
            fenceShowHide.setAttribute('checked', 'checked');
        }
        var fenceName = document.createElement('span');
        fenceName.className = 'fence-name';
        fenceName.setAttribute('for', 'fence-' + fence.Id);
        fenceName.setAttribute('href', '#');
        fenceName.textContent = fence.Name;
        //var fenceEdit = document.createElement('div');
        //fenceEdit.className = 'edit-fence';
        var fenceEditLink = document.createElement('a');
        fenceEditLink.setAttribute('href', '#');
        fenceEditLink.className = 'edit-fence t-icon t-icon-context'
        //var fenceEditIcon = document.createElement('span');
        //fenceEditIcon.className = 't-icon t-icon-context';
        //fenceEditLink.appendChild(fenceEditIcon);
        //fenceEdit.appendChild(fenceEditLink);

        fenceLi.appendChild(fenceShowHide);
        fenceLi.appendChild(fenceName);
        //fenceLi.appendChild(fenceEdit);
        fenceLi.appendChild(fenceEditLink);

        return fenceLi;
    }

    function createTripNode(journey, trip, asset) {
        if (trip === null || asset === null || journey === null) {
            return null;
        }
        var isActive = tracking.user.isAnonymous ? true : isItemIncluded(tracking.user.displayPreferences.visibleTrips, trip.Id);
        if (isActive) {
            if (_.indexOf(tracking.data.visible.trips, trip.Id) === -1) {
                tracking.data.visible.trips.push(trip.Id);
            }
        }
        var alpha = (isActive ? null : 50);
        var description = '';
        if (trip.FromEpoch !== undefined && trip.FromEpoch !== null) {
            description = moment(trip.FromEpoch).format('L');
        }
        description += ' - ';
        if (trip.ToEpoch !== undefined && trip.ToEpoch !== null) {
            description += moment(trip.ToEpoch).format('L');
        }

        var tripDto = _.map([trip], function (item, index, trips) {
            return {
                id: item.Id,
                name: item.Name,
                description: description,
                journeyId: journey.Id,
                color: asset.Color,
                icon: createMarkerPath(asset.Class, asset.Color, null, alpha, asset.Id, true),
                isVisible: isItemIncluded(tracking.user.displayPreferences.visibleTrips, item.Id),
                visibilityClass: isItemIncluded(tracking.user.displayPreferences.visibleTrips, item.Id) ? 'active' : 'disabled',
                serviceClass: '',
                location: {
                    address: null,
                    latitude: null,
                    longitude: null,
                    time: null,
                    speed: null
                },
                notifications: {
                    symbol: (tracking.user.isAnonymous ? (tracking.options.allowAnonymousMessaging ? 'notifications-sh' : 'notifications-sh-nc'): 'notifications'),
                    alerts: 0,
                    events: 0,
                    status: 0,
                    messages: 0
                }
            };
        });

        var html = tracking.templates.trip(tripDto[0]);
        return $(html)[0];
    }

    function createAssetNode(asset) {
        if (asset == null)
            return null;
        var isActive = !isItemIncluded(tracking.user.displayPreferences.hiddenAssets, asset.Id);
        if (isActive) {
            if (_.indexOf(tracking.data.visible.assets, asset.Id) === -1) {
                tracking.data.visible.assets.push(asset.Id);
            }
        }
        var alpha = (isActive ? null : 50);

        var assetDto = _.map([asset], function (item, index, assets) {
            return {
                id: item.Id,
                name: item.Name,
                color: item.Color,
                icon: createMarkerPath(item.Class, item.Color, null, alpha, item.Id, true),
                isVisible: !isItemIncluded(tracking.user.displayPreferences.hiddenAssets, item.Id),
                visibilityClass: !isItemIncluded(tracking.user.displayPreferences.hiddenAssets, item.Id) ? 'active' : 'disabled',
                serviceClass: item.IsOutOfService ? 'noservice' : '',
                location: {
                    address: null,
                    latitude: null,
                    longitude: null,
                    time: null,
                    speed: null
                },
                notifications: {
                    symbol: (tracking.user.isAnonymous ? (tracking.options.allowAnonymousMessaging ? 'notifications-sh' : 'notifications-sh-nc') : 'notifications'),
                    alerts: 0,
                    events: 0,
                    status: 0,
                    messages: 0
                }
            };
        });

        var html = tracking.templates.asset(assetDto[0]);
        return $(html)[0];

        var device = tracking.data.devicesById[asset.DeviceId];

        var assetDto = {
            Id: asset.Id,
            Name: asset.Name,
            ServiceClass: asset.IsOutOfService ? ' noservice' : '',
            SupportsMessaging: (device.SupportsMessaging && !tracking.user.isAnonymous),
            ImagePath: createMarkerPath(asset.Class, asset.Color, null, alpha, asset.Id, true),
            IsActive: isActive
        };

        var mailbox = document.createDocumentFragment();
        if (assetDto.SupportsMessaging) {
            //<div class="mailbox"><a href="#"><span class="t-icon t-icon-mail"></span></a></div>
            //mailbox = document.createElement('div');
            //mailbox.className = 'mailbox';
            var box = document.createElement('a');
            box.setAttribute('href', '#');
            box.className = 'mailbox t-icon t-icon-mail';
            //var icon = document.createElement('span');
            //icon.className = 't-icon t-icon-mail';
            //link.appendChild(icon);
            mailbox.appendChild(box);
        }
        var serviceClass = '';
        if (asset.IsOutOfService) {
            serviceClass = ' noservice';
        }

        var assetLi = document.createElement('li');
        assetLi.className = 'assets-item group-item asset-' + assetDto.Id + assetDto.ServiceClass + (!assetDto.IsActive ? ' disabled' : ' active');
        assetLi.setAttribute('data-asset-id', assetDto.Id);
        assetLi.style.cssText = 'background-image: url(' + assetDto.ImagePath + ');';
        var assetShowHide = document.createElement('input');
        assetShowHide.setAttribute('type', 'checkbox');
        assetShowHide.className = 'showhide';
        assetShowHide.setAttribute('data-asset-id', assetDto.Id);
        if (assetDto.IsActive) {
            assetShowHide.setAttribute('checked', 'checked');
        }
        var assetName = document.createElement('a');
        assetName.className = 'asset-name';
        assetName.setAttribute('for', 'asset-' + assetDto.Id);
        assetName.setAttribute('href', '#');
        assetName.textContent = assetDto.Name;
        //var assetTreeControl = document.createElement('div');
        //assetTreeControl.className = 'tree-control toggle-content is-visible';
        var assetTreeControlLink = document.createElement('a');
        assetTreeControlLink.setAttribute('href', '#');
        assetTreeControlLink.className = 'tree-toggle show recent-positions-no toggle-content';
        //var assetEdit = document.createElement('div');
        //assetEdit.className = 'edit';
        var assetEditLink = document.createElement('a');
        assetEditLink.setAttribute('href', '#');
        assetEditLink.className = 'edit t-icon t-icon-context'
        //var assetEditIcon = document.createElement('span');
        //assetEditIcon.className = 't-icon t-icon-context';
        var assetIndicators = document.createElement('div');
        assetIndicators.className = 'indicators';

        //assetEditLink.appendChild(assetEditIcon);
        //assetEdit.appendChild(assetEditLink);

        //assetTreeControl.appendChild(assetTreeControlLink);
        assetIndicators.appendChild(mailbox);

        assetLi.appendChild(assetShowHide);
        assetLi.appendChild(assetName);
        //assetLi.appendChild(assetTreeControl);
        assetLi.appendChild(assetTreeControlLink);
        //assetLi.appendChild(assetEdit);
        assetLi.appendChild(assetEditLink);
        assetLi.appendChild(assetIndicators);

        return assetLi;
    }

    function addAssetGroup(group, groupAssetIds) {
        var groupNode = createGroupNode(group, 'assets');
        var groupContentsNode = groupNode.querySelector('.group-contents');
        var groupContentsItems = groupContentsNode.querySelector('ul.group-list-list');

        // add assets for group
        var groupAssets = _.filter(tracking.data.assets, function (asset) { return _.indexOf(groupAssetIds, asset.Id) !== -1; }).sort(sortByName);
        _.each(groupAssets, function (asset) {
            asset.GroupIds.push(group.Id);
            var groupAssetNode = tracking.data.domNodes.assets[asset.Id][0].cloneNode(true);
            tracking.data.domNodes.assets[asset.Id].push(groupAssetNode);
            groupContentsItems.appendChild(groupAssetNode);
        });

        // cache group content nodes so they can be detached/reattached to the dom
        // based on whether the group is expanded/visible
        tracking.data.domNodes.groups[group.Id] = groupNode;
        tracking.data.domNodes.groupContents[group.Id] = groupContentsNode;

        // add group to appropriate place
        var isGroupAdded = false;
        if (group.ParentGroupId != null) {
            var parentGroup = findGroupById(group.ParentGroupId);
            if (parentGroup !== null) {
                var parentGroupContents = tracking.data.domNodes.groupContents[parentGroup.Id];
                // place in appropriate order within group's subgroups
                parentGroup.GroupIds.push(group.Id);
                if (parentGroup.GroupIds.length == 1) {
                    parentGroupContents.insertBefore(groupNode, parentGroupContents.firstChild);
                } else {
                    var subGroups = _.filter(tracking.data.groups, function (item) { return _.indexOf(parentGroup.GroupIds, item.Id) !== -1; }).sort(sortByName);
                    var groupIndex = _.indexOf(subGroups, group);
                    parentGroupContents.insertBefore(groupNode, parentGroupContents.children[groupIndex]);
                }
            }
        } else {
            // add to root groups listing
            var assetGroupsContainer = document.getElementById('assets-all');//.querySelector('ul.group-list');
            var subGroups = _.filter(tracking.data.groups, function (item) { return item.ParentGroupId == null; }).sort(sortByName);
            if (subGroups.length > 1) {
                var groupIndex = _.indexOf(subGroups, group);
                assetGroupsContainer.insertBefore(groupNode, assetGroupsContainer.children[groupIndex + 1]);
            } else {
                assetGroupsContainer.insertBefore(groupNode, assetGroupsContainer.children[1]);
            }
        }
        tracking.data.domNodes.groupColors[group.Id] = group.Color;
        updateGroupVisibilityStatus(group.Id);
        createGroupColorStyles();
        //if (group == null)
        //    return;
        //var li = createAssetGroupNode(group);
        //if (group.ParentGroupId == null) {
        //    // root group
        //    var geofences = document.getElementById('all-fences');
        //    geofences.parentNode.insertBefore(li, geofences);
        //} else {
        //    // find parent and place under
        //    var parent = document.getElementById('group-' + group.ParentGroupId).querySelector('ul');
        //    parent.appendChild(li);
        //}

        //tracking.data.domNodes.groups[group.Id] = li;

        //// sub groups
        //for (var j = 0; j < group.Groups.length; j++) {
        //    renderGroup(group.Groups[j]);
        //}
    }

    function sortItemGroups(groups) {
        // reset groups
        var groupsById = _.keyBy(groups, 'Id');
        for (var i = 0; i < groups.length; i++) {
            groups[i].Groups = [];
        }

        var sortedGroups = groups.sort(function (a, b) { return a.Name.localeCompare(b.Name); });
        var rootGroups = { Groups: [] };
        for (var i = 0; i < sortedGroups.length; i++) {
            var group = sortedGroups[i];
            if (group.ParentGroupId == null
                || groupsById[group.ParentGroupId] === undefined) {
                sortItemGroup(groups, group, rootGroups);
            }
        }
        return rootGroups.Groups;
    }

    function sortItemGroup(groups, group, parent) {
        parent.Groups.push(group);

        for (var i = 0; i < groups.length; i++) {
            var otherGroup = groups[i];
            if (otherGroup.ParentGroupId == group.Id) {
                sortItemGroup(groups, otherGroup, group);
            }
        }
    }

    function sortAssetGroups() {
        // reset groups
        for (var i = 0; i < tracking.data.groups.length; i++) {
            tracking.data.groups[i].Groups = [];
        }

        var sortedGroups = tracking.data.groups.sort(function (a, b) { return a.Name.localeCompare(b.Name); });
        var rootGroups = { Groups: [] };
        for (var i = 0; i < sortedGroups.length; i++) {
            var group = sortedGroups[i];
            if (group.ParentGroupId == null
                || tracking.data.groupsById[group.ParentGroupId] === undefined) {
                sortAssetGroup(group, rootGroups);
            }
        }
        return rootGroups.Groups;
    }

    function sortAssetGroup(group, parent) {
        parent.Groups.push(group);

        for (var i = 0; i < tracking.data.groups.length; i++) {
            var otherGroup = tracking.data.groups[i];
            if (otherGroup.ParentGroupId == group.Id)
                sortAssetGroup(otherGroup, group);
        }
    }

    //function getDisplayPreferences(grouping) {
    //    switch (grouping) {
    //        case 'fences':
    //            return t
    //    }
    //}

    // javascript doesnt pass standard variables by reference because it is awful
    function getDisplayPreferences(grouping) {
        switch (grouping) {
            case 'hiddenFences':
                return tracking.user.displayPreferences.hiddenFences;
            case 'expandedGroups':
                return tracking.user.displayPreferences.expandedGroups;
            case 'hiddenAssets':
                return tracking.user.displayPreferences.hiddenAssets;
            case 'hiddenPlaces':
                return tracking.user.displayPreferences.hiddenPlaces;
            case 'visibleTrips':
                return tracking.user.displayPreferences.visibleTrips;
        }
        return null;
    }

    function setDisplayPreferences(grouping, value) {
        switch (grouping) {
            case 'hiddenFences':
                tracking.user.displayPreferences.hiddenFences = value;
                break;
            case 'expandedGroups':
                tracking.user.displayPreferences.expandedGroups = value;
                break;
            case 'hiddenAssets':
                tracking.user.displayPreferences.hiddenAssets = value;
                break;
            case 'hiddenPlaces':
                tracking.user.displayPreferences.hiddenPlaces = value;
                break;
            case 'visibleTrips':
                tracking.user.displayPreferences.visibleTrips = value;
                break;
        }
        return;
    }

    function displayPreferencesRemove(grouping, id) {
        //tracking.log('Remove hidden preference: ' + grouping + ' -> ' + id);
        var grp = getDisplayPreferences(grouping);

        if (grp == null)
            return;
        grp = _.without(grp, id.toString());
        setDisplayPreferences(grouping, grp);
        saveDisplayPreferences();
    }

    function displayPreferencesAdd(grouping, id) {
        id = id.toString();
        //tracking.log('Add hidden preference: ' + grouping + ' -> ' + id);

        var grp = getDisplayPreferences(grouping);

        if (grp == null)
            return;

        var isFound = grp.indexOf(id) !== -1;
        if (!isFound) {
            grp.push(id);
        }
        setDisplayPreferences(grouping, grp);
        saveDisplayPreferences();
    }

    //function saveDisplayPreferences() {
    //    $j.cookie('hiddenFences', tracking.user.displayPreferences.hiddenFences.join(','), { expires: 365, path: '/', secure: true });
    //    $j.cookie('hiddenAssets', tracking.user.displayPreferences.hiddenAssets.join(','), { expires: 365, path: '/', secure: true });
    //    $j.cookie('hiddenGroups', tracking.user.displayPreferences.expandedGroups.join(','), { expires: 365, path: '/', secure: true });
    //    $j.cookie('hiddenPlaces', tracking.user.displayPreferences.hiddenPlaces.join(','), { expires: 365, path: '/', secure: true });
    //}

    function isStorageAvailable(type) {
        try {
            var storage = window[type],
                x = '__storage_test__';
            storage.setItem(x, x);
            storage.removeItem(x);
            return true;
        }
        catch (e) {
            return e instanceof DOMException && (
                // everything except Firefox
                e.code === 22 ||
                // Firefox
                e.code === 1014 ||
                // test name field too, because code might not be present
                // everything except Firefox
                e.name === 'QuotaExceededError' ||
                // Firefox
                e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
                // acknowledge QuotaExceededError only if there's something already stored
                storage.length !== 0;
        }
    }

    function loadDisplayPreferences() {
        if (isStorageAvailable('localStorage')) {
            if (localStorage.getItem('displayPreferences') === null
                && localStorage.getItem('displayPreferences-' + tracking.user.id) === null) {
                // we may need to convert from cookies
                var hiddenFences = $.cookie('hiddenFences');
                var hiddenAssets = $.cookie('hiddenAssets');
                var hiddenPlaces = $.cookie('hiddenPlaces');
                var hiddenGroups = $.cookie('hiddenGroups');

                // remove old cookies
                if (hiddenFences != null) {
                    $.cookie('hiddenFences', null);
                }
                if (hiddenAssets != null) {
                    $.cookie('hiddenAssets', null);
                }
                if (hiddenPlaces != null) {
                    $.cookie('hiddenPlaces', null);
                }
                if (hiddenGroups != null) {
                    $.cookie('hiddenGroups', null);
                }

                if ((hiddenFences == null) || (hiddenFences == ''))
                    hiddenFences = [];
                else
                    hiddenFences = hiddenFences.split(',');

                if ((hiddenAssets == null) || (hiddenAssets == ''))
                    hiddenAssets = [];
                else
                    hiddenAssets = hiddenAssets.split(',');

                if ((hiddenGroups == null) || (hiddenGroups == '')) {
                    hiddenGroups = [];
                } else
                    hiddenGroups = hiddenGroups.split(',');

                if ((hiddenPlaces == null) || (hiddenPlaces == ''))
                    hiddenPlaces = [];
                else
                    hiddenPlaces = hiddenPlaces.split(',');

                // load new preferences
                tracking.user.displayPreferences.hiddenPlaces = hiddenPlaces;
                tracking.user.displayPreferences.hiddenFences = hiddenFences;
                tracking.user.displayPreferences.hiddenAssets = hiddenAssets;
                tracking.user.displayPreferences.visibleTrips = [];
                tracking.user.displayPreferences.hideAllAssets = tracking.data.assets.length >= 200 ? true : false;
                tracking.user.displayPreferences.customSort = {};
                tracking.user.displayPreferences.sortMode = {
                    assets: tracking.sortModes.ALPHABETICAL,
                    fences: tracking.sortModes.ALPHABETICAL,
                    places: tracking.sortModes.ALPHABETICAL,
                    journeys: tracking.sortModes.ALPHABETICAL,
                    sharedViews: tracking.sortModes.ALPHABETICAL
                };
                tracking.user.displayPreferences.sortDirection = {
                    assets: tracking.sortDirections.ASC,
                    fences: tracking.sortDirections.ASC,
                    places: tracking.sortDirections.ASC,
                    journeys: tracking.sortDirections.ASC,
                    sharedViews: tracking.sortDirections.ASC
                };
                tracking.user.displayPreferences.warnings = {
                    addresses: true,
                    speed: true
                };
                saveDisplayPreferences();
            } else {
                if (localStorage.getItem('displayPreferences-' + tracking.user.id) !== null) {
                    tracking.user.displayPreferences = JSON.parse(localStorage.getItem('displayPreferences-' + tracking.user.id));
                } else {
                    tracking.user.displayPreferences = JSON.parse(localStorage.getItem('displayPreferences'));
                }

                if (tracking.user.displayPreferences.hiddenPlaces === undefined) {
                    tracking.user.displayPreferences.hiddenPlaces = [];
                }
                if (tracking.user.displayPreferences.hiddenFences === undefined) {
                    tracking.user.displayPreferences.hiddenFences = [];
                }
                if (tracking.user.displayPreferences.hiddenAssets === undefined) {
                    tracking.user.displayPreferences.hiddenAssets = [];
                }
                if (tracking.user.displayPreferences.expandedGroups === undefined) {
                    tracking.user.displayPreferences.expandedGroups = [];
                }
                if (tracking.user.displayPreferences.hideAllAssets === undefined) {
                    tracking.user.displayPreferences.hideAllAssets = tracking.data.assets.length >= 200 ? true : false;
                }
                if (tracking.user.displayPreferences.visibleTrips === undefined) {
                    tracking.user.displayPreferences.visibleTrips = [];
                }
                if (tracking.user.displayPreferences.customSort === undefined) {
                    tracking.user.displayPreferences.customSort = {};
                }
                if (tracking.user.displayPreferences.sortMode === undefined) {
                    tracking.user.displayPreferences.sortMode = {
                        assets: tracking.sortModes.ALPHABETICAL,
                        fences: tracking.sortModes.ALPHABETICAL,
                        places: tracking.sortModes.ALPHABETICAL,
                        journeys: tracking.sortModes.ALPHABETICAL,
                        sharedViews: tracking.sortModes.ALPHABETICAL,
                    };
                }
                if (tracking.user.displayPreferences.sortDirection === undefined) {
                    tracking.user.displayPreferences.sortDirection = {
                        assets: tracking.sortDirections.ASC,
                        fences: tracking.sortDirections.ASC,
                        places: tracking.sortDirections.ASC,
                        journeys: tracking.sortDirections.ASC,
                        sharedViews: tracking.sortDirections.ASC
                    };
                }
                if (tracking.user.displayPreferences.warnings === undefined) {
                    tracking.user.displayPreferences.warnings = {
                        addresses: true,
                        speed: true
                    };
                }
            }
        }
    }

    function saveDisplayPreferences() {
        if (isStorageAvailable('localStorage')) {
            localStorage.removeItem('displayPreferences');
            localStorage.setItem('displayPreferences-' + tracking.user.id, JSON.stringify(tracking.user.displayPreferences));
        }
    }

    function addPlaceMarker(latlng, location, place) {
        if (latlng == null) {
            return null;
        }
        tracking.extendBounds(latlng);

        var color = place.Color;
        if (color == null) {
            color = '';
        }
        var imagePath = createMarkerPath('Generic', color, null, null, null, false);

        var placeMap = tracking.map;
        var isCurrentlyActive = !isItemIncluded(tracking.user.displayPreferences.hiddenPlaces, place.Id);
        if (!isCurrentlyActive) {
            placeMap = null;
        }

        var icon = L.icon({
            iconUrl: imagePath,
            iconSize: [36, 36],
            iconAnchor: [18, 18]
        });
        var marker = L.marker(latlng, { icon: icon });
        marker.data = {
            location: null,
            placeId: null
        };
        if (placeMap != null) {
            addItemToMap(marker);
        }

        location.marker = marker;
        if (location != null) {
            //$j.data(marker, 'location', location);
            marker.data.location = location;
        }
        if (place != null) {
            //$j.data(marker, 'placeId', place.Id);
            marker.data.placeId = place.Id;
        }
        marker.on('mouseover', function (e) {
            markerHover(marker);
        	//var evt = e.originalEvent;
        	//evt.preventDefault = true;
        	//$j('#fence-tooltip').tooltip('option', 'position', { of: evt, my: 'left+15 bottom+15', at: 'right center', }).tooltip('option', 'content', place.Name).tooltip('open');
        });
        marker.on('mouseout', function (e) {
            markerUnhover(marker);
        	//$j('#fence-tooltip').tooltip('close');
        });
        marker.on('click', function (e) {
			//placeClick(marker);
            markerClick(marker, 'place', e.latlng, true);
        });
        tracking.data.placeMarkers.push(marker);
        return marker;
    }

    function getHighestPriorityEventType(events) {
        if (events == null)
            return null;

        // indicator priority
        // emergency, alert triggered, checkin, reset, enter fence, exit fence, start, stop
        var evtTypePriority = [9, 14, 15, 10, 7, 8, 1, 2];
        var evtType = null;
        _.each(events, function (evt) {
            if (evt.Hide) {
                return;
            }
            if (tracking.user.isAnonymous || tracking.options.hideEmergencyEvents) {
                if ((evt.Type === 9) || (evt.Type === 124) || (evt.Type === 126) || (evt.Type === 29)) {
                    return;
                }
            }
            if (tracking.user.isAnonymous || tracking.options.hideAlertTriggeredEvents) {
                if ((evt.Type === 14) || (evt.Type === 16)) {
                    return;
                }
            }
            if (evtType === null) {
                evtType = evt.Type;
                return;
            }
            var priority = _.indexOf(evtTypePriority, evt.Type);
            if (priority !== -1) {
                var currentPriority = _.indexOf(evtTypePriority, evtType);
                if (priority < currentPriority || currentPriority === -1) {
                    evtType = evt.Type;
                }
            }
        });
        return evtType;
    }

    function addPositionMarkerToPoint(latlng, userAdded, location, asset, alpha, isCluster, isFirst, isLast, dataGroup, trip, sharedViewId) {
        if (isCluster === undefined || isCluster === null) {
            isCluster = false;
        }
        if (latlng === undefined || latlng === null) {
            return null;
        }
        tracking.extendBounds(latlng);

        var color = asset.Color;
        if (color === undefined || color === null) {
            color = '';
        }
        var course = location.Course;
        if (course === undefined || course === null) {
            course = '';
        }
        var evtType = getHighestPriorityEventType(location.Events);
        var imagePath = createMarkerPath(asset.Class, color, course, alpha, asset.Id, false, evtType, isFirst, isLast);
        var icon = L.icon({
            iconUrl: imagePath,
            iconSize: [36, 36],
            iconAnchor: [18, 18]
        });
        var marker = L.marker(latlng, { icon: icon });
        marker.data = {
            location: null,
            assetId: null,
            alpha: null,
            isFirst: null,
            isLast: null,
            selected: null,
            message: null,
            tripId: null,
            sharedViewId: null,
            isHidden: false
        };

        if (!isCluster && !location.IsHidden) {
            var viewMode = null;
            if (dataGroup === tracking.dataGroups.SHARED_VIEW_HISTORY || (sharedViewId !== undefined && sharedViewId !== null)) {
                viewMode = tracking.viewModes.SHARED_VIEW;
            }
            if (dataGroup === getAssetDataGroupForCurrentViewMode() || dataGroup === tracking.dataGroups.JOURNEY_HISTORY) {
                addItemToMap(marker, null, viewMode);
            }
        }
        //location.marker = marker; // associate marker with location so that it can be hidden/shown

        if (location !== null) {
            marker.data.location = location;
        }
        if (asset !== null) {
            marker.data.assetId = asset.Id;
        }
        if (trip !== undefined && trip !== null) {
            marker.data.tripId = trip.Id;
        }
        if (sharedViewId !== undefined && sharedViewId !== null) {
            marker.data.sharedViewId = sharedViewId;
        }
        if (alpha !== undefined && alpha !== null) {
            marker.data.alpha = alpha;
        }
        if (isFirst !== undefined && isFirst !== null && isFirst) {
            marker.data.isFirst = true;
        }
        if (isLast !== undefined && isLast !== null && isLast) {
            marker.data.isLast = true;
        }
        marker.on('mouseover', function (e) {
            markerHover(marker);
        });
        marker.on('mouseout', function (e) {
            markerUnhover(marker);
        });
        marker.on('click', function (e) {
            markerClick(marker, 'position', null, true);
        });
        // we don't simply check the current mode as results may come back asynchronously
        // so the current mode might not be the intended one
        var markers = getMapMarkersForDataGroup(dataGroup);
        if (markers !== undefined) {
            markers.push(marker);
        }
        return marker;
    }

    function markerHover(marker) {
        if (marker.data.location.IsHidden) {
            return;
        }
        var isSelected = marker.data.selected;
        if ((isSelected == null) || !isSelected) {
            var titleElement = null;
            var asset = null;
            if (marker.data.assetId !== undefined && marker.data.assetId !== null) {
                var location = marker.data.location;
                var timeElement = null;
                if (location !== null && location !== undefined) {
                    if ((location.IsAcc != null) && (location.IsAcc === false)) {
                        timeElement = el('div', [el('span.inaccurate', location.Time), el('sup', { title: tracking.strings.TIME_INACCURATE}, '*')]);
                    }
                    timeElement = el('div.position-time', location.Time);
                }
                asset = findAssetById(marker.data.assetId);
                titleElement = el('div', [text(asset.Name), timeElement]);
            }
            var place = null;
            if (marker.data.placeId !== undefined && marker.data.placeId !== null) {
                place = findPlaceById(marker.data.placeId);
                titleElement = el('div', place.Name);
            }
            var fence = null;
            if (marker.data.fenceId !== undefined && marker.data.fenceId !== null) {
                fence = findFenceById(marker.data.fenceId);
                titleElement = el('div', fence.Name);
            }
            var waypoint = null;
            if (marker.data.waypointId !== undefined && marker.data.waypointId !== null) {
                waypoint = findWaypointById(marker.data.waypointId);
                titleElement = el('div', waypoint.Name);
            }
            // change to yellow
            changeMarkerIcon(marker, 'yellow', null, true);

            // show tooltip
            // todo: don't show tooltip if marker has tags assigned to it
            $(marker.getElement()).bsTooltip({ title: titleElement, html: true, placement: 'left', trigger: 'manual', container: '.leaflet-pane.leaflet-marker-pane', offset: '0, -5px' }).bsTooltip('show');
        }

        //showMarkerInformation(marker, true);
    }

    function changeMarkerIcon(marker, overrideColor, overrideAlpha, isForHover) {
        if (isForHover == null) {
            isForHover = false;
        }
        var color = '';
        var type = '';
        var course = null;
        var alpha = 255;
        var assetId = null;
        var evtType = null;
        var isFirst = false;
        var isLast = false;
        var forceAlpha = false;
        var location = marker.data.location;

        var asset = null;
        if (marker.data.assetId !== undefined && marker.data.assetId !== null) {
            asset = findAssetById(marker.data.assetId);
        }
        var place = null;
        if (marker.data.placeId !== undefined && marker.data.placeId !== null) {
            place = findPlaceById(marker.data.placeId);
        }
        var fence = null;
        if (marker.data.fenceId !== undefined && marker.data.fenceId !== null) {
            fence = findFenceById(marker.data.fenceId);
        }
        var waypoint = null;
        if (marker.data.waypointId !== undefined && marker.data.waypointId !== null) {
            waypoint = findWaypointById(marker.data.waypointId);
        }

        if (asset !== null) {
            assetId = asset.Id;
            isFirst = marker.data.isFirst;
            isLast = marker.data.isLast;
            color = asset.Color != null ? asset.Color : '';
            type = asset.Class != null ? asset.Class : '';
            course = location.Course != null ? location.Course : '';
            evtType = getHighestPriorityEventType(location.Events);
        } else if (place !== null) {
            type = 'Generic';
            color = place.Color;
        } else if (waypoint !== null) {
            type = 'Waypoint';
            var waypointAsset = findAssetById(waypoint.AssetId);
            color = waypointAsset.Color;
        } else if (fence !== null) {
            type = 'Fence';
            color = fence.Color;
        }

        if (overrideColor != null && overrideColor != '') {
            color = overrideColor;
        }
        if (overrideAlpha != null && overrideAlpha != '') {
            alpha = overrideAlpha;
        }

        // assumine 100% alpha for hover
        var imagePath = createMarkerPath(type, color, course, alpha, assetId, false, evtType, isFirst, isLast);
        if (!tracking.map.hasLayer(marker)
            && asset !== null
            && tracking.state.activeMapMode !== tracking.mapModes.LIVE
            && tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined
            && tracking.data.history.markerClustersByAssetId[asset.Id].getVisibleParent(marker) != null) {
            // marker currently hidden, perhaps within a cluster?
            var parent = tracking.data.history.markerClustersByAssetId[asset.Id].getVisibleParent(marker);
            if (isForHover) {
                parent.imagePath = imagePath;
            } else {
                parent.imagePath = null;
            }
            if (!parent.isBouncing()) {
                marker.refreshIconOptions({ iconUrl: imagePath }, true);
            }
        } else {
            marker.setIcon(L.icon({ iconUrl: imagePath, iconSize: [36, 36], iconAnchor: [18, 18] }));
        }
    }

    function markerUnhover(marker) {
        changeMarkerIcon(marker, null, marker.data.alpha, false);
        hideTemporaryMarker(marker);
        $(marker.getElement()).bsTooltip('dispose');
    }

    function includeRowIfNotNull(header, content, headerOptions) {
        if (content === null || content === undefined) {
            return null;
        }
        if (content === '') {
            return null;
        }
        return el('tr', [el('td.field', headerOptions, header + ':'), el('td', content)]);
    }

    function createAccordionCard(id, parentId, header, body) {
        var contentId = 'accordion-' + id + '-content';
        
        var cardheadertext = el('span.mr-auto', header);
        var cardicon = svg('svg.list-item-action', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#angle-down' } }));

        var cardheaderbutton = el('button.btn.btn-link.d-flex.flex-row.w-100.p-0', { type: 'button', 'data-toggle': 'collapse', 'data-target': '#' + contentId, 'aria-expanded': 'false', 'aria-controls': contentId }, [cardheadertext, cardicon]);
        var cardbody = el('.card-body', body);
        var cardcontent = el('#' + contentId + '.collapse', { 'data-parent': '#' + parentId }, cardbody);
        var cardheader = el('.card-header.p-0.pl-2#accordion-' + id + '-head', cardheaderbutton);
         
        return el('.card', [cardheader, cardcontent]);
    }

    function createPositionPlaybackForAsset(asset, marker) {
        var location = marker.data.location;
        var tripId = marker.data.tripId;
        var isForTrip = (tripId !== undefined && tripId !== null);
        if (tracking.state.activeMapMode === tracking.mapModes.LIVE && tracking.state.activeViewMode === tracking.viewModes.NORMAL && (tripId === undefined || tripId === null)) {
            return null;
        }

        // include position history playback buttons in history mode
        var playbackTextElements;
        var total = null;
        var current = null;

        var linkOptions = { dataset: { assetId: asset.Id, positionId: location.Id }, href: '#', title: tracking.strings.PLAYBACK_PLAY };
        if (isForTrip) {
            linkOptions.dataset.tripId = tripId;
        }

        if (tracking.state.isInPlaybackMode) {
            total = tracking.data.playback.positions.length;
            current = tracking.data.playback.positions.length - tracking.data.playback.index;
            playbackTextElements = [svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#pause-solid' } }))]; // text(tracking.strings.PLAYBACK_STOP)
            linkOptions.title = tracking.strings.PLAYBACK_STOP;
        } else {
            // todo: doesn't work with trips
            // determine current position in history through calculation
            total = null;
            current = null;
            var assetPositions = [];
            if (isForTrip) {
                assetPositions = tracking.data.trips.positionsByTripId[tripId];
            } else if (tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
                assetPositions = tracking.data.history.positionsByAssetId[asset.Id];
            } else if (tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
                assetPositions = tracking.data.sharedView.positionsByAssetId[asset.Id];
            }
            
            if (assetPositions !== undefined) {
                assetPositions = assetPositions.Positions;
                for (var j = 0; j < assetPositions.length; j++) {
                    if (assetPositions[j].Id === location.Id) {
                        total = assetPositions.length;
                        current = assetPositions.length - j;
                        break;
                    }
                }
            }

            playbackTextElements = [svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#play-solid' } }))]; // text(tracking.strings.PLAYBACK_PLAY)
            linkOptions.title = tracking.strings.PLAYBACK_PLAY;
        }

        var playbackStatus = tracking.strings.POSITION_INDEX.replace('{0}', current).replace('{1}', total) + ' ';
        var disablePrev = current === 1;
        var disableNext = current >= total;

        var playback = [
            el('a#playback-first.btn.btn-secondary.btn-sm.playback-link' + (disablePrev ? '.disabled' : ''), { href: '#', title: tracking.strings.FIRST  }, [svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#fast-backward-solid' } }))]), // , el('span', tracking.strings.FIRST)
            el('a#playback-prev.btn.btn-secondary.btn-sm.playback-link' + (disablePrev ? '.disabled' : ''), { href: '#', title: tracking.strings.POSITION_PREV }, [svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#backward-solid' } }))]), //, el('span', tracking.strings.POSITION_PREV)
            el('span#playback-status.btn.btn-secondary.btn-sm.disabled.w-100', playbackStatus),
            el('a#playback-next.btn.btn-secondary.btn-sm.playback-link' + (disableNext ? '.disabled' : ''), { href: '#', title: tracking.strings.POSITION_NEXT }, [svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#forward-solid' } }))]), //el('span', tracking.strings.POSITION_NEXT),
            el('a#playback-last.btn.btn-secondary.btn-sm.playback-link' + (disableNext ? '.disabled' : ''), { href: '#', title: tracking.strings.LAST }, [svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#fast-forward-solid' } }))]), // el('span', tracking.strings.LAST),
            el('a#playback.btn.btn-secondary.btn-sm.playback-link' + (disableNext ? '.disabled' : ''), linkOptions, playbackTextElements)
        ];
        return el('div.btn-group.w-100.my-1.playback-container', playback);
    }

    function getStatusTextForLocation(location) {
        var status = '';
        if (location.State.IsSpeeding === true) {
            status = tracking.strings.SPEEDING;
        } else if (location.State.IsTowing === true) {
            status = tracking.strings.TOWING;
        } else if (location.State.IsMoving === true) {
            status = tracking.strings.MOVING;
        } else if ((location.State.IsMoving === false) || ((location.State.IsMoving == null) && (location.State.IsTowing == false))) {
            status = tracking.strings.STATIONARY;
        }

        var ignition = '';
        if (location.State.IsIdling === true) {
            ignition = tracking.strings.IDLING;
        } else if (location.State.IsIgnitionOn === true) {
            ignition = tracking.strings.IGNITION_ON;
        } else if (location.State.IsIgnitionOn === false) {
            ignition = tracking.strings.IGNITION_OFF;
        }

        if ((status !== '') && (ignition !== '')) {
            status += ', ' + ignition;
        }

        if (location.State.IsInLowPowerMode === true) {
            if (status !== '') {
                status += ', ';
            }
            status += tracking.strings.IN_LOW_POWER_MODE;
        }

        if (location.State.IsAntennaCut === true) {
            if (status !== '') {
                status += ', ';
            }
            status += tracking.strings.ANTENNA_CUT;
        }

        if (location.State.IsCellJammed === true) {
            if (status !== '') {
                status += ', ';
            }
            status += tracking.strings.CELL_JAMMED;
        }

        if (location.State.IsGpsJammed === true) {
            if (status !== '') {
                status += ', ';
            }
            status += tracking.strings.GPS_JAMMED;
        }

        if (location.State.IsOnBackupPower === true) {
            if (status !== '') {
                status += ', ';
            }
            status += tracking.strings.ON_BACKUP_POWER;
        }

        if (location.State.IsImmobilized === true) {
            if (status !== '') {
                status += ', ';
            }
            status += tracking.strings.IMMOBILIZED;
        }
        return status;
    }

    function populatePositionInformation(asset, marker) {
        // no strings for html! WHY ME SO DUMB?!
        var location = marker.data.location;
        var message = marker.data.message;
        if (asset === undefined || asset === null) {
            return;
        }

        var isHover = false;
        var isMarkerSelected = true;

        var headingRow = null;
        if (location.Course != null && !asset.HideCourse) {
            headingRow = includeRowIfNotNull(tracking.strings.HEADING, location.Course + '°');
        }

        var elevationRow = null;
        if ((location.Altitude != null) && (location.Altitude != '') && (!asset.HideAltitude)) {
            elevationRow = includeRowIfNotNull(tracking.strings.ALTITUDE, convertAltitudeToPreference(location.Altitude));
        }

        var playbackContent = createPositionPlaybackForAsset(asset, marker);

        var waypointRow = null;
        var waypoint = findWaypointByAsset(asset);
        if (waypoint != null) {
            var distance = waypoint.DistanceTo;
            if (distance == null) {
                var destination = L.latLng(waypoint.Lat, waypoint.Lng);
                distance = convertFromMetresToUserDistancePreference(destination.distanceTo(L.latLng(location.Lat, location.Lng)));
            }
            waypointRow = includeRowIfNotNull(tracking.strings.WAYPOINT, [text(waypoint.Name), el('br'), text(distance)]);
        }

        var messageRow = null;
        if ((message != null) && (message != '')) {
            messageRow = includeRowIfNotNull(tracking.strings.MESSAGE, messageRow);
        }

        var garminFormElement = null; // can't have more than one form submission per position
        var eventInformationElements = [];
        var shockLogRow = null;
        if (location.Events != null) {
            for (var i = 0; i < location.Events.length; i++) {
                var evtclass = (i % 2 === 0) ? 'odd' : 'even';
                var evt = location.Events[i];
                // todo: actually filter this out in the data feed -- maybe not, would it break alerts tab?
                if (tracking.user.isAnonymous && ((evt.Type === 14) || (evt.Type === 16) || (evt.Type === 9) || (evt.Type === 124) || (evt.Type === 126) || (evt.Type === 29))) {
                    continue;
                }
                if ((tracking.options.hideEmergencyEvents || evt.Hide) && ((evt.Type === 9) || (evt.Type === 124) || (evt.Type === 126) || (evt.Type === 29))) {
                    continue;
                }
                if ((tracking.options.hideAlertTriggeredEvents || evt.Hide) && ((evt.Type === 14) || (evt.Type === 16))) {
                    continue;
                }

                if (evt.Type == 268) { // SL Summary
                    shockLogRow = includeRowIfNotNull('ShockLog', evt.Details);
                    continue;
                }
                var eventicon = createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false, evt.Type);
                var eventInformationDetails = [];
                switch (evt.Type) {
                    case 14: // alert triggered
                    case 16: // alert no longer triggered
                        if (evt.Details != null) {
                            eventInformationDetails.push(el('button.event-information.btn.btn-primary', { dataset: { position: location.Id, event: evt.Id } }, tracking.strings.EVENT_VIEW_DETAILS));
                        } else {
                            eventInformationDetails.push(text(tracking.strings.DELETED));
                        }
                        break;
                    case 127: // garmin form submission
                        if (evt.Details != null) {
                            var formDetails = evt.Details.split('|');
                            var formId = formDetails[0];
                            var formName = tracking.strings.VIEW;
                            if (formDetails.length > 1) {
                                formName = formDetails[1];
                            }
                            eventInformationDetails.push(text(formName));
                            garminFormElement = createAccordionCard('garmin', 'asset-information-accordion', tracking.strings.GARMIN_FORM + ' - ' + formName, el('ul.GarminSubmissionItems', {'data-form-id': formId }));
                        } else {
                            eventInformationDetails.push(text(tracking.strings.DELETED));
                        }
                        break;
                    case 270: // beacon read
                        if (evt.Details != null) {
                            var beacons = evt.Details.split("\n");
                            for (var k = 0; k < beacons.length; k++) {
                                var beacon = beacons[k].split('|');
                                if (beacon.length > 1) {
                                    var beaconUniqueKey = beacon[0];
                                    var beaconRSSI = beacon[1];
                                    if (beaconUniqueKey == null) {
                                        continue;
                                    }
                                    var beaconPlace = findPlaceByUniqueKey(beaconUniqueKey);
                                    if (beaconPlace != null) {
                                        eventInformationDetails.push(el('a.beacon-place', { href: '#', dataset: { placeId: beaconPlace.Id } }, beaconPlace.Name));
                                        eventInformationDetails.push(text(' @ ' + beaconRSSI + ' RSSI'));
                                    } else {
                                        eventInformationDetails.push(text(beaconUniqueKey + ' @ ' + beaconRSSI + ' RSSI'));
                                    }
                                    eventInformationDetails.push(el('br'));
                                }
                            }
                        }
                        break;
                    case 272:
                    case 273:
                    case 274:
                        // driver fatigue
                        if (evt.Details != null) {
                            eventInformationDetails.push(el('img.fatigue-image', { src: 'data:image/jpeg;base64, ' + evt.Details }));
                        }
                        break;
                    case 275:
                        break;
                    default:
                        eventInformationDetails.push(formattedTextToDiv(evt.Details));
                        break;
                }
                // add row for event information
                // TODO: remove inline styles except background image
                eventInformationElements.push(el('tr.' + evtclass, [
                    el('td', el('div', { style: { backgroundImage: 'url(' + eventicon + ')', width: '36px', height: '27px', display: 'block', backgroundPosition: '0 0' } })), 
                    el('td', { style: { verticalAlign: 'middle' } }, evt.TypeName), 
                    el('td.break-text', { style: { verticalAlign: 'middle' } }, _.compact(eventInformationDetails))
                ]));

                if (evt.Alert != null && isMarkerSelected && evt.Type === 14) {
                    // todo: use DOM editing here to prevent script/html injection
                    var includeFields = evt.Alert.Acknowledged === 0;

                    var alertHeaderElements = [];
                    if (evt.Alert.Color != null) {
                        alertHeaderElements.push(el('span.alert-color', { style: { backgroundColor: evt.Alert.Color }}));
                    }
                    if (evt.Alert.Name != null) {
                        alertHeaderElements.push(text(evt.Alert.Name + ': '));
                    }
                    alertHeaderElements.push(text(evt.Alert.Type));

                    var alertFormElements = [];
                    if (evt.Alert.Description != null) {
                        alertFormElements.push(el('div.form-group', [
                            el('label', { for: 'PositionAlertDescription' + evt.Id }, tracking.strings.DESCRIPTION), 
                            el('input#PositionAlertDescription' + evt.Id + '.form-control-plaintext', { type: 'text', value: evt.Alert.Description, readOnly: true })
                        ]));
                    }
                    if (evt.Alert.ResolutionProcedure != null) {
                        alertFormElements.push(el('div.form-group', [
                            el('label', { for: 'PositionAlertResolutionProcedure' + evt.Id }, tracking.strings.RESOLUTION_PROCEDURE),
                            el('input#PositionAlertResolutionProcedure' + evt.Id + '.form-control-plaintext', { type: 'text', value: evt.Alert.ResolutionProcedure, readOnly: true })
                        ]));
                    }
                    var alertResolutionElements = [];
                    if (includeFields) {
                        alertResolutionElements.push(el('div.form-group', [
                            el('label', { for: 'AlertResolution' + evt.Id }, tracking.strings.RESOLUTION),
                            // TODO: remove inline style
                            el('textarea#AlertResolution' + evt.Id + '.required.form-control', { style: { height: '4em', width: '100%' }, name: 'AlertResolution' }, evt.Alert.AcknowledgedText)
                        ]));
                    } else if (evt.Alert.AcknowledgedText != null) {
                        alertResolutionElements.push(el('div.form-group', [
                            el('label', { for: 'PositionAlertResolution' + evt.Id }, tracking.strings.RESOLUTION),
                            el('input#PositionAlertResolution' + evt.Id + '.form-control-plaintext', { type: 'text', value: evt.Alert.AcknowledgedText, readOnly: true })
                        ]));
                    }
                    alertFormElements.push(el('div.alert-resolution', alertResolutionElements));

                    var ack = tracking.strings.ACKNOWLEDGE;
                    var ackalt = tracking.strings.ACKNOWLEDGE_ALT;
                    if (evt.Alert.LabelAcknowledge != null) {
                        ack = evt.Alert.LabelAcknowledge;
                    }
                    if (evt.Alert.LabelAcknowledgeAlt != null) {
                        ackalt = evt.Alert.LabelAcknowledgeAlt;
                    }
                    if (includeFields) {
                        alertFormElements.push(el('div.alert-acknowledge', [
                            el('button.AlertAcknowledge.btn.btn-primary.alert-acknowledge', ack),
                            text(' ' + tracking.strings.OR + ' '),
                            el('button.AlertAcknowledgeAlt.btn.btn-secondary.alert-acknowledge-alt', ackalt),
                        ]));
                    } else {
                        alertFormElements.push(el('div.form-group', [
                            el('label', { for: 'PositionAlertStatus' + evt.Id }, tracking.strings.STATUS),
                            el('input#PositionAlertStatus' + evt.Id + '.form-control-plaintext', { type: 'text', value: tracking.strings.ACKNOWLEDGE_STATUS.replace('{Status}', evt.Alert.AcknowledgedStatus).replace('{Time}', evt.Alert.AcknowledgedOn).replace('{User}', evt.Alert.AcknowledgedBy), readOnly: true })
                        ]));
                    }
                    alertFormElements.push(el('input', { type: 'hidden', value: evt.Id }));
                    var alertForm = el('form#AcknowledgeAlert' + evt.Id, { dataset: { assetId: asset.Id, eventId: evt.Id } }, alertFormElements);

                    // add row for alert information
                    eventInformationElements.push(el('tr.' + evtclass, [
                        el('td', { colspan: 3 }, el('div#alert-information-accordion' + evt.Id + '.alert-information-accordion.position-accordion', createAccordionCard('alert-information' + evt.Id, 'alert-information-accordion' + evt.Id, alertHeaderElements, alertForm))),
                    ]));
                }
            }
        }
        var eventInformationTable = null;
        if (eventInformationElements.length > 0 ) {
            eventInformationTable = el('table.table.table-striped', [
                el('thead', 
                    el('tr', [
                        el('th', ' '), 
                        el('th', tracking.strings.EVENT_EVENT),
                        el('th', tracking.strings.EVENT_DETAILS)
                    ])
                ),
                el('tbody', eventInformationElements)
            ]);
        }

        var ioInformationElement = null;
        if (location.GPIO != null) {
            var ioRows = createIOOutput(asset, location.GPIO, true);
            ioRows.push(includeRowIfNotNull(tracking.strings.CHANGED, location.GPIO.CreatedOn));
            var ioInformationTable = el('table', el('tbody', _.compact(ioRows)));
            ioInformationElement = createAccordionCard('io', 'asset-information-accordion', tracking.strings.IO_FIELDS, ioInformationTable);
        }

        var obdInformationElement = null;
        if (location.OBD != null) {
            var obdInformationTable = el('table', el('tbody', _.compact(createOBDOutput(location.OBD))));
            obdInformationElement = createAccordionCard('obd', 'asset-information-accordion', tracking.strings.OBD_FIELDS, obdInformationTable);
        }

        // asset photo should be shown in either the general information accordion or alongside the position information
        // depending on the user's preferences
        var assetPhotoElement = null;
        if (asset.PhotoType !== null && asset.PhotoType !== '') {
            assetPhotoElement = el('a', { href: '/uploads/images/assets/' + asset.Id + '.' + asset.PhotoType, target: '_blank' }, el('img.asset-photo', { src: '/uploads/images/assets/' + asset.Id + '_thumb.' + asset.PhotoType }));
        }

        var generalInformationElements = [
            includeRowIfNotNull(tracking.strings.IMEI, asset.IMEI),
            includeRowIfNotNull(tracking.strings.SERIAL_NUMBER, asset.SerialNumber),
            includeRowIfNotNull(tracking.strings.SOFTWARE_VERSION, asset.SoftwareVersion),
            includeRowIfNotNull(tracking.strings.NOTES, asset.Notes)
        ];
        if (!tracking.options.placeAssetPhotosInPositionDialog) {
            generalInformationElements.push(includeRowIfNotNull(tracking.strings.PHOTO, assetPhotoElement));
        }
        generalInformationElements = _.compact(generalInformationElements);

        var generalInformationElement = null;
        if (generalInformationElements.length > 0) {
            var generalInformationTable = el('table', el('tbody', generalInformationElements));
            generalInformationElement = createAccordionCard('general', 'asset-information-accordion', tracking.strings.GENERAL_FIELDS, generalInformationTable);
        }

        var simInformationElements = _.compact([
            includeRowIfNotNull(tracking.strings.SIM_ICCID, asset.SIMICCID),
            includeRowIfNotNull(tracking.strings.SIM_PHONE_NUMBER, asset.SIMPhoneNumber),
            includeRowIfNotNull(tracking.strings.SIM_PIN, asset.SIMPIN),
            includeRowIfNotNull(tracking.strings.SIM_PIN_UNLOCK, asset.SIMPINUnlock),
            includeRowIfNotNull(tracking.strings.SIM_PROVIDER, asset.SIMProvider)
        ]);
        var simInformationElement = null;
        if (simInformationElements.length > 0) {
            var simInformationTable = el('table', el('tbody', simInformationElements));
            simInformationElement = createAccordionCard('sim', 'asset-information-accordion', tracking.strings.SIM_FIELDS, simInformationTable);
        }

        // custom attributes
        var attributeElements = populateCustomAttributes(asset.Attributes, 0, 'asset-information-accordion');

        var driverInformationElements = [];
        if (location.Drivers != null && location.Drivers.length > 0) {
            // show driver for position instead of assigned driver
            // todo: cross-check DriverId to tracking.data.drivers for this information so the position information can be much less? (findDriverById)
            for (var i = 0; i < location.Drivers.length; i++) {
                var driver = location.Drivers[i].Driver;
                var driverStatus = location.Drivers[i].DriverStatus;
                var driverSummary = driver.DriverId;
                if (driverStatus != null) {
                    driverSummary += ' - ' + driverStatus.Status;
                }
                var assetDriverFields = [
                    includeRowIfNotNull(tracking.strings.DRIVER_ID, driver.DriverId),
                    includeRowIfNotNull(tracking.strings.IBUTTON_ID, driver.IButtonId),
                    includeRowIfNotNull(tracking.strings.GARMIN_ID, driver.GarminId),
                    includeRowIfNotNull(tracking.strings.REGION, driver.Region),
                    includeRowIfNotNull(tracking.strings.PHONE_NUMBER, driver.Phone),
                    includeRowIfNotNull(tracking.strings.BLOOD_TYPE, driver.BloodType),
                    includeRowIfNotNull(tracking.strings.LICENSE_NUMBER, driver.LicenseNumber),
                    includeRowIfNotNull(tracking.strings.LICENSE_EXPIRATION, driver.LicenseExpiration),
                    includeRowIfNotNull(tracking.strings.LICENSE_RESTRICTION, driver.LicenseRestriction),
                    includeRowIfNotNull(tracking.strings.MANAGER, driver.Manager),
                    includeRowIfNotNull(tracking.strings.EMERGENCY_CONTACT, driver.EmergencyContact),
                    includeRowIfNotNull(tracking.strings.EMERGENCY_CONTACT_NUMBER, driver.EmergencyContactNumber)
                ];
                var assetDriverInfo = [];
                if (driver.PhotoType !== null && driver.PhotoType !== '') {
                    assetDriverInfo.push(el('a', { href: '/uploads/images/drivers/' + driver.Id + '.' + driver.PhotoType, target: '_blank' }, el('img', { align: 'right', src: '/uploads/images/drivers/' + driver.Id + '_thumb.' + driver.PhotoType, alt: driver.DriverId })));
                }

                if (driverStatus != null) {
                    assetDriverFields.push(includeRowIfNotNull(tracking.strings.STATUS, driverStatus.Status));
                }
                assetDriverFields = _.compact(assetDriverFields);
                if (assetDriverFields.length > 0) {
                    assetDriverInfo.push(el('table', el('tbody', assetDriverFields)));
                }
                
                assetDriverInfo = _.compact(assetDriverInfo);
                if (assetDriverInfo.length > 0) {
                    driverInformationElements.push(createAccordionCard('driver-' + i, 'asset-information-accordion', tracking.strings.DRIVER_FIELDS + ': ' + driverSummary, assetDriverInfo));
                }
            }
        } else {
            // asset's driver attributes
            var driverFields = _.compact([
                includeRowIfNotNull(tracking.strings.MISSION, asset.Mission),
                includeRowIfNotNull(tracking.strings.DRIVER, asset.Driver),
                includeRowIfNotNull(tracking.strings.PHONE_NUMBER, asset.PhoneNumber),
                includeRowIfNotNull(tracking.strings.LICENSE_NUMBER, asset.LicenseNumber),
                includeRowIfNotNull(tracking.strings.NATIONAL_IDENTITY_CARD_NUMBER, asset.NationalIdentityCardNumber)
            ]);
            if (driverFields.length > 0) {
                driverInformationElements.push(createAccordionCard('driver', 'asset-information-accordion', tracking.strings.DRIVER_FIELDS, el('table', el('tbody', driverFields))));
            }
        }

        var vehicleInformationElement = null;
        var vehicleFields = _.compact([
            includeRowIfNotNull(tracking.strings.VEHICLE_MAKE_AND_MODEL, asset.VehicleMakeAndModel),
            includeRowIfNotNull(tracking.strings.VEHICLE_PURCHASE_DATE, asset.VehiclePurchaseDate),
            includeRowIfNotNull(tracking.strings.VEHICLE_VIN, asset.VehicleVIN),
            includeRowIfNotNull(tracking.strings.PLATE_NUMBER, asset.PlateNumber),
            includeRowIfNotNull(tracking.strings.FUEL_EFFICIENCY, asset.FuelEfficiency)
        ]);
        if (vehicleFields.length > 0) {
            vehicleInformationElement = createAccordionCard('vehicle', 'asset-information-accordion', tracking.strings.VEHICLE_FIELDS, el('table', el('tbody', vehicleFields)));
        }
        var vesselInformationElement = null;
        var vesselFields = _.compact([
            includeRowIfNotNull(tracking.strings.VESSEL_NAME, asset.VesselName),
            includeRowIfNotNull(tracking.strings.VESSEL_CALL_SIGN, asset.VesselCallSign),
            includeRowIfNotNull(tracking.strings.VESSEL_IMO_NUMBER, asset.VesselIMONumber),
            includeRowIfNotNull(tracking.strings.VESSEL_FLAG_REGISTRY, asset.VesselFlagRegistry),
            includeRowIfNotNull(tracking.strings.VESSEL_TONNAGE, asset.VesselTonnage),
            includeRowIfNotNull(tracking.strings.VESSEL_CLASS, asset.VesselClass),
            includeRowIfNotNull(tracking.strings.VESSEL_SKIPPER, asset.VesselSkipper),
            includeRowIfNotNull(tracking.strings.VESSEL_MMSI, asset.VesselMMSI)
        ]);
        if (vesselFields.length > 0) {
            vesselInformationElement = createAccordionCard('vessel', 'asset-information-accordion', tracking.strings.VESSEL_FIELDS, el('table', el('tbody', vesselFields)));
        }

        var assetInformationElement = null;
        var assetFields = [
            obdInformationElement,
            ioInformationElement,
            generalInformationElement,
            simInformationElement,
        ];
        assetFields = assetFields.concat(driverInformationElements);
        assetFields = assetFields.concat([vehicleInformationElement, vesselInformationElement, garminFormElement]);
        assetFields = assetFields.concat(attributeElements);
        assetFields = _.compact(assetFields);

        if (assetFields.length > 0) {
            assetInformationElement = el('div#asset-information-accordion.asset-information-accordion.position-accordion', assetFields);
        }

        var extraRow = null;
        if ((location.Extra != null) && (location.Extra != ''))  {
            extraRow = includeRowIfNotNull(tracking.strings.EXTRA, formattedTextToDiv(location.Extra), { style: { verticalAlign: 'top' }}); // TODO: remove inline style
        }
        var addressRow = null
        if ((location.Address != null) && (location.Address != '') && !asset.HideAddress)  {
            addressRow = includeRowIfNotNull(tracking.strings.ADDRESS, location.Address, { style: { verticalAlign: 'top' } }); // TODO: remove inline style
        }
        var odometerRow = null;
        if (location.Odometer != null) {
            odometerRow = includeRowIfNotNull(tracking.strings.ODOMETER, convertFromMetresToUserDistancePreference(location.Odometer));
        }
        var sourceRow = null;
        if (location.Source != null) {
            sourceRow = includeRowIfNotNull(tracking.strings.DATA_SOURCE, location.Source);
        }

        var statusRow = includeRowIfNotNull(tracking.strings.STATUS, getStatusTextForLocation(location));

        var fencesRow = null;
        if (location.InsideFences != null) {
            var fences = '';
            for (var i = 0; i < location.InsideFences.length; i++) {
                var fenceId = location.InsideFences[i];
                var fence = findFenceById(fenceId);
                if (fence != null) {
                    if (fences !== '') {
                        fences += ', ';
                    }
                    fences += fence.Name;
                }
            }
            fencesRow = includeRowIfNotNull(tracking.strings.GEOFENCES, fences);
        }

        var speedRow = null;
        if (!asset.HideSpeed) {
            var speed = convertSpeedToPreference(location.Speed);
            if (location.IsEst === true) {
                speed = el('span.estimated', { title: tracking.strings.SPEED_ESTIMATED}, [ text(speed), el('span.tooltip-circle', '?')]);
            }
            speedRow = includeRowIfNotNull(tracking.strings.SPEED, speed);
        }
        
        var accuracyRow = null;
        if ((location.Accuracy != null) && (location.Accuracy !== '') && !asset.HideAccuracy) {
            accuracyRow = includeRowIfNotNull(tracking.strings.ACCURACY, location.Accuracy);
        }
        var timeElement = el('span.mr-auto', location.IsAcc === false ? el('span.inaccurate', [text(location.Time), el('sup', { title: tracking.strings.TIME_INACCURATE }, '*')]) : text(location.Time));
        var timeContainerElement = el('div.d-flex.align-items-center', timeElement);
        var latLngRow = includeRowIfNotNull(tracking.strings.LAT_LNG, convertToLatLngPreference(location.DisplayLat, location.DisplayLng, location.Grid));

        var mapItemDetailsRows = _.compact([
            messageRow,
            addressRow,
            latLngRow,
            speedRow,
            elevationRow,
            headingRow,
            accuracyRow,
            odometerRow,
            sourceRow,
            extraRow,
            statusRow,
            fencesRow,
            shockLogRow,
            waypointRow
        ]);
        var mapItemsDetailsElement = null;
        if (mapItemDetailsRows.length > 0) {
            mapItemsDetailsElement = el('table.map-item-details', el('tbody', mapItemDetailsRows));
        }
        
        var infoElements = [
            tracking.options.placeAssetPhotosInPositionDialog ? assetPhotoElement : null,
            timeContainerElement,
            mapItemsDetailsElement,
        ];
        if (isMarkerSelected) {
            infoElements = infoElements.concat(playbackContent);
        }
        infoElements.push(eventInformationTable);
        if (isMarkerSelected) {
            infoElements.push(assetInformationElement);
        }
        infoElements = _.compact(infoElements);
        return el('div.markercontent', infoElements);
    }

    function showMarkerInformation(marker, isHover) {

    }

    function hideTemporaryMarker(marker) {
        if (marker.data.selected == true) {
            return;
        }
        // markers that were loaded outside the scope of the view
        // i.e. a marker from a historic event in live view
        if (marker.data.hide == true) {
        	removeItemFromMap(marker);
        }
    }

    function createQuickActionsForSharedView(sharedView) {
        document.getElementById('shared-view-information-actions').classList.add('is-visible');
        var quickActions = document.getElementById('shared-view-information-actions-list');
        var actionOptions = quickActions.querySelector('a[data-action="shared-view-options"]');
        actionOptions.setAttribute('data-shared-view-id', sharedView.Id);

        var actionToggleStatus = quickActions.querySelector('a[data-action="shared-view-status"]');
        actionToggleStatus.setAttribute('data-shared-view-id', sharedView.Id);
        if (sharedView.IsEnabled) {
            actionToggleStatus.title = tracking.strings.DISABLE;
            actionToggleStatus.querySelector('span').textContent = tracking.strings.DISABLE;
            actionToggleStatus.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgPath('ban'));
        } else {
            actionToggleStatus.title = tracking.strings.ENABLE;
            actionToggleStatus.querySelector('span').textContent = tracking.strings.ENABLE;
            actionToggleStatus.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgPath('check'));
        }
    }

    function createQuickActionsForGeofence(fence) {
        document.getElementById('map-item-actions-geofence').classList.add('is-visible');
        var quickActions = document.getElementById('geofence-information-actions-list');
        var actionHide = quickActions.querySelector('a[data-action="fence-hide"]');
        actionHide.setAttribute('data-fence-id', fence.Id);
        var actionOptions = quickActions.querySelector('a[data-action="fence-options"]');
        actionOptions.setAttribute('data-fence-id', fence.Id);

        var isSendDisabled = false, isReplayDisabled = false, isGroupDisabled = false, isAlertDisabled = false, isReportDisabled = false;
        if (tracking.user.isAnonymous) {
            isSendDisabled = isGroupDisabled = isAlertDisabled = isReportDisabled = true;
        }

        if (findAssetIdsInGeofence(fence).length == 0) {
            isSendDisabled = isGroupDisabled = isReplayDisabled = isReportDisabled = true;
        }

        var actionSendMessage = quickActions.querySelector('a[data-action="fence-send-message"]');
        var actionHistory = quickActions.querySelector('a[data-action="fence-history"]');
        var actionCreateAlert = quickActions.querySelector('a[data-action="fence-create-alert"]');
        var actionLocationReport = quickActions.querySelector('a[data-action="fence-location-report"]');
        var actionGroupAssets = quickActions.querySelector('a[data-action="fence-group-assets"]');
        if (isSendDisabled) {
            actionSendMessage.classList.add('disabled');
        } else {
            actionSendMessage.classList.remove('disabled');
        }
        if (isReplayDisabled) {
            actionHistory.classList.add('disabled');
        } else {
            actionHistory.classList.remove('disabled');
        }
        if (isGroupDisabled) {
            actionGroupAssets.classList.add('disabled');
        } else {
            actionGroupAssets.classList.remove('disabled');
        }
        if (isAlertDisabled) {
            actionCreateAlert.classList.add('disabled');
        } else {
            actionCreateAlert.classList.remove('disabled');
        }
        if (isReportDisabled) {
            actionLocationReport.classList.add('disabled');
        } else {
            actionLocationReport.classList.remove('disabled');
        }
        actionSendMessage.setAttribute('data-fence-id', fence.Id);
        actionHistory.setAttribute('data-fence-id', fence.Id);
        actionCreateAlert.setAttribute('data-fence-id', fence.Id);
        actionLocationReport.setAttribute('data-fence-id', fence.Id);
        actionGroupAssets.setAttribute('data-fence-id', fence.Id);
    }

    function createQuickActionsForWaypoint(waypoint, asset) {
        document.getElementById('map-item-actions-waypoint').classList.add('is-visible');
        var quickActions = document.getElementById('waypoint-information-actions-list');
        var actionHide = quickActions.querySelector('a[data-action="waypoint-hide"]');
        actionHide.setAttribute('data-asset-id', asset.Id);
        var actionOptions = quickActions.querySelector('a[data-action="waypoint-options"]');
        actionOptions.setAttribute('data-asset-id', asset.Id);
        var actionGetRoute = quickActions.querySelector('a[data-action="waypoint-route-asset"]');
        actionGetRoute.setAttribute('data-waypoint-id', waypoint.Id);
        var actionMarkComplete = quickActions.querySelector('a[data-action="waypoint-mark-complete"]');
        actionMarkComplete.setAttribute('data-waypoint-id', waypoint.Id);
        if (!canAssetBeRouted(asset) || waypoint.route != null) {
            actionGetRoute.classList.add('disabled');
        } else {
            actionGetRoute.classList.remove('disabled');
        }
        if (tracking.user.isAnonymous) {
            actionMarkComplete.classList.add('disabled');
        }
    }

    function createQuickActionsForPlace(place) {
        document.getElementById('map-item-actions-place').classList.add('is-visible');
        var quickActions = document.getElementById('place-information-actions-list');
        var actionHide = quickActions.querySelector('a[data-action="place-hide"]');
        actionHide.setAttribute('data-place-id', place.Id);
        var actionOptions = quickActions.querySelector('a[data-action="place-options"]');
        actionOptions.setAttribute('data-place-id', place.Id);
        var isAssetRoutingDisabled = (tracking.data.assets === null
            || tracking.data.assets.length === 0
            || tracking.options.enabledFeatures.indexOf('ASSET_ROUTING') === -1);
        var isRoutingDisabled = (tracking.options.enabledFeatures.indexOf('GET_ROUTE') === -1);
        var actionRouteAsset = quickActions.querySelector('a[data-action="place-route-asset"]');
        actionRouteAsset.setAttribute('data-place-id', place.Id);
        if (isAssetRoutingDisabled) {
            actionRouteAsset.classList.add('disabled');
        } else {
            actionRouteAsset.classList.remove('disabled');
        }
        var actionMeasureDistanceTo = quickActions.querySelector('a[data-action="place-measure-distance"]');
        actionMeasureDistanceTo.setAttribute('data-lat', place.Location.Lat);
        actionMeasureDistanceTo.setAttribute('data-lng', place.Location.Lng);
        var actionRouteFrom = quickActions.querySelector('a[data-action="place-route-from"]');
        actionRouteFrom.setAttribute('data-lat', place.Location.Lat);
        actionRouteFrom.setAttribute('data-lng', place.Location.Lng);
        var actionRouteTo = quickActions.querySelector('a[data-action="place-route-to"]');
        actionRouteTo.setAttribute('data-lat', place.Location.Lat);
        actionRouteTo.setAttribute('data-lng', place.Location.Lng);
        if (isRoutingDisabled) {
            actionRouteFrom.classList.add('disabled');
            actionRouteTo.classList.add('disabled');
        } else {
            actionRouteFrom.classList.remove('disabled');
            actionRouteTo.classList.remove('disabled');
        }
    }

    function createQuickActionsForPosition(asset, location) {
        document.getElementById('map-item-actions-position').classList.add('is-visible');
        var quickActions = document.getElementById('position-information-actions-list');
        var actionPositionToPlace = quickActions.querySelector('a[data-action="position-to-place"]');
        actionPositionToPlace.classList.remove('disabled');
        actionPositionToPlace.setAttribute('data-asset-id', asset.Id);
        actionPositionToPlace.setAttribute('data-lat', location.Lat);
        actionPositionToPlace.setAttribute('data-lng', location.Lng);
        actionPositionToPlace.setAttribute('data-position-id', location.Id);
        if (!tracking.user.canEditPlaces) {
            actionPositionToPlace.classList.add('disabled');
        }
        var actionHide = quickActions.querySelector('a[data-action="position-hide"]');
        actionHide.setAttribute('data-asset-id', asset.Id);

        var actionActivity = quickActions.querySelector('a[data-action="position-activity"]');
        actionActivity.setAttribute('data-asset-id', asset.Id);

        var actionPositionVisibility = quickActions.querySelector('a[data-action="position-toggle-map"]');
        actionPositionVisibility.setAttribute('data-position-id', location.Id);
        actionPositionVisibility.setAttribute('data-asset-id', asset.Id);
        actionPositionVisibility.setAttribute('data-hidden', location.IsHidden === true);
        actionPositionVisibility.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#' + (location.IsHidden ? 'visible' : 'invisible'));
        actionPositionVisibility.querySelector('span').innerText = location.IsHidden ? tracking.strings.SHOW_POSITION : tracking.strings.HIDE_POSITION;
        actionPositionVisibility.title = location.IsHidden ? tracking.strings.SHOW_POSITION : tracking.strings.HIDE_POSITION;

        var actionOptions = quickActions.querySelector('a[data-action="position-options"]');
        actionOptions.setAttribute('data-asset-id', asset.Id);
        var actionMeasureDistanceTo = quickActions.querySelector('a[data-action="position-measure-distance"]');
        actionMeasureDistanceTo.setAttribute('data-lat', location.Lat);
        actionMeasureDistanceTo.setAttribute('data-lng', location.Lng);
        var isRoutingDisabled = (tracking.options.enabledFeatures.indexOf('GET_ROUTE') === -1);
        var actionRouteFrom = quickActions.querySelector('a[data-action="position-route-from"]');
        actionRouteFrom.setAttribute('data-lat', location.Lat);
        actionRouteFrom.setAttribute('data-lng', location.Lng);
        var actionRouteTo = quickActions.querySelector('a[data-action="position-route-to"]');
        actionRouteTo.setAttribute('data-lat', location.Lat);
        actionRouteTo.setAttribute('data-lng', location.Lng);
        if (isRoutingDisabled) {
            actionRouteFrom.classList.add('disabled');
            actionRouteTo.classList.add('disabled');
        } else {
            actionRouteFrom.classList.remove('disabled');
            actionRouteTo.classList.remove('disabled');
        }
    }

    function selectedMarkerChanging() {
        if (tracking.state.selectedMarker === null) {
            return;
        }
        tracking.state.selectedMarker.data.selected = false;
        markerUnhover(tracking.state.selectedMarker);
        tracking.log('stop bouncing - previously selected');
        tracking.state.selectedMarker.stopBouncing();        

        var accuracy = tracking.state.selectedMarker.data.accuracyCircle;
        if (accuracy != null) {
            removeItemFromMap(accuracy);
        }
    }

    //function animateFence(timestamp, markers) {
    //    var af = function () { animateFence(timestamp, markers); };
    //    if ((timestamp - tracking.state.fenceAnimationTime) < 500) {
    //        tracking.state.fenceAnimationRequestId = requestAnimationFrame(af);
    //        return;
    //    }
    //    markers.setStyle({ dashArray: options[i % 3] });
    //    if (i === 1000) {
    //        i = 0;
    //    }
    //    i++;

    //    tracking.state.fenceAnimationTime = timestamp;
    //    tracking.state.fenceAnimationRequestId = requestAnimationFrame(af);
    //}

    //function animateFenceBorder(fenceMarkers) {
    //    var i = 0;
    //    tracking.state.fenceAnimationTime = 0;
    //    requestAnimateFrame(animateFence)
    //}

    function moveOrOpenDialogRelativeTo($dialog, referencePoint, relativePlacement) {
        if (!$dialog.dialog('isOpen')) {
            if (window.screen.width > 768) {
                $dialog.dialog('option', 'position', {
                    my: 'left center', at: relativePlacement, of: referencePoint, collision: 'flipfit', within: tracking.data.domNodes.map, using:
                        function (param1, param2) {
                            $(this).css(param1);
                        }
                });
            }
            $dialog.dialog('open'); // dialog must be opened away from the marker so it can be moved without triggering
        } else {
            // dialog was already open but may have been resized due to content changes
            var parent = $dialog.closest('.ui-dialog');
            parent.position({
                my: 'left center',
                at: relativePlacement,
                of: referencePoint,
                collision: 'flipfit',
                within: tracking.data.domNodes.map,
                using: function (param1, param2) {
                    // if the dialog is currently in the same horizontal position as before
                    // then we will assume the user has not manually moved it and it can be
                    // repositioned programmatically to fit centered vertically
                    if ($dialog.closest('.ui-dialog').position().left == param1.left) {
                        // just move the dialog vertically
                        $(this).css(param1);
                    }
                }
            });
        }
        $dialog.dialog('moveToTop');
    }

    function markerClick(marker, type, latLng, isMapClick) {
        console.log('marker click: ' + type);
        if (latLng === null || latLng === undefined) {
            latLng = marker.getLatLng();
        }

        if (isMapClick && tracking.state.mapClickQueue.length !== 0) {
            updateChosenLocation(latLng);
            return;
        }

        // this now handles assets, places, waypoints, fences, history?
        var asset = null;
        if (marker.data.assetId !== undefined && marker.data.assetId !== null) {
            asset = findAssetById(marker.data.assetId);
        }
        var place = null;
        if (marker.data.placeId !== undefined && marker.data.placeId !== null) {
            place = findPlaceById(marker.data.placeId);
        }
        var fence = null;
        if (marker.data.fenceId !== undefined && marker.data.fenceId !== null) {
            fence = findFenceById(marker.data.fenceId);
        }
        var waypoint = null;
        if (marker.data.waypointId !== undefined && marker.data.waypointId !== null) {
            waypoint = findWaypointById(marker.data.waypointId);
        }

        if (tracking.state.selectedMarker != null && tracking.state.selectedMarker != marker) { // reclicked the same marker already selected
            selectedMarkerChanging();
        }

        if (type !== 'fence') {
            // select this marker
            marker.data.selected = true;
            markerUnhover(marker);
            tracking.state.selectedMarker = marker;

            // no map interaction if marker is hidden
            if (!marker.data.location.IsHidden) {
                if (asset !== null
                    && tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined
                    && tracking.state.activeMapMode === tracking.mapModes.HISTORY
                    && tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
                    tracking.data.history.markerClustersByAssetId[asset.Id].zoomToShowLayer(marker, function () {
                        tracking.map.panTo(latLng);
                    });
                } else if (asset !== null
                    && tracking.data.sharedView.markerClustersByAssetId[asset.Id] !== undefined
                    && tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
                    tracking.data.sharedView.markerClustersByAssetId[asset.Id].zoomToShowLayer(marker, function () {
                        tracking.map.panTo(latLng);
                    });

                }

                // bounce marker
                bounceMarker(marker);

                if (!isMapClick) {
                    if (!tracking.state.isInPlaybackMode && tracking.map.getZoom() < tracking.options.defaultZoom) {
                        tracking.map.setView(latLng, tracking.options.defaultZoom);
                    } else {
                        tracking.map.panTo(latLng);
                    }
                }
            }
        } else {
            if (!isMapClick) {
                centerOnFence(fence.Id);
            }
            tracking.state.selectedMarker = null;
        }

        if (!isMapClick) {
            // pan/zoom to marker

            // just pan to marker
        }

        // when to pan?
        //  as part of playback: yes
        //  as part of map click: no
        //  as part of UI click: yes
        // when to zoom?
        //  when marker is clustered and hidden? maybe
        //  as part of UI click: yes
        //  as part of playback: no
        //  as part of map click: no
        // when to bounce?
        //  fence: no
        //  hidden clustered position: yes for icon and visible clustered icon
        //  others: yes

        //var isZoomHandled = false;
        //if (type !== 'fence') {
        //    if (!tracking.state.isInPlaybackMode) {
        //        if ((asset !== null) && (asset.MarkerCluster !== undefined) && (tracking.state.activeMapMode === tracking.mapModes.HISTORY)) {
        //            // handle clustering for asset markers in history mode
        //            var visibleParent = asset.MarkerCluster.getVisibleParent(marker);

        //            // marker clusters don't currently bounce
        //            // we should probably check whether it should on cluster creation (i.e. if a marker inside the cluster is bouncing, then it should also bounce)
        //            var isCurrentlyBouncing = false;
        //            if (visibleParent != null) {
        //                isCurrentlyBouncing = visibleParent.isBouncing();
        //            }
        //            console.log('Is clustered icon bouncing? ' + isCurrentlyBouncing);

        //            // zoom low enough to show the individual marker inside the cluster
        //            asset.MarkerCluster.zoomToShowLayer(marker, function () {
        //                tracking.map.panTo(latLng);
        //                bounceMarker(marker);
        //            });
        //            isZoomHandled = true;
        //        }
        //        if (!isZoomHandled) {
        //            if (tracking.map.getZoom() < tracking.options.defaultZoom) {
        //                tracking.map.setView(latLng, tracking.options.defaultZoom);
        //            } else {
        //                tracking.map.panTo(latLng);
        //            }
        //        }
        //    } else {
        //        tracking.map.panTo(latLng);
        //    }
        //} else {
        //    centerOnFence(fence.Id);
        //    isZoomHandled = true;
        //}

        // in mobile mode, show the map
        if (window.screen.width <= 768) {
            hidePrimaryPanel();
        }

        var dialogPanel = document.getElementById('dialog-map-item-information');

        // hide prior quick actions
        var actionOptions = dialogPanel.querySelectorAll('.dialog-titlebar .dialog-quick-actions');
        _.each(actionOptions, function(actionOption) {
            actionOption.classList.remove('is-visible');
        });

        // populate quick actions and dialog content
        var dialog = tracking.data.domNodes.infoDialogs.mapItemInformation;
        var $dialog = $(dialog);

        // clear previous data values - check about close callback first?
        $dialog.removeData('location'); // used?
        $dialog.removeData('assetId');  // used?
        dialog.removeAttribute('data-asset-id');
        dialog.removeAttribute('data-position-id');
        dialog.removeAttribute('data-fence-id');
        dialog.removeAttribute('data-waypoint-id');
        dialog.removeAttribute('data-shared-view-id');
        dialog.data = {};

        var location = marker.data.location;
        if (location !== undefined && location !== null) {
            if (location.Id !== undefined) {
                dialog.setAttribute('data-position-id', location.Id);
            }
            $dialog.data('location', location);
            dialog.data.location = location;
        }

        var markerIcon = marker._icon;
        var content = '';
        var titleIcon = '';
        var contentCreatedCallback = null;
        var closeCallback = null;

        var titleFragment = document.createDocumentFragment();

        if (asset !== null) {
            if (!marker.data.location.IsHidden) {
                // show accuracy of location, if available
                var accuracy = marker.data.accuracyCircle;
                if (accuracy != null) {
                    addItemToMap(accuracy);
                } else if (asset !== null) {
                    if ((location.Accuracy != null) && (location.Accuracy != '')) {
                        accuracy = L.circle(latLng, {
                            radius: location.Accuracy,
                            color: asset.Color,
                            fillColor: asset.Color,
                            opacity: 1,
                            fillOpacity: 0.15,
                            weight: 2
                        }).bindTooltip(asset.Name);
                        addItemToMap(accuracy);
                        marker.data.accuracyCircle = accuracy;
                    }
                }
            }

            createQuickActionsForPosition(asset, location);

            dialog.setAttribute('data-asset-id', asset.Id);
            dialog.data.assetId = asset.Id;
            $dialog.data('assetId', asset.Id);

            if (markerIcon === undefined && tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined && tracking.state.activeMapMode === tracking.mapModes.HISTORY && tracking.state.activeViewMode === tracking.viewModes.NORMAL) {
                var visibleMarker = tracking.data.history.markerClustersByAssetId[asset.Id].getVisibleParent(marker);
                if (visibleMarker !== null) {
                    markerIcon = visibleMarker._icon;
                }
            } else if (markerIcon === undefined && tracking.data.sharedView.markerClustersByAssetId[asset.Id] !== undefined && tracking.state.activeViewMode === tracking.viewModes.SHARED_VIEW) {
                var visibleMarker = tracking.data.sharedView.markerClustersByAssetId[asset.Id].getVisibleParent(marker);
                if (visibleMarker !== null) {
                    markerIcon = visibleMarker._icon;
                }
            }
            titleFragment = createDialogTitleFragment(asset.Name, (tracking.options.hideDeviceName ? null : asset.DeviceName));
            titleIcon = createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false, null, false, false);
            content = populatePositionInformation(asset, marker);
            contentCreatedCallback = function () {
                $('#asset-information-accordion,.alert-information-accordion').on('show.bs.collapse', function (e) {
                    e.target.previousElementSibling.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#angle-up');
                    if (e.target.id === 'accordion-garmin-content') {
                        var data = $(e.target.querySelector('.GarminSubmissionItems'));
                        var formId = data.data('formId');
                        var isLoaded = data.data('formLoaded') === true;
                        if (!isLoaded) {
                            loadGarminFormSubmission(asset.Id, formId, e.target);
                        }
                        data.data('formLoaded', true);
                    }
                });
                $('#asset-information-accordion,.alert-information-accordion').on('hide.bs.collapse', function (e) {
                    e.target.previousElementSibling.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#angle-down');
                });
            };
        } else if (place !== null) {
            createQuickActionsForPlace(place);

            dialog.setAttribute('data-place-id', place.Id);
            dialog.data.placeId = place.Id;

            titleFragment = createDialogTitleFragment(place.Name, tracking.strings.PLACE);
            titleIcon = createMarkerPath('Generic', place.Color, null, null, null, false, null, false, false);
            content = populatePlaceInformation(place);
        } else if (fence !== null) {
            createQuickActionsForGeofence(fence);
            dialog.setAttribute('data-fence-id', fence.Id);
            dialog.data.fenceId = fence.Id;
            titleFragment = createDialogTitleFragment(fence.Name, tracking.strings.GEOFENCE);
            titleIcon = createMarkerPath('Fence', getFenceColor(fence), null, null, null, false, null, false, false);
            content = populateGeofenceInformation(fence);
        } else if (waypoint !== null) {
            var waypointAsset = findAssetById(waypoint.AssetId);
            createQuickActionsForWaypoint(waypoint, waypointAsset);

            dialog.setAttribute('data-waypoint-id', waypoint.Id);
            dialog.data.waypointId = waypoint.Id;
            titleFragment = createDialogTitleFragment(waypoint.Name, tracking.strings.WAYPOINT);
            titleIcon = createMarkerPath('Waypoint', waypointAsset.Color, null, null, null, false, null, false, false);
            content = populateWaypointInformation(waypoint, waypointAsset);
            contentCreatedCallback = function () {
                if ((waypoint.route != null) && (waypoint.routingService != null)) {
                    waypoint.routingService.route();
                    $('#WaypointRoutePanel').append(waypoint.routingService.control);
                }
            };
        }

        $dialog.empty();
        $dialog.append(content);
        if (contentCreatedCallback !== null) {
            contentCreatedCallback();
        }

        $dialog.dialog('option', 'title', titleFragment);
        var dialogTitleBar = dialogPanel.querySelector('div.ui-dialog-titlebar');
        //var dialogTitle = dialogPanel.querySelector('.dialog-titlebar');
        dialogTitleBar.classList.remove('has-svg-icon');
        if (titleIcon != '') {
            dialogTitleBar.style.backgroundImage = 'url(' + titleIcon + ')';
        } else {
            dialogTitleBar.style.backgroundImage = null;
        }

        //if (isHover && !isMarkerSelected) {
        //    $(dialog).closest('.ui-dialog').addClass('is-hover');
        //} else {
        //    $(dialog).closest('.ui-dialog').removeClass('is-hover');
        //}

        // position the dialog
        var relativePlacement = 'right center';
        var referencePoint = markerIcon;
        //if (type === 'fence' && fence !== null) {
        //    var fenceBounds = getFenceBounds(fence.Id);
        //    if (fenceBounds !== null) {
        //        var latLng = L.latLng(fenceBounds.getCenter().lat, fenceBounds.getEast());
        //        var tooltipPoint = tracking.map.latLngToContainerPoint(latLng);
        //        tooltipPoint.x += getMapLeftOffset();
        //    }
        //    referencePoint =  { pageX: tooltipPoint.x, pageY: tooltipPoint.y};
        //}
        if (referencePoint === undefined || referencePoint === null) {
            // if there's no icon for this location (such as a live position that no longer is mapped), then center it on the map
            // todo: place it at the appropriate location on the map instead of in the center by showing its marker
            referencePoint = tracking.map._container;
            relativePlacement = 'center center';
        }

        // only move the dialog if its not already open
        moveOrOpenDialogRelativeTo($dialog, referencePoint, relativePlacement);
        
        $dialog.unbind('dialogclose');

        $dialog.bind('dialogclose', function (event, ui) {
            tracking.log('dialog close');
            selectedMarkerChanging();

            if (closeCallback !== null) {
                closeCallback();
            }
            tracking.state.selectedMarker = null;
            playbackEnd();
            $(document).off('keydown.map-item-information-dialog');
        });

        //// bounce selected marker via animation
        //if (!isZoomHandled) {
        //    bounceMarker(marker);
        //}
    }

    function addWorldIR() {
        removeWorldIR();
        worldCloudOverlay = new google.maps.GroundOverlay('https://wublast.wunderground.com/cgi-bin/WUBLAST?maxlat=64&maxlon=180&minlat=-64&minlon=-180&width=4000&height=2000&gtt=180&frame=0&num=1&delay=25&key=sat_ir4&proj=ll&rand=' + new Date().getTime() + '&timelabel=0&timelabel.x=1032&timelabel.y=41&cors=1&api_key=56bf855f82bdf16a',
            {
                north: 64,
                south: -64,
                west: -180,
                east: 180
            }, { opacity: 0.8 });
        worldCloudOverlay.setMap(tracking.map);
    }

    function removeWorldIR() {
        if (worldCloudOverlay != null) {
            worldCloudOverlay.setMap(null);
        }
        worldCloudOverlay = null;
    }

    function refreshWorldIR() {
        if (isOverlayActive(worldCloudOverlay)) {
            //$j('#radar .loading').show();
            tracking.log('World Cloud IR refreshing.');
            removeOverlay(worldCloudOverlay);
            addOverlay(worldCloudOverlay);
            tracking.data.worldIR.timer = setTimeout(refreshWorldIR, tracking.options.worldIRRefreshMinutes * 60 * 1000);
        } else {
            tracking.log('World cloud IR not active');
        }
    }

    function refreshRadar() {
        // if the radar is enabled, refresh it
        if (isOverlayActive(radarOverlay)) {
            tracking.log('Radar refreshing.');
            removeOverlay(radarOverlay);
            addOverlay(radarOverlay);
            tracking.data.radar.timer = setTimeout(refreshRadar, tracking.options.radarRefreshMinutes * 60 * 1000);
        } else {
			tracking.data.radar.timer = null;
            tracking.log('radar not active');
        }
    }

    function refreshRadarAustralia() {
        // if the radar is enabled, refresh it
        if (isOverlayActive(radarAustraliaOverlay)) {
            tracking.log('Australia Radar refreshing.');
            removeOverlay(radarAustraliaOverlay);
            addOverlay(radarAustraliaOverlay);
            tracking.data.radarAustralia.timer = setTimeout(refreshRadarAustralia, tracking.options.radarRefreshMinutes * 60 * 1000);
            //enableRadarAustralia(true, function () {
            //    removeOverlay(radarAustraliaOverlay);
            //    addOverlay(radarAustraliaOverlay);
            //    tracking.data.radarAustralia.timer = setTimeout(refreshRadarAustralia, tracking.options.radarRefreshMinutes * 60 * 1000);
            //});
        } else {
            tracking.data.radarAustralia.timer = null;
            tracking.log('Australia radar not active.');
        }
    }

    function simpleAnimation(renderFrameCallback, duration) {
    	var _timerId,
			_frame = 0;

    	duration = (duration && duration > 0) ? duration : 150;

    	_timerId = setInterval(function () {
    		var progress = (_frame * 30) / duration;

    		if (progress > 1) {
    			progress = 1;
    		}

    		renderFrameCallback(progress);

    		if (progress == 1) {
    			clearInterval(_timerId);
    		}

    		_frame++;
    	}, 30);
    }

    function bouncePin(pin, height, duration) {
    	height = (height && height > 0) ? height : 150;
    	duration = (duration && duration > 0) ? duration : 1000;

    	var anchor = pin.getAnchor();
    	var from = anchor.y + height;

    	pin.setOptions({ anchor: new Microsoft.Maps.Point(anchor.x, anchor.y + height) });

    	simpleAnimation(
            function (progress) {
            	var delta = Math.abs(Math.sin(progress * 2.5 * Math.PI)) / Math.exp(3 * progress);
            	var y = from - height * (1 - delta);
            	pin.setOptions({ anchor: new Microsoft.Maps.Point(anchor.x, y) });
            },
            duration
        );
    }

    function bounceMarker(marker) {
        if (marker == null) {
            return;
        }
        var isSelected = marker.data.selected;
        var assetId = marker.data.assetId;
        var tripId = marker.data.tripId;
        var isTrip = tripId !== undefined && tripId !== null;

        // trips and history mode can have clustered markers
        if (assetId != null) {
            var asset = tracking.findAssetById(assetId);
            if (asset !== null) {
                if (isTrip) {
                    var trip = findTripById(tripId);
                    if (trip !== null && tracking.data.trips.markerClustersByTripId[trip.Id] !== undefined) {
                        marker = tracking.data.trips.markerClustersByTripId[trip.Id].getVisibleParent(marker);
                    }
                } else if (tracking.state.activeMapMode !== tracking.mapModes.LIVE && tracking.data.history.markerClustersByAssetId[asset.Id] !== undefined) {
                    marker = tracking.data.history.markerClustersByAssetId[asset.Id].getVisibleParent(marker);
                }
            }
        }
        if (marker == null) {
            return;
        }

        if (isSelected) {
        	// bounce me
            if (!marker.isBouncing()) {
                console.log('is not bouncing - toggling');
                marker.setBouncingOptions({
                    bounceHeight: 15,
                    bounceSpeed: 150,
                    exclusive: true
                }).toggleBouncing();
            } else {
                console.log('already bouncing');
            }
        } else {
            // stop bouncing, don't rebounce
        	marker.stopBouncing();
        }
    }

    function LatLngToTileCoordinate(latlng, zoom) {
        var MERCATOR_RANGE = 256;
        var proj = tracking.map.getProjection();
        // world coordinate
        var worldCoord = proj.fromLatLngToPoint(latlng);
        // pixel
        var pixelCoord = new google.maps.Point(worldCoord.x * Math.pow(2, zoom), worldCoord.y * Math.pow(2, zoom));
        // tile
        var tileCoord = new google.maps.Point(Math.floor(pixelCoord.x / MERCATOR_RANGE), Math.floor(pixelCoord.y / MERCATOR_RANGE));
        return tileCoord;
    }

    function TileToLocationRect(tile) {
    	var mapSize = Math.pow(2, tile.z);
    	var west = ((tile.x * 360) / mapSize) - 180;
    	var east = (((tile.x + 1) * 360) / mapSize) - 180;

    	var efactor = Math.exp((0.5 - tile.y / mapSize) * 4 * Math.PI);
    	var north = (Math.asin((efactor - 1) / (efactor + 1))) * (180 / Math.PI);

    	efactor = Math.exp((0.5 - (tile.y + 1) / mapSize) * 4 * Math.PI);
    	var south = (Math.asin((efactor - 1) / (efactor + 1))) * (180 / Math.PI);
		var rect = L.latLngBounds(L.latLng(north, west), L.latLng(south, east));
		return rect;
    }

    function enableLayerTraffic() {
        if (tracking.data.traffic.isActive) {
            return;
        }

        // extra check for whether traffic API is available
        if (tracking.layers.traffic == null && typeof MQ !== 'undefined') {
            tracking.layers.traffic = MQ.trafficLayer({ layers: ['flow'] });
        }
        if (tracking.layers.traffic != null) {
            addLayer(tracking.layers.traffic);
            tracking.data.traffic.isActive = true;
            $('#map-layers-list .traffic').addClass('active');
            Cookies.set('layer-traffic', true, { expires: 365, path: '/', secure: true });
        }
    }

    function disableLayerTraffic() {
        if (!tracking.data.traffic.isActive) {
            return;
        }

        removeLayer(tracking.layers.traffic);
        tracking.data.traffic.isActive = false;
        $('#map-layers-list .traffic').removeClass('active');
        Cookies.remove('layer-traffic');
    }

    function enableLayerWeather(requestData) {
        if (tracking.data.weather.isActive)
            return;
        tracking.data.weather.isActive = true;
        tracking.data.weather.layer = L.geoJSON(null, {
            style: function (feature) {

            },
            onEachFeature: function (feature, layer) {
                if (feature.properties != null) {
                    if (feature.properties.city != null) {
                        if (feature.properties.temperature != null) {
                            //var tooltipIcon = '<img src="' + feature.properties.icon + '"><br>';
                            var tooltip = "<strong>" + feature.properties.city + "</strong>"
                                + "<br />" + feature.properties.temperature + "°C"
                                + "<br />" + feature.properties.weather;
                            layer.bindPopup(tooltip);
                            layer.bindTooltip(tooltip);
                        }
                    }
                }
            },
            pointToLayer: function (feature, latlng) {
                if ((feature.properties != undefined) && (feature.properties.icon != undefined)) {
                    var icon = L.icon({
                        iconUrl: feature.properties.icon,
                        iconSize: [ 50, 50 ],
                        iconAnchor: [25, 25],
                        tooltipAnchor: [25, 0],
                        popupAnchor: [0, -25]
                    });
                    return L.marker(latlng, { icon: icon });
                }
                return L.marker(latlng);
            }
        });

        if (requestData) {
            checkWeatherForecast();
        }
        addLayer(tracking.data.weather.layer);
        $j('#map-layers-list .weather').addClass('active');
        Cookies.set('layer-weather', true, { expires: 365, path: '/', secure: true });
    }

    function disableLayerWeather() {
        if (!tracking.data.weather.isActive)
            return;
        removeLayer(tracking.data.weather.layer);
        tracking.data.weather.isActive = false;
        $j('#map-layers-list .weather').removeClass('active');
        Cookies.remove('layer-weather');
    }

    function addCustomLayer(layerId) {
        for (var i = 0; i < tracking.options.customLayers.length; i++) {
            var layer = tracking.options.customLayers[i];
            if (layer.id == layerId) {
            	if (layer.layer != null) {
            		addOverlay(layer.layer);
                    return;
                } else {
                    switch (layer.type) {
                        case 1:
                            var layerBounds = undefined;
                            if (layer.bounds !== undefined && layer.bounds !== '|||') {
                                var bounds = layer.bounds.split('|');
                                layerBounds = L.latLngBounds(L.latLng(bounds[0], bounds[1]), L.latLng(bounds[2], bounds[3]));
                            }
                            layer.layer = new L.tileLayer(layer.url, {
                                bounds: layerBounds,
                                opacity: layer.opacity,
                                minZoom: layer.minZoom,
                                maxZoom: layer.maxZoom,
                                zIndex: 6
                            });
                            break;
                        case 5:
                            var layerBounds = undefined;
                            if (layer.bounds !== undefined && layer.bounds !== '|||') {
                                var bounds = layer.bounds.split('|');
                                layerBounds = L.latLngBounds(L.latLng(bounds[0], bounds[1]), L.latLng(bounds[2], bounds[3]));
                            }
                            layer.layer = L.tileLayer.wms(layer.url, {
                                bounds: layerBounds,
                                opacity: layer.opacity,
                                minZoom: layer.minZoom,
                                maxZoom: layer.maxZoom,
                                layers: layer.layers,
                                format: layer.format,
                                transparent: layer.transparent,
                                zIndex: 6
                            });
                            break;
                        case 0:
                            // todo: KMZ support and GroundOverlay support
                            // consider https://raw.githubusercontent.com/falcacibar/leaflet-plugins/f2ce428c1bbca178dd2c887a4721ef528d829ace/layer/vector/KML.js
                            // and hybrid support of L.imageOverlay.. should be able to parse
                            // GroundOverlay and use Icon and LatLonBox for placement
                            // jszip and jsziputils for unzipping KMZ? or have the server handle unzipping KMZ on upload?
                            // pull the styling first
                            var excludedProperties = ['stroke', 'stroke-opacity', 'fill-opacity', 'styleUrl', 'styleHash'];
                            layer.styles = [];
                            $.ajax({
                                url: wrapUrl('/uploads/layers/' + layer.path),
                                type: 'get',
                                dataType: 'xml',
                                async: false,
                                success: function (data) {
                                    $(data).find('Style').each(function (index) {
                                        var item = $(this);
                                        // may have multiple styles associated with it
                                        // so we should prioritize via PolyStyle, LineStyle, IconStyle, LabelStyle
                                        var style = {
                                            // defaults
                                            fillColor: null,
                                            fillOpacity: 0.2,
                                            color: '#3388ff',
                                            opacity: 1.0,
                                            weight: 3
                                        };
                                        // parse KML styles, which should be in abgr format
                                        var fillColor = item.find('PolyStyle color').text();
                                        var strokeColor = item.find('LineStyle color').text();
                                        var strokeWeight = item.find('LineStyle width').text();
                                        var iconScale = item.find('IconStyle scale').text();
                                        if (iconScale !== '' && parseFloat(iconScale) !== NaN) {
                                            style.radius = 10 * parseFloat(iconScale);
                                        }
                                        if (strokeWeight !== '') {
                                            style.weight = strokeWeight;
                                        }
                                        if (strokeColor === '') {
                                            strokeColor = item.find('IconStyle color').text();
                                        }
                                        if (strokeColor === '') {
                                            strokeColor = item.find('LabelStyle color').text();
                                        }
                                        if (strokeColor !== '') {
                                            // parse opacity
                                            style.color = '#' + strokeColor.substring(6, 8) + strokeColor.substring(4, 6) + strokeColor.substring(2, 4);
                                            style.opacity = parseInt(strokeColor.substring(0, 2), 16) / 255;
                                        }
                                        if (fillColor !== '') {
                                            style.fillColor = '#' + fillColor.substring(6, 8) + fillColor.substring(4, 6) + fillColor.substring(2, 4);
                                            style.fillOpacity = parseInt(fillColor.substring(0, 2), 16) / 255;
                                            if (strokeColor === '') {
                                                style.color = style.fillColor;
                                                style.opacity = style.fillOpacity;
                                            }
                                        }
                                        // non-KML
                                        if (strokeColor === '') {
                                            style.color = item.find('color').text();
                                        }
                                        if (style.color === '') {
                                            style.color = '#ffffff'; // default isn't always populated
                                        }

                                        layer.styles[item.attr('id')] = style;
                                    });

                                    $(data).find('StyleMap').each(function (index) {
                                        var item = $(this);
                                        layer.styles[item.attr('id')] = layer.styles[item.find('styleUrl').eq(0).text().substr(1)];
                                    });


                                    layer.layer = L.geoJSON(null, {
                                        onEachFeature: function (feature, layer) {
                                            if (feature.properties != null) {
                                                var hasPopup = false;
                                                if (feature.properties.name != null) {
                                                    layer.bindTooltip(feature.properties.name);
                                                    if (feature.properties.description != null) {
                                                        layer.bindPopup(el('div', [el('h3', feature.properties.name), text(feature.properties.description)]));
                                                        hasPopup = true;
                                                    }
                                                }
                                                if (!hasPopup) {
                                                    var rows = [];
                                                    _.each(feature.properties, function (val, key, list) {
                                                        if (_.indexOf(excludedProperties, key) !== -1) {
                                                            return;
                                                        }
                                                        rows.push(el('tr', [el('td', key), el('td', val)]));
                                                    });
                                                    if (rows.length > 0) {
                                                        layer.bindPopup(el('table.feature-properties', el('tbody', rows)));
                                                    }
                                                }
                                            }
                                        },
                                        pointToLayer: function (feature, latlng) {
                                            // check for custom styling for the placemark
                                            if ((feature.properties != undefined) && (feature.properties.styleUrl != undefined)) {
                                                var iconStyle = layer.styles[feature.properties.styleUrl.substr(1)];
                                                if (iconStyle === null) {
                                                    // map be using a style map
                                                    iconStyle = layer.styles[feature.properties.styleUrl.substr(1) + '-normal'];
                                                }
                                                if (iconStyle !== null) {
                                                    // with placemarks, GoogleEarth sets both a PolyStyle and an IconStyle with the PolyStyle having a fill of black
                                                    // so we should ignore fillColor for them and set the fillColor to the strokeColor at a lower opacity
                                                    if (iconStyle.color !== null) {
                                                        iconStyle.fillColor = iconStyle.color;
                                                        if (iconStyle.opacity !== null) {
                                                            iconStyle.fillOpacity = iconStyle.opacity * 0.5;
                                                        }
                                                    }
                                                    return L.circleMarker(latlng, iconStyle);
                                                }
                                            }
                                            return L.marker(latlng);
                                        },
                                        style: function (feature) {
                                            if ((feature.properties != undefined) && (feature.properties.styleUrl != undefined)) {
                                                var layerStyle = layer.styles[feature.properties.styleUrl.substr(1)];
                                                if (layerStyle === undefined) {
                                                    // map be using a style map
                                                    layerStyle = layer.styles[feature.properties.styleUrl.substr(1) + '-normal'];
                                                }
                                                if (layerStyle !== undefined) {
                                                    return layerStyle;
                                                }
                                            }
                                        }
                                    });
                                    omnivore.kml.parse(data, null, layer.layer)
                                        .on('ready', function () {
                                        }
                                    );
                                }
                            });
							break;
                    	case 2:
                    		// pull json from URL
                    		layer.layer = L.geoJSON(null, {
                    			onEachFeature: function (feature, layer) {
                                    var items = [];
									if (feature.properties != null) {
										for (var name in feature.properties) {
											if (!feature.properties.hasOwnProperty(name)) { // skip inherited properties
												continue;
                                            }
                                            items.push(el('strong', name + ':'));
                                            items.push(text(' ' + feature.properties[name]));
                                            items.push(el('br'));
										}
									}
									if (items.length > 0) {
                                        var tooltip = el('div', items);
										layer.bindPopup(tooltip); // perhaps bindTooltip instead?
									}
                    			}
                    		});
                    		$j.ajax({
                    			dataType: 'json',
                    			url: wrapUrl('/uploads/layers/' + layer.path),
                    			success: function (data) {
                    				$(data.features).each(function (key, data) {
                    					layer.layer.addData(data);
                    				});
                    			}
                    		});
							break;
                    	case 3:
                    		layer.layer = L.geoJSON(null, {
                    			onEachFeature: function (feature, layer) {
                    				if (feature.properties != null) {
                    					if (feature.properties.name != null) {
                    						layer.bindTooltip(feature.properties.name);
                    					}
                    				}
                    			}
                    		});
                    		omnivore.gpx(wrapUrl('/uploads/layers/' + layer.path), null, layer.layer)
								.on('ready', function () {
								});
                            break;
                        case 4:
                            var bounds = layer.bounds.split('|');
                            var layerBounds = L.latLngBounds(L.latLng(bounds[0], bounds[1]), L.latLng(bounds[2], bounds[3]));
                            layer.layer = L.imageOverlay(wrapUrl('/uploads/layers/' + layer.path), layerBounds);
                            break;
                    }
                    if (layer.layer != null) {
                    	addOverlay(layer.layer);
                    }
                    return;
                }
            }
        }
    }

    function removeCustomLayer(layerId) {
        for (var i = 0; i < tracking.options.customLayers.length; i++) {
            var layer = tracking.options.customLayers[i];
            if (layer.id == layerId) {
                if (layer.layer != null) {
                    removeOverlay(layer.layer);
                    return;
                }
            }
        }
    }

    function updateUIPreferences() {

    }

    function enableCustomLayers(layers) {
        var layerIds = layers.split(',');
        for (var i = 0; i < layerIds.length; i++) {
            var layerId = parseInt(layerIds[i]);
            enableCustomLayer(layerId);
        }
    }

    function enableCustomLayer(layerId) {
        // check if currently enabled
        if ($j.inArray(layerId, tracking.data.activeLayers) !== -1) {
            return;
        }

        addCustomLayer(layerId);

        tracking.data.activeLayers.push(layerId);
        $j('#map-layers-list .custom-' + layerId).addClass('active');
        Cookies.set('layer-custom', tracking.data.activeLayers.join(','), { expires: 365, path: '/', secure: true });
    }

    function disableCustomLayer(layerId) {
        var index = $j.inArray(layerId, tracking.data.activeLayers);
        if (index === -1)
            return;

        removeCustomLayer(layerId);

        // remove from array
        tracking.data.activeLayers.splice(index, 1);
        $j('#map-layers-list .custom-' + layerId).removeClass('active');
        Cookies.set('layer-custom', tracking.data.activeLayers.join(','), { expires: 365, path: '/', secure: true });
    }

    function disableLayerClouds() {
        if (!tracking.data.clouds.isActive)
            return;
        removeLayer(tracking.layers.clouds);
        tracking.data.clouds.isActive = false;
        $j('#map-layers-list .clouds').removeClass('active');
        Cookies.remove('layer-clouds');
    }

    function enableLayerClouds() {
        if (tracking.data.clouds.isActive)
            return;
        addLayer(tracking.layers.clouds);
        tracking.data.clouds.isActive = true;
        $j('#map-layers-list .clouds').addClass('active');
        Cookies.set('layer-clouds', true, { expires: 365, path: '/', secure: true });
    }

    function collapseMapType() {
        $j('#map_type_list').hide();
        $j('body').unbind('click.maptype');
    }

    function expandMapType() {
        $j('#map_type_list').show();

        $j('body').bind('click.maptype', function (e) {
            if ($(e.target).parents('#map_type').length == 0) {
                collapseMapType();
            }
        });
    }

    function collapseMapLayers() {
        $j('#map_layers_list').hide();
        $j('body').unbind('click.maplayers');
    }

    function expandMapLayers() {
        $j('#map_layers_list').show();

        $j('body').bind('click.maplayers', function(e) {
                if ($(e.target).parents('#map_layers').length==0) {
                collapseMapLayers();
                }
        });
    }

    function enableLayerSeamap() {
        if(tracking.data.seamap.isActive)
            return;

        tracking.data.seamap.isActive = true;
        addOverlay(seamapOverlay);
        $j('#map-layers-list .seamap').addClass('active');
        Cookies.set('layer-seamap', true, { expires: 365, path: '/', secure: true });
    }

    function disableLayerSeamap() {
        if (!tracking.data.seamap.isActive)
            return;
        tracking.data.seamap.isActive = false;
        removeOverlay(seamapOverlay);
        $j('#map-layers-list .seamap').removeClass('active');
        Cookies.remove('layer-seamap');
    }

    function enableLayerOil() {
        if (tracking.data.oil.isActive)
            return;

        tracking.data.oil.isActive = true;
        addOverlay(oilOverlay);
        $j('#map-layers-list .oil').addClass('active');
        Cookies.set('layer-oil', true, { expires: 365, path: '/', secure: true });
    }

    function disableLayerOil() {
        if (!tracking.data.oil.isActive)
            return;
        tracking.data.oil.isActive = false;
        removeOverlay(oilOverlay);
        $j('#map-layers-list .oil').removeClass('active');
        Cookies.remove('layer-oil');
    }

    function enableLayerMaritime() {
        if(tracking.data.maritime.isActive)
            return;

        tracking.data.maritime.isActive = true;
        addOverlay(maritimeOverlay);
        $j('#map-layers-list .maritime').addClass('active');
        Cookies.set('layer-maritime', true, { expires: 365, path: '/', secure: true });
    }

    function disableLayerMaritime() {
        if(!tracking.data.maritime.isActive)
            return;
        tracking.data.maritime.isActive = false;
        removeOverlay(maritimeOverlay);
        $j('#map-layers-list .maritime').removeClass('active');
        Cookies.remove('layer-maritime');
    }

    function enableLayerIridium() {
        if (tracking.data.iridium.isActive)
            return;
        tracking.data.iridium.isActive = true;
        //startIridiumOverlay();
        startSatelliteOverlay('Iridium');
        $j('#map-layers-list .iridium').addClass('active');
        Cookies.set('layer-iridium', true, { expires: 365, path: '/', secure: true });
    }

    function enableLayerIridiumNext() {
        if (tracking.data.iridiumnext.isActive)
            return;
        tracking.data.iridiumnext.isActive = true;
        startSatelliteOverlay('IridiumNext');
        $j('#map-layers-list .iridiumnext').addClass('active');
        Cookies.set('layer-iridiumnext', true, { expires: 365, path: '/', secure: true });
    }

    function enableLayerGlobalstar() {
        if (tracking.data.globalstar.isActive)
            return;
        tracking.data.globalstar.isActive = true;
        startSatelliteOverlay('Globalstar');
        $j('#map-layers-list .globalstar').addClass('active');
        Cookies.set('layer-globalstar', true, { expires: 365, path: '/', secure: true });
    }

    function enableLayerInmarsat() {
        if (tracking.data.inmarsat.isActive)
            return;
        tracking.data.inmarsat.isActive = true;
        startSatelliteOverlay('Inmarsat');
        $j('#map-layers-list .inmarsat').addClass('active');
        Cookies.set('layer-inmarsat', true, { expires: 365, path: '/', secure: true });
    }

    function enableLayerOrbcomm() {
        if (tracking.data.orbcomm.isActive)
            return;
        tracking.data.orbcomm.isActive = true;
        startSatelliteOverlay('Orbcomm');
        $j('#map-layers-list .orbcomm').addClass('active');
        Cookies.set('layer-orbcomm', true, { expires: 365, path: '/', secure: true });
    }

    function disableLayerGeostationary() {
        if (!tracking.data.geostationary.isActive)
            return;
        tracking.data.geostationary.isActive = false;
        stopSatelliteOverlay('Geostationary');
        $j('#map-layers-list .geostationary').removeClass('active');
        Cookies.remove('layer-geostationary');
    }

    function enableLayerGeostationary() {
        if (tracking.data.geostationary.isActive)
            return;
        tracking.data.geostationary.isActive = true;
        startSatelliteOverlay('Geostationary');
        $j('#map-layers-list .geostationary').addClass('active');
        Cookies.set('layer-geostationary', true, { expires: 365, path: '/', secure: true });
    }

    function disableLayerIridium() {
        if (!tracking.data.iridium.isActive)
            return;
        tracking.data.iridium.isActive = false;
        stopSatelliteOverlay('Iridium');
        $j('#map-layers-list .iridium').removeClass('active');
        Cookies.remove('layer-iridium');
    }

    function disableLayerIridiumNext() {
        if (!tracking.data.iridiumnext.isActive)
            return;
        tracking.data.iridiumnext.isActive = false;
        stopSatelliteOverlay('IridiumNext');
        $j('#map-layers-list .iridiumnext').removeClass('active');
        Cookies.remove('layer-iridiumnext');
    }

    function disableLayerGlobalstar() {
        if (!tracking.data.globalstar.isActive)
            return;
        tracking.data.globalstar.isActive = false;
        stopSatelliteOverlay('Globalstar');
        $j('#map-layers-list .globalstar').removeClass('active');
        Cookies.remove('layer-globalstar');
    }

    function disableLayerInmarsat() {
        if (!tracking.data.inmarsat.isActive)
            return;
        tracking.data.inmarsat.isActive = false;
        stopSatelliteOverlay('Inmarsat');
        $j('#map-layers-list .inmarsat').removeClass('active');
        Cookies.remove('layer-inmarsat');
    }

    function disableLayerOrbcomm() {
        if (!tracking.data.orbcomm.isActive)
            return;
        tracking.data.orbcomm.isActive = false;
        stopSatelliteOverlay('Orbcomm');
        $j('#map-layers-list .orbcomm').removeClass('active');
        Cookies.remove('layer-orbcomm');
    }

    function enableWorldIR() {
        if (tracking.data.worldIR.isActive)
            return;

        tracking.data.worldIR.isActive = true;
        addOverlay(worldCloudOverlay);
        tracking.data.worldIR.timer = setTimeout(refreshWorldIR, tracking.options.worldIRRefreshMinutes * 60 * 1000);
        $j('#map-layers-list .worldIR').addClass('active');
        Cookies.set('worldIR', true, { expires: 365, path: '/', secure: true });
    }

    function disableWorldIR() {
        if (!tracking.data.worldIR.isActive)
            return;
        tracking.data.worldIR.isActive = false;
        clearTimeout(tracking.data.worldIR.timer);
        removeOverlay(worldCloudOverlay);

        Cookies.remove('worldIR');
        $j('#map-layers-list .worldIR').removeClass('active');
    }

    function enableRadar() {
        if (tracking.data.radar.isActive)
            return;

        tracking.data.radar.isActive = true;
        addOverlay(radarOverlay);
        // use setInterval instead
        tracking.data.radar.timer = setTimeout(refreshRadar, tracking.options.radarRefreshMinutes * 60 * 1000);
        //$j('#radar a').addClass('active');
        $j('#map-layers-list .radar').addClass('active');
        $j('#legend_radar').show();
        Cookies.set('radar', true, { expires: 365, path: '/', secure: true });
    }

    function disableRadar() {
        if (!tracking.data.radar.isActive)
            return;
        tracking.data.radar.isActive = false;
        clearTimeout(tracking.data.radar.timer);
        removeOverlay(radarOverlay);
        Cookies.remove('radar');
        $j('#legend_radar').hide();
        $j('#map-layers-list .radar').removeClass('active');
    }

    function enableRadarAustraliaActive() {
        if (tracking.data.radarAustralia.isActive)
            return;
        tracking.data.radarAustralia.isActive = true;
        addOverlay(radarAustraliaOverlay);
        // use setInterval instead
        tracking.data.radarAustralia.timer = setTimeout(refreshRadarAustralia, tracking.options.radarRefreshMinutes * 60 * 1000);
        $j('#map-layers-list .radar-australia').addClass('active');
        $j('#legend_radar').show();
        Cookies.set('radar-australia', true, { expires: 365, path: '/', secure: true });
    }

    function enableRadarAustralia(forceRefresh, callback) {
        if ((tracking.data.radarAustralia.basetime == null)
            || (tracking.data.radarAustralia.issuetime == null)
            || forceRefresh) {
            // query for latest imagery
            var queryTime = new Date().getTime();
            var url = '/services/layer.ashx?type=aus';
            tracking.data.radarAustralia.gettingData = true;
            tracking.data.radarAustralia.request = $j.ajax({
                type: 'GET',
                url: wrapUrl(url),
                dataType: 'json',
                contentType: false,
                success: function (results) {
                    tracking.data.radarAustralia.basetime = results.metadata[0].basetime;
                    tracking.data.radarAustralia.issuetime = results.metadata[0].issuetime;
                    callback();
                },
                error: function () {
                    tracking.data.radarAustralia.gettingData = false;
                }
            });
        } else {
            callback();
        }
    }

    function disableRadarAustralia() {
        if (!tracking.data.radarAustralia.isActive) {
            return;
        }
        tracking.data.radarAustralia.isActive = false;
        clearTimeout(tracking.data.radarAustralia.timer);
        removeOverlay(radarAustraliaOverlay);
        Cookies.remove('radar-australia');
        $('#legend_radar').hide();
        $('#map-layers-list .radar-australia').removeClass('active');
    }

    function zoomMouseDown(event) {
        // simulate shift
        tracking.map.boxZoom._onMouseDown.call(tracking.map.boxZoom, { clientX: event.originalEvent.clientX, clientY: event.originalEvent.clientY, which: 1, shiftKey: true });
    }

    function enableDragZoom() {
        $('#map-zoom-region').addClass('active');
        tracking.data.zoom.isActive = true;
        tracking.map.dragging.disable();
        tracking.map.boxZoom.addHooks();
        tracking.map.on('mousedown', zoomMouseDown);
        tracking.map.on('boxzoomend', disableDragZoom);
	}

    function disableDragZoom() {
        clearTimeout(tracking.state.zoomTimeout);
        tracking.state.zoomTimeout = null;

        // disable icon state
        $('#map-zoom-region').removeClass('active');
        tracking.data.zoom.isActive = false;
        tracking.map.off('mousedown', zoomMouseDown);
        tracking.map.dragging.enable();
        tracking.map.boxZoom.removeHooks();
        L.DomUtil.removeClass(tracking.map._container, 'leaflet-control-boxzoom-active');
    }

    function toggleOverlay(overlay) {
        var foundAt = null;
        if (tracking.map.hasLayer(overlay)) {
            tracking.map.removeLayer(overlay);
        } else {
            tracking.map.addLayer(overlay);
        }
    }

    function addOverlay(overlay) {
        if (overlay == null)
            return;
        var isFound = false;
        if (tracking.map.hasLayer(overlay))
            return;
        tracking.map.addLayer(overlay);
    }

    function isOverlayActive(overlay) {
        if (tracking.map.hasLayer(overlay))
            return true;

        return false;
    }

    function removeOverlay(overlay) {
        tracking.map.removeLayer(overlay);
    }

    function addLayer(layer) {
        //layer.setMap(tracking.map);
        addOverlay(layer);
    }

    function removeLayer(layer) {
        //layer.setMap(null);
        removeOverlay(layer);
    }

    function wrapLat(lat) {
        if (lat > 90) {
            lat = -90 + (lat - 90);
        }
        if (lat < -90) {
            lat = 90 - (-90 - lat);
        }
        return lat;
    }

    function wrapLng(lng) {
        if (lng > 180) {
            lng = -180 + (lng - 180);
        }
        if (lng < -180) {
            lng = 180 - (-180 - lng);
        }
        return lng;
    }

    function startAisOverlay(vesselMmsis) {
        var mmsis = [];
        if (vesselMmsis !== undefined && vesselMmsis !== null) {
            mmsis = vesselMMsis;
        }
        var data = { mmsis: mmsis };
        $.ajax({
            type: 'POST',
            url: wrapUrl('/api/ais/latest'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                console.log(msg);
                tracking.data.aisMarkers = [];
                tracking.data.aisPositions = msg.positions;
                for (var i = 0; i < msg.positions.length; i++) {
                    var vessel = msg.positions[i];
                    var latlng = L.latLng(wrapLat(vessel.position.latitude), wrapLng(vessel.position.longitude));
                    var popup = "<strong>MMSI: " + vessel.mmsi + "</strong><br>Epoch: " + vessel.position.epoch + "<br>Speed: " + vessel.position.speedOverGround + "<br>Heading: " + vessel.position.trueHeading;
                    var markerOptions = {
                        radius: 5,
                        weight: 1,
                        fillColor: "#" + (Math.random().toString(16) + "000000").slice(2, 8),
                        color: '#000000',
                        opacity: 1,
                        fillOpacity: 0.75
                    };
                    var tooltip = vessel.mmsi;
                    var aisMarker = L.circleMarker(latlng, markerOptions).bindTooltip(tooltip).bindPopup(popup);
                    tracking.data.aisMarkers.push(aisMarker);
                    addItemToMap(aisMarker);
                    vessel.marker = aisMarker;
                }
            }
        });
    }

    function startSatelliteOverlay(constellation) {
        // map current positions
        var dataPost = { 'constellation': constellation };
        $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/GetSatelliteTelemetry'),
            data: JSON.stringify(dataPost),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                if (msg.d) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success === false) {
                            utility.handleWebServiceError(tracking.strings.MSG_SATELLITE_ORBIT_ERROR + ' ' + result.ErrorMessage);
                            return;
                        }
                        tracking.data.satelliteMarkers[constellation] = [];
                        tracking.data.satelliteOrbits[constellation] = result.Satellites;
                        tracking.data.satelliteIndex[constellation] = 1;
                        // map the current positions
                        for (var i = 0; i < result.Satellites.length; i++) {
                            var sat = result.Satellites[i];
                            if (sat.Positions == null) {
                                continue;
                            }
                            if (sat.Positions.length == 0) {
                                continue;
                            }
                            var pos = sat.Positions[0];
                        	// wrap locations
                            var lat = wrapLat(pos.Lat);
                            var lng = wrapLng(pos.Lng);
                            var latlng = L.latLng(lat, lng);

                            var icon = L.icon({
                            	iconUrl: '/content/images/marker-satellite-small.png',
                            	iconSize: [20, 20],
                            	iconAnchor: [10, 10]
                            });
                            var tooltip = sat.Name;
                            if (sat.Description != null) {
                                tooltip += " - " + sat.Description;
                            }
                            var satelliteMarker = L.marker(latlng, {
                                icon: icon
                                //title: sat.Name
                            }).bindTooltip(tooltip);
                            addItemToMap(satelliteMarker);
                            sat.Marker = satelliteMarker;
                            tracking.data.satelliteMarkers[constellation].push(satelliteMarker);
                        }

                        // set timeout to show the next
                        tracking.intervals.satelliteInterval[constellation] = setInterval(function () { nextTelemetryPosition(constellation); }, 10000);
                    }
                }
            },
            error: function (xhr, status, error) {
                utility.handleWebServiceError(tracking.strings.MSG_SATELLITE_ORBIT_ERROR);
            }
        });
    }

    function stopSatelliteOverlay(constellation) {
        if (tracking.data.satelliteMarkers[constellation] != null) {
            for (var j = 0; j < tracking.data.satelliteMarkers[constellation].length; j++) {
            	var marker = tracking.data.satelliteMarkers[constellation][j];
            	removeItemFromMap(marker);
            }
        }
        clearInterval(tracking.intervals.satelliteInterval[constellation]);
        tracking.data.satelliteIndex[constellation] = 0;
    }

    function nextTelemetryPosition(constellation) {
        if (tracking.data.satelliteIndex[constellation] >= 60) {
            // it's time to requery
            stopSatelliteOverlay(constellation);
            startSatelliteOverlay(constellation);
            return;
        }

        for (var i = 0; i < tracking.data.satelliteOrbits[constellation].length; i++) {
            var sat = tracking.data.satelliteOrbits[constellation][i];
            var pos = sat.Positions[tracking.data.satelliteIndex[constellation]];
            if (pos == null) {
                continue;
            }
            var lat = wrapLat(pos.Lat);
            var lng = wrapLng(pos.Lng);
            var latlng = L.latLng(lat, lng);
            sat.Marker.setLatLng(latlng);
        }
        tracking.data.satelliteIndex[constellation]++;
    }

    function nextIridiumPosition() {
        if (tracking.data.satelliteIndex >= 60) {
            // it's time to requery
            stopIridiumOverlay();
            startIridiumOverlay();
            return;
        }

        for (var i = 0; i < tracking.data.satelliteOrbits.length; i++) {
            var sat = tracking.data.satelliteOrbits[i];
            var pos = sat.Positions[tracking.data.satelliteIndex];
            var lat = wrapLat(pos.Lat);
            var lng = wrapLng(pos.Lng)
            var latlng = L.latLng(lat, lng);
            sat.Marker.setLatLng(latlng);
        }
        tracking.data.satelliteIndex++;
    }

    function checkWeatherForecast() {
	    if(!tracking.data.weather.isActive)
		    return;
	    if(tracking.data.weather.gettingData) {
		    if(tracking.data.weather.request != null) {
			    tracking.data.weather.request.abort();
		    }
		    tracking.data.weather.gettingData = false;
	    }
	    getWeatherForecast();
    }

    function getWeatherForecast() {
	    var bounds = tracking.map.getBounds();
    	var NE = bounds.getNorthEast();
    	var SW = bounds.getSouthWest();
    	var northLat = NE.lat;
    	var eastLng = NE.lng;
    	var southLat = SW.lat;
    	var westLng = SW.lng;
    	tracking.data.weather.gettingData = true;
        var url = "https://api.openweathermap.org/data/2.5/box/city?bbox="
                        + westLng + "," + northLat + "," //left top
                        + eastLng + "," + southLat + "," //right bottom
                        + tracking.map.getZoom()
                        + "&cluster=yes&format=json"
                        + "&APPID=" + "551b97ca557560dfc7d8c49a81b37d89";
  	    tracking.data.weather.request = $j.ajax({
  		    type: 'GET',
  		    url: wrapUrl(url),
  		    dataType: 'json',
  		    contentType: false,
  		    success: function(results) {
  			    console.log(results);
  			    if(results == null)
  				    return;
  			    if(results.list == null)
  				    return;
			    if (results.list.length > 0) {
				    // reset data
			  	    tracking.data.weather.geoJSON = {
				      	    type: "FeatureCollection",
				      	    features: []
                    };

                    // clear the prior set of data
                    tracking.data.weather.layer.clearLayers();

			  	    for (var i = 0; i < results.list.length; i++) {
			    		    tracking.data.weather.geoJSON.features.push(forecastJsonToGeoJson(results.list[i]));
			  	    }
                    tracking.data.weather.layer.addData(tracking.data.weather.geoJSON);
       				tracking.data.weather.gettingData = false;
      			}
  		    },
  		    error: function() {
  			    tracking.data.weather.gettingData = false;
  		    }
  	    });
    }

    function forecastJsonToGeoJson (weatherItem) {
          var feature = {
            type: "Feature",
            properties: {
              city: weatherItem.name,
              weather: weatherItem.weather[0].main,
              temperature: weatherItem.main.temp,
              min: weatherItem.main.temp_min,
              max: weatherItem.main.temp_max,
              humidity: weatherItem.main.humidity,
              pressure: weatherItem.main.pressure,
              windSpeed: weatherItem.wind.speed,
              windDegrees: weatherItem.wind.deg,
              windGust: weatherItem.wind.gust,
              icon: "https://openweathermap.org/img/w/"
                    + weatherItem.weather[0].icon  + ".png",
              coordinates: [weatherItem.coord.Lon, weatherItem.coord.Lat]
            },
            geometry: {
              type: "Point",
              coordinates: [weatherItem.coord.Lon, weatherItem.coord.Lat]
            }
          };
          //// Set the custom marker icon
          //tracking.data.weather.layer.setStyle(function(feature) {
          //  return {
          //    icon: {
          //      url: wrapUrl(feature.getProperty('icon')),
          //      anchor: new google.maps.Point(25, 25)
          //    }
          //  };
          //});
          // returns object
          return feature;
    }

    function replaceDialogButtons(dialog, buttons) {
        if (buttons === undefined || buttons.length === 0) {
            return;
        }
        var ui = dialog.parentNode;
        var pane = ui.querySelector('.ui-dialog-buttonset');
        var uiButtons = pane.querySelectorAll('button');
        _.each(uiButtons, function (uiButton, index, list) {
            var buttonType = buttons[index].buttonType === undefined ? 'primary' : buttons[index].buttonType;
            var buttonIcon = buttons[index].svgIcon === undefined ? null : svg('svg', svg('use', { xlink: { href: '/content/svg/tracking.svg?v=15#' + buttons[index].svgIcon } }));
            uiButton.className = "btn btn-" + buttonType;
            setChildren(uiButton, _.compact(buttonIcon, el('span', buttons[index].text)));
        });
    }

    function createDialogTitleFragment(name, subHeading) {
        var titleFragment = document.createDocumentFragment();
        titleFragment.appendChild(document.createTextNode(name));

        if (subHeading !== null && subHeading !== '') {
            var deviceName = document.createElement('div');
            deviceName.className = 'device-name';
            deviceName.textContent = subHeading;
            titleFragment.appendChild(deviceName);
        }
        return titleFragment;
    }

    function createDialogTitlebar(dialog) {
        var panel = $(dialog).parent();
        var title = $('div.ui-dialog-titlebar', panel).addClass('w-100');
        $('.ui-dialog-titlebar-close', title).remove();

        var bar = $('<div class="dialog-titlebar">');
        // svg icon for those that support it
        var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
        svg.appendChild(use);
        use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgPath('info-circle'));
        title[0].insertBefore(svg, title[0].firstChild);
        //title[0].appendChild(svg);

        title.appendTo(bar); // move title to new flex-row for button support
        var $quickActions = $('.dialog-quick-actions', dialog);
        if ($quickActions.length > 0) {
            //// quick actions select list should be pre-defined somewhere?
            //var action = $('<div class="dropdown" id="' + dialog.id + '-actions-dropdown"><button id="' + dialog.id + '-actions" class="panel-options panel-button" value="" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><svg><use xlink:href="/content/svg/tracking.svg?v=15#ellipsis-v"></use></svg></button></div>')
            //action.appendTo(bar);
            $quickActions.appendTo(bar);
        }
        if (dialog.getAttribute('data-has-collapse') !== null) {
            var collapse = document.createElement('button');
            collapse.setAttribute('data-for-dialog', dialog.id);
            collapse.className = 'btn item-collapse panel-options';
            var collapseSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
            var collapseUse = document.createElementNS('http://www.w3.org/2000/svg', 'use');
            collapseSvg.appendChild(collapseUse);
            collapseUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgPath('angle-up'));
            collapse.appendChild(collapseSvg);
            bar[0].appendChild(collapse);
        }

        var close = document.createElement('button');
        close.setAttribute('data-for-dialog', dialog.id);
        close.className = 'btn item-close';
        var closeSvg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
        var closeUse = document.createElementNS('http://www.w3.org/2000/svg', 'use');
        closeSvg.appendChild(closeUse);
        closeUse.setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgPath('times'));
        close.appendChild(closeSvg);
        bar[0].appendChild(close);

        //var close = $('<button type="button" data-for-dialog="' + dialog.id + '" class="btn item-close"><svg><use xlink:href="/content/svg/tracking.svg?v=15#times"></use></svg></button>');
        //close.appendTo(bar);
        bar.prependTo(panel);
    }

    function reverseGeocode(latlng, callback) {
        if (tracking.options.enabledFeatures.indexOf('UI_GEOCODING') === -1) {
            return;
        }
        var data = { latLng: { Lat: latlng.lat, Lng: latlng.lng } };
        return $j.ajax({
            type: 'POST',
            url: wrapUrl('/services/GPSService.asmx/ReverseGeocode'),
            data: JSON.stringify(data),
            contentType: 'application/json; charset=utf-8',
            dataType: 'json',
            success: function (msg) {
                var result = msg.d;
                if (result) {
                    console.log(result);
                    if (result.Success == true) {
                        callback(true, result.Address);
                    } else {
                        callback(false);
                    }
                }
            },
            error: function (xhr, status, error) {
                callback(false);
            }
        });
    }

    function bindParameterSelectionEvents(selector) {
        $j(selector).on('click', 'a.select-all', function (e) {
            e.preventDefault();
            var table = $j(this).closest('table');
            $j('input:checkbox', table).prop('checked', true).parents('td').find('input,select,label').not(':checkbox').prop('disabled', false).removeClass('disabled');
            $j('input:checkbox.next-cell', table).parents('td').next('td').find('input,select,label').not(':checkbox').prop('disabled', false).removeClass('disabled');
            $j('input:checkbox.next-rows', table).parents('td').parent().nextUntil('tr.collapse').find('input,select,label').not(':checkbox').prop('disabled', false).removeClass('disabled');
        });
        $j(selector).on('click', 'a.select-none', function (e) {
            e.preventDefault();
            var table = $j(this).closest('table');
            $j('input:checkbox', table).prop('checked', false).parents('td').find('input,select,label').not(':checkbox').prop('disabled', true).addClass('disabled');
            $j('input:checkbox.next-cell', table).parents('td').next('td').find('input,select,label').not(':checkbox').prop('disabled', true).addClass('disabled');
            $j('input:checkbox.next-rows', table).parents('td').parent().nextUntil('tr.collapse').find('input,select,label').not(':checkbox').prop('disabled', true).addClass('disabled');
        });
        $j(selector).on('click', 'label', function (e) {
            var cell = $j(this).parent().next('td');
            cell.find('input:checkbox').prop('checked', true);
            cell.find('input,select,label').not(':checkbox').prop('disabled', false).removeClass('disabled');
            if (cell.hasClass('next-cell')) {
                cell = cell.next('td').find('input,select,label').not(':checkbox').prop('disabled', false).removeClass('disabled');
            }
        });
        $j(selector).on('change', 'input:checkbox', function (e) {
            var isDisabled = !$j(this).prop('checked');
            if (isDisabled) {
                $j(this).parents('td').find('input,select,label').not(':checkbox').prop('disabled', isDisabled).addClass('disabled');
                if ($j(this).hasClass('next-cell')) {
                    $j(this).parents('td').next('td').find('input,select,label').not(':checkbox').prop('disabled', isDisabled).addClass('disabled');
                }
            } else {
                $j(this).parents('td').find('input,select,label').not(':checkbox').prop('disabled', isDisabled).removeClass('disabled');
                if ($j(this).hasClass('next-cell')) {
                    $j(this).parents('td').next('td').find('input,select,label').not(':checkbox').prop('disabled', isDisabled).removeClass('disabled');
                }
            }

        });
    }

    function getCurrentUserTime() {
        return new Date().getTime() - tracking.user.tickOffset;
    }

    function getCurrentUserTimeString(includeOffset) {
        var time = moment(new Date().getTime() - tracking.user.tickOffset).format('HH:mm');
        if (includeOffset) {
            time += ' ' + tracking.user.utcDateOffset;
        }
        return time;
    }

    function updateUserTime() {
        tracking.data.domNodes.mapTools.currentTimeCompact.textContent = getCurrentUserTimeString(false);
        tracking.data.domNodes.mapTools.currentTime.textContent = getCurrentUserTimeString(true);
    }

    function uploadFile(id, url, data, callback) {
        var file = document.getElementById(id).files[0];
        if (file === undefined) {
            return false;
        }
        var formData = new FormData();
        formData.append(id, file);
        _.each(data, function (value, key, obj) {
            formData.append(key, value);
        });

        $.ajax({
            url: wrapUrl(url),
            type: 'POST',
            data: formData,
            cache: false,
            contentType: false,
            processData: false,
            success: callback
        });
        return true;
    }

    tracking.uploadFile = uploadFile;

    // open a dialog for an action - this should be called as early as possible so that previous dialog cleanup does not interfere with no dialog intialization
    function openDialogPanel(domNode, title, item, isLargeDialog, closeCallback, type, action, nextItemCallback) {
        // don't do anything if the dialog is already open for this exact item

        var panel = tracking.data.domNodes.panels.secondary;
        var currentType = panel.getAttribute('data-group-for'); // what was populated in the secondary panel before this dialog: assets, groups, places, fences, dialog

        if (type === undefined) {
            type = 'asset';
        }
        var id = null;
        if (item !== null) {
            id = item.Id;
        }
        openSettingsPanel(type + 's', item, (domNode !== null)); // hack/lazy alert
        if (isLargeDialog) {
            panel.classList.add('is-full-width');
        }

        panel.closeCallback = closeCallback;
        panel.nextItemCallback = nextItemCallback;

        var showMetaPanel = false;
        var showNotificationItems = false;
        var notificationTabs = document.getElementById('panel-secondary-nav-tabs');
        var isNotificationItem = true;

        var panelItemType = panel.getAttribute('data-item-type');
        var notificationTabItemTypes = ['groups', 'assets', 'trips', 'journeys', 'shared-views']; // todo: only asset groups
        if (panelItemType !== undefined && _.indexOf(notificationTabItemTypes, panelItemType) !== -1) {
            showNotificationItems = true;
        }

        var metaPanel = document.getElementById('panel-secondary-meta');
        // item selection dropdown (action determines which items are disabled)
        console.log(domNode, type, item, action);
        if (action !== undefined && action !== null) {
            // showing of selector handled above by openSettingsPanel
            populateItemSelector(action, item, type + 's');

            populateMetaInformation(type, item);
            showMetaPanel = true;

            // select notification tab based on action here?
            var actionsPositions = ['asset-positions', 'group-positions', 'trip-positions', 'journey-positions', 'shared-view-positions'];
            var actionsEvents = ['asset-events', 'group-events', 'trip-events', 'journey-events', 'shared-view-events'];
            var actionsAlerts = ['asset-alerts', 'group-alerts', 'trip-alerts', 'journey-alerts', 'shared-view-alerts'];
            var actionsStatus = ['asset-status', 'group-status', 'trip-status', 'journey-status', 'shared-view-status'];
            var actionsMessages = ['asset-messages', 'group-messages', 'trip-messages', 'journey-messages'];
            var actionsChat = ['asset-chat', 'group-chat', 'trip-chat', 'journey-chat', 'shared-view-chat'];
            var actionsActivity = ['asset-activity', 'group-activity', 'trip-activity', 'journey-activity', 'shared-view-activity'];
            var optionsActions = ['shared-view-options', 'asset-options', 'group-options', 'trip-options', 'journey-options', 'fence-options', 'place-options'];
            _.each(notificationTabs.querySelectorAll('.nav-item'), function (nav) {
                nav.classList.remove('active');
            });

            var leftMeta = metaPanel.querySelectorAll('.meta-left,.meta-options');
            _.each(leftMeta, function (meta) {
                meta.classList.remove('is-visible');
            });

            //var metaTypes = ['positions', 'events', 'alerts', 'status', 'messages', 'chat', 'activity'];
            var metaType = null;
            if (_.indexOf(actionsPositions, action) !== -1) {
                document.getElementById('nav-positions-tab').classList.add('active');
                metaType = 'positions';
            } else if (_.indexOf(actionsEvents, action) !== -1) {
                document.getElementById('nav-events-tab').classList.add('active');
                metaType = 'events';
            } else if (_.indexOf(actionsAlerts, action) !== -1) {
                document.getElementById('nav-alerts-tab').classList.add('active');
                metaType = 'alerts';
            } else if (_.indexOf(actionsStatus, action) !== -1) {
                document.getElementById('nav-status-tab').classList.add('active');
                metaType = 'status';
            } else if (_.indexOf(actionsMessages, action) !== -1) {
                document.getElementById('nav-messages-tab').classList.add('active');
                metaType = 'messages';
            } else if (_.indexOf(actionsChat, action) !== -1) {
                document.getElementById('nav-chat-tab').classList.add('active');
                metaType = 'chat';
            } else if (_.indexOf(actionsActivity, action) !== -1) {
                document.getElementById('nav-activity-tab').classList.add('active');
                metaType = 'activity';
            } else if (_.indexOf(optionsActions, action) !== -1) {
                document.getElementById('nav-options-tab').classList.add('active');
                isNotificationItem = false;
                metaType = 'options';
            } else {
                isNotificationItem = false;
                showMetaPanel = false;
            }

            if (metaType !== null) {
                var optionsMeta = metaPanel.querySelectorAll('.meta-' + metaType);
                _.each(optionsMeta, function (meta) {
                    meta.classList.add('is-visible');
                });
            }
        }

        if (showMetaPanel) {
            metaPanel.classList.add('is-visible');
        } else {
            metaPanel.classList.remove('is-visible');
        }

        if (showNotificationItems && id !== null && (isNotificationItem || metaType === 'options')) {
            notificationTabs.classList.add('is-visible');

            var counts = null;
            if (type === 'asset') {
                counts = getNotificationCounts(getAssetDataGroupForCurrentViewMode(), id);
            } else if (type === 'trip') {
                counts = getNotificationCounts(tracking.dataGroups.JOURNEY_HISTORY, id);
            } else if (type === 'group') {
                counts = getNotificationCountsForGroup(getAssetDataGroupForCurrentViewMode(), id);
            } else if (type === 'journey') {
                counts = getNotificationCountsForGroup(tracking.dataGroups.JOURNEY_HISTORY, id);
            } else if (type === 'shared-view') {
                counts = getNotificationCounts(tracking.dataGroups.SHARED_VIEW_HISTORY, id);
                console.log(counts);
            }
            if (counts !== null) {
                updateNotificationsInSecondaryPanel(type + 's', id, counts);
            }
        } else {
            notificationTabs.classList.remove('is-visible');
        }

        var panelTitle = document.getElementById('panel-secondary-title');
        panelTitle.textContent = title;

        if (domNode !== null) {
            var dialogDom = document.getElementById('dialog-functions');
            var existingDialog = dialogDom.querySelector('.dialog');
            if (existingDialog !== domNode) {
                if (existingDialog !== null) {
                    document.getElementById('detached-dialogs').appendChild(existingDialog);
                }
                dialogDom.appendChild(domNode);
            }

            // breadcrumbs
            var breadcrumbs = [];
            if (id !== null) {
                // when inside of a dialog, add breadcrumbs leading back to the item in general
                switch (type + 's') {
                    case 'assets':
                        breadcrumbs.push({ label: tracking.strings.ASSET, callback: itemSettingsClick, attributes: { 'data-asset-id': id } });
                        break;
                    case 'groups':
                        // todo: properly supply the group-for parameter or change dialogGroup to groups-assets, groups-places, etc
                        breadcrumbs.push({ label: tracking.strings.ASSET_GROUP, callback: itemSettingsClick, attributes: { 'data-group-id': id, 'data-group-for': 'assets' } });
                        break;
                    case 'fences':
                        breadcrumbs.push({ label: tracking.strings.GEOFENCE, callback: itemSettingsClick, attributes: { 'data-fence-id': id } });
                        break;
                    case 'places':
                        breadcrumbs.push({ label: tracking.strings.PLACE, callback: itemSettingsClick, attributes: { 'data-place-id': id } });
                        break;
                    case 'journeys':
                        breadcrumbs.push({ label: tracking.strings.JOURNEY, callback: itemSettingsClick, attributes: { 'data-journey-id': id } });
                        break;
                    case 'trips':
                        breadcrumbs.push({ label: tracking.strings.TRIP, callback: itemSettingsClick, attributes: { 'data-trip-id': id } });
                        break;
                    case 'shared-views':
                        breadcrumbs.push({ label: tracking.strings.SHARED_VIEW, callback: itemSettingsClick, attributes: { 'data-shared-view-id': id } });
                        break;
                }
            }
            breadcrumbs.push({ label: title, callback: function () { } });
            var breadcrumbDom = document.getElementById('dialog-breadcrumbs');
            var breadcrumbTitle = breadcrumbDom.querySelector('.panel-title');
            breadcrumbTitle.innerHTML = '';
            breadcrumbTitle.appendChild(createSecondaryBreadcrumb(breadcrumbs));

            // enable back button to go back to item settings
            var backButton = document.getElementById('panel-dialog-back');
            backButton.removeAttribute('data-asset-id');
            backButton.removeAttribute('data-group-id');
            backButton.removeAttribute('data-group-for');
            backButton.removeAttribute('data-fence-id');
            backButton.removeAttribute('data-place-id');
            backButton.removeAttribute('data-trip-id');
            backButton.removeAttribute('data-shared-view-id');
            if (id !== null && !isNotificationItem) {
                // show breadcrumbs
                breadcrumbDom.classList.add('is-visible');
                backButton.setAttribute('data-' + type + '-id', id);
                if (type === 'group') {
                    // todo: properly supply this parameter
                    backButton.setAttribute('data-group-for', 'assets');
                }
            } else {
                // hide breadcrumbs
                breadcrumbDom.classList.remove('is-visible');
            }

            //// panel filters shown/hidden here - should move to a more declarative way of doing this
            //var notificationOptions = document.getElementById('panel-options-notifications');
            //if (notificationOptions !== null) {
            //    var notificationDialogs = [
            //        tracking.data.domNodes.dialogs.assetMessages,
            //        tracking.data.domNodes.dialogs.assetStatus,
            //        tracking.data.domNodes.dialogs.assetEvents,
            //        tracking.data.domNodes.dialogs.assetAlerts,
            //        tracking.data.domNodes.dialogs.assetPositions
            //    ];
            //    if (notificationDialogs.indexOf(domNode) !== -1) {
            //        notificationOptions.classList.add('is-visible');
            //    } else {
            //        notificationOptions.classList.remove('is-visible');
            //    }
            //}
            resetParametersForms(domNode);

            // clear dialog statuses
            var statuses = dialogDom.querySelectorAll('.dialog-status');
            _.each(statuses, function (status) {
                clearStatusMessage(status);
            });
        }

        tracking.data.domNodes.simpleBars.secondary.recalculate(); // todo: this should occur after all mutations
    }

    function createSecondaryBreadcrumb(breadcrumbs) {
        // asset / action / grouping
        // add item / asset
        var nav = document.createElement('nav');
        nav.setAttribute('aria-label', 'breadcrumb');
        var navList = document.createElement('ol');
        navList.className = 'breadcrumb p-0 m-0';
        _.each(breadcrumbs, function (breadcrumb, index, list) {
            var navItem = document.createElement('li');
            var className = 'breadcrumb-item';
            var isCurrent = false;
            if (index === (list.length - 1)) {
                className += ' active';
                navItem.setAttribute('aria-current', 'page');
                isCurrent = true;
            }
            navItem.className = className;
            if (!isCurrent) {
                var navItemLink = document.createElement('a');
                navItemLink.setAttribute('href', '#');
                _.each(breadcrumb.attributes, function (val, key) {
                    navItemLink.setAttribute(key, val);
                });
                navItemLink.textContent = breadcrumb.label;
                navItemLink.callback = breadcrumb.callback;
                navItem.appendChild(navItemLink);
            } else {
                navItem.textContent = breadcrumb.label;
            }
            navList.appendChild(navItem);
        });
        nav.appendChild(navList);
        return nav;
    }

    function clearSettingsNode(node) {
        if (node.classList.contains('settings-active')) {
            node.classList.remove('settings-active');
            var settingsIcon = node.querySelector('.item-settings');
            if (settingsIcon !== null) {
                var use = settingsIcon.querySelector('use');
                use.setAttributeNS('http://www.w3.org/1999/xlink', 'href',
                    use.getAttributeNS('http://www.w3.org/1999/xlink', 'href').replace('-solid', ''));
            }
        }
    }

    function changeActivePrimaryPanelToMatchSecondary(type) {
        switch (type) {
            case 'fences':
                if (tracking.state.activePanel !== tracking.panels.GEOFENCES) {
                    changeActivePanel(tracking.panels.GEOFENCES);
                }
                break;
            case 'places':
                if (tracking.state.activePanel !== tracking.panels.PLACES) {
                    changeActivePanel(tracking.panels.PLACES);
                }
                break;
            case 'assets':
                if (tracking.state.activePanel !== tracking.panels.ASSETS) {
                    changeActivePanel(tracking.panels.ASSETS);
                }
                //if (tracking.state.activeMapMode === tracking.mapModes.LIVE) {
                //    if (tracking.state.activePanel !== tracking.panels.LIVE) {
                //        changeActivePanel(tracking.panels.LIVE);
                //    }
                //} else if (tracking.state.activeMapMode === tracking.mapModes.HISTORY) {
                //    if (tracking.state.activePanel !== tracking.panels.HISTORY) {
                //        changeActivePanel(tracking.panels.HISTORY);
                //    }
                //}
                break;
            case 'journeys': // fallthrough
            case 'trips':
                if (tracking.state.activePanel !== tracking.panels.JOURNEYS) {
                    changeActivePanel(tracking.panels.JOURNEYS);
                }
                break;
            case 'shared-views':
                if (tracking.state.activePanel !== tracking.panels.SHARED_VIEWS) {
                    changeActivePanel(tracking.panels.SHARED_VIEWS);
                }
                break;
            default: // dialog, todo: groups
                break;
        }
    }

    function getSvgIconForItemType(type, item) {
        if (type === 'shared-views') {
            return svgPath('share-alt-square');
        } else if (type === 'groups') {
            return svgPath('folder-open-solid');
        } else if (type === 'journeys') {
            return svgPath('folder-open-solid');
        } else if (type === 'drivers') {
            return svgPath('steering-wheel');
        } else if (type == 'fences') {
            return svgPath('square-full-duo');
        }
        return null;
    }

    function getGenericIconUrlForItemType(type, item) {
        var icon = null;
        switch (type) {
            case 'fences':
                icon = 'url(' + createMarkerPath('Fence', item.Color.substring(1), null, null, null, false, null, false, false) + ')';
                break;
            case 'places':
                icon = 'url(' + createMarkerPath('Generic', item.Color, null, null, null, false, null, false, false) + ')';
                break;
            case 'assets':
                icon = 'url(' + createMarkerPath(item.Class, item.Color, null, null, item.Id, false, null, false, false) + ')';
                break;
            //case 'journeys': // fallthrough
            case 'trips':
                if (type === 'trips') {
                    var journey = findJourneyById(item.JourneyId);
                    var asset = findAssetById(journey.AssetId);
                    icon = 'url(' + createMarkerPath(asset.Class, asset.Color, null, null, asset.Id, false, null, false, false) + ')';
                } else {
                    icon = 'url(' + createMarkerPath('Fence', item.Color.substring(1), null, null, null, false, null, false, false) + ')';
                }
                break;
            //case 'groups': // TODO... SVG support here to match listing
            //    icon = 'url(' + createMarkerPath('Fence', item.Color.substring(1), null, null, null, false, null, false, false) + ')';
            //    break;
            //case 'shared-views': // TODO... SVG support here
            //    break;
            default: // dialog
                break;
        }
        return icon;
    }


    function openSettingsPanel(type, item, isDialog) {
        var icon = null;
        var svgIcon = null;
        var id = null;
        var title = null;

        changeActivePrimaryPanelToMatchSecondary(type);

        if (item !== null) {
            id = item.Id;
            title = item.Name;
            icon = getGenericIconUrlForItemType(type, item);
            svgIcon = getSvgIconForItemType(type, item);
        }

        var panelType = isDialog ? 'dialog' : type;
        var panel = tracking.data.domNodes.panels.secondary;
        if (id !== null) {
            panel.setAttribute('data-item-type', type);
            panel.setAttribute('data-item-id', id);
        } else {
            panel.removeAttribute('data-item-type');
            panel.removeAttribute('data-item-id');
        }

        panel.setAttribute('data-group-for', panelType);
        panel.classList.remove('is-full-width');

        var itemOptions = document.getElementById('panel-secondary-item-options');
        //var itemNav = document.getElementById('panel-secondary-item-nav');
        var itemPrev = document.getElementById('panel-secondary-item-prev');
        var itemNext = document.getElementById('panel-secondary-item-next');
        var itemTitle = document.getElementById('panel-secondary-title');
        if (id === null) {
            // panel unrelated to a specific item (Get Route, Add Asset, etc)
            // so only the title should be shown
            // item selector should be hidden
            itemOptions.classList.remove('is-visible');
            itemPrev.classList.remove('is-visible');
            itemNext.classList.remove('is-visible');
            itemTitle.classList.add('is-visible');
        } else {
            // item selector should be shown
            itemOptions.classList.add('is-visible');
            itemPrev.classList.add('is-visible');
            itemNext.classList.add('is-visible');
            itemTitle.classList.remove('is-visible');
        }

        // clear all previous icons highlighted from having the prior panel open
        // todo: store previously selected item to deselect it instead of having to iterate all
        _.each(tracking.data.domNodes.assets, function (assetNodes) { _.each(assetNodes, clearSettingsNode); });
        _.each(tracking.data.domNodes.groups, clearSettingsNode);
        _.each(tracking.data.domNodes.fences, clearSettingsNode);
        _.each(tracking.data.domNodes.places, clearSettingsNode);
        //_.each(tracking.data.domNodes.journeys, clearSettingsNode); // handled by groups
        _.each(tracking.data.domNodes.trips, clearSettingsNode);
        _.each(tracking.data.domNodes.sharedViews, clearSettingsNode);

        // highlight item this panel is related to
        if (id !== null) {
            highlightActiveItemInPrimaryPanel(type, id);
        }

        // set icon
        var panelTitle = panel.querySelector('.panel-title');
        var panelSvgIcon = panel.querySelector('.panel-svg-icon');
        if (svgIcon !== null) {
            panelSvgIcon.classList.add('is-visible');
            panelSvgIcon.setAttribute('style', 'color: ' + item.Color + '; --color-primary: ' + item.Color + '; --color-secondary: ' + item.Color);
            panelSvgIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgIcon);
            panelTitle.style.backgroundImage = null;
        } else {
            panelSvgIcon.classList.remove('is-visible');
            if (icon !== null) {
                panelTitle.classList.add('has-icon');
            } else {
                panelTitle.classList.remove('has-icon');
            }
            panelTitle.style.backgroundImage = icon;
        }        

        // hide any previous panel content
        var functionPanels = tracking.data.domNodes.panels.secondary.querySelectorAll('.item-group-functions');
        _.each(functionPanels, function (panel, index, list) {
            panel.classList.remove('is-visible');
        });

        // show panel content relevant to chosen item
        var functionPanel = document.getElementById(panelType + '-functions');
        functionPanel.classList.add('is-visible');

        openSecondaryPanel(true);
    }

    function highlightActiveItemInPrimaryPanel(itemType, id) {
        var settingsIcon = null;
        switch (itemType) {
            case 'assets':
                var assetNodes = tracking.data.domNodes.assets[id];
                _.each(assetNodes, function (assetNode) {
                    assetNode.classList.add('settings-active');
                    settingsIcon = assetNode.querySelector('.item-settings');                    
                });
                break;
            case 'groups':
                var groupNode = tracking.data.domNodes.groups[id];
                groupNode.classList.add('settings-active');
                settingsIcon = groupNode.querySelector('.item-settings');
                break;
            case 'journeys':
                var groupNode = tracking.data.domNodes.groups['journey-' + id];
                groupNode.classList.add('settings-active');
                settingsIcon = groupNode.querySelector('.item-settings');
                break;
            case 'fences':
                var fenceNode = tracking.data.domNodes.fences[id];
                fenceNode.classList.add('settings-active');
                settingsIcon = fenceNode.querySelector('.item-settings');
                break;
            case 'places':
                var placeNode = tracking.data.domNodes.places[id];
                placeNode.classList.add('settings-active');
                settingsIcon = placeNode.querySelector('.item-settings');
                break;
            case 'trips':
                var tripNode = tracking.data.domNodes.trips[id];
                tripNode.classList.add('settings-active');
                settingsIcon = tripNode.querySelector('.item-settings');
                break;
            case 'shared-views':
                var sharedViewNode = tracking.data.domNodes.sharedViews[id];
                sharedViewNode.classList.add('settings-active');
                settingsIcon = sharedViewNode.querySelector('.item-settings');
                break;
            default:
                break;
        }
        if (settingsIcon !== null) {
            if (itemType === 'groups' || itemType === 'journeys') {
                settingsIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#group-settings-solid');
            } else {
                settingsIcon.querySelector('use').setAttributeNS('http://www.w3.org/1999/xlink', 'href', '/content/svg/tracking.svg?v=15#settings-solid');
            }
        }
    }

    function openAssetPositionsPanel(asset, positions) {
        // for listing an asset's positions in either live or history mode
        // in live mode, we're collecting positions as they arrive
    }

    function openAssetGroupSettingsPanel(group) {
        openDialogPanel(null, group.Name, group, false, null, 'group', 'group-options', openAssetGroupSettingsPanel);

        var groupMenuDom = {
            message: document.getElementById('group-function-message'),
            historyReplay: document.getElementById('group-function-history-replay'),
            followGroup: document.getElementById('group-function-follow-group'),
            editGroup: document.getElementById('group-function-edit-group'),
            deleteGroup: document.getElementById('group-function-delete-group'),
            copyGroup: document.getElementById('group-function-copy-group')
        };

        var followText = groupMenuDom.followGroup.querySelector('.item-action-text');
        if (isFollowingGroup(group)) {
            followText.textContent = tracking.strings.UNFOLLOW_LIVE;
        } else {
            followText.textContent = tracking.strings.FOLLOW_LIVE;
        }

        _.each(groupMenuDom, function (item) {
            item.classList.remove('disabled');
            item.querySelector('a').classList.remove('disabled');
        });

        if ((tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging)
            || !isMessagingEnabledForAssetGroup(group)) {
            disableSettingsMenuItem(groupMenuDom.message);
        }

        // enable/disable menu items based on user permissions and group status
        if (tracking.data.assets === null
            || tracking.data.assets.length === 0
            || findAssetIdsUnderGroup(group) === 0) {
            disableSettingsMenuItem(groupMenuDom.historyReplay);
            disableSettingsMenuItem(groupMenuDom.followGroup);
        }

        if (tracking.user.isAnonymous || tracking.user.canEditAssetGroups === undefined || !tracking.user.canEditAssetGroups) {
            disableSettingsMenuItem(groupMenuDom.editGroup);
            disableSettingsMenuItem(groupMenuDom.deleteGroup);
            disableSettingsMenuItem(groupMenuDom.copyGroup);
        }

        if (group.IsDefault) {
            disableSettingsMenuItem(groupMenuDom.editGroup);
            disableSettingsMenuItem(groupMenuDom.deleteGroup);
        }

        if (tracking.options.enabledFeatures.indexOf('UI_DUPLICATE_GROUP') === -1) {
            disableSettingsMenuItem(groupMenuDom.copyGroup);
        }

        // remove some options altogether based on menu exclusion options
        _.each(tracking.options.assetMenuExclude, function (excluded) {
            var domName = dashToCamelCase(excluded);
            if (groupMenuDom[domName] !== undefined) {
                if (groupMenuDom[domName].parentNode !== null) {
                    groupMenuDom[domName].parentNode.removeChild(groupMenuDom[domName]);
                }
            }
        });
    }

    function svgPath(icon) {
        return '/content/svg/tracking.svg?v=15#' + icon;
    }

    function checkForShareViewChange(sharedView, wasEditing) {
        return function() {
            console.log('checkForShareViewChange - dialog closed', wasEditing, sharedView);
            var currentSharedViewId = tracking.data.domNodes.panels.secondary.getAttribute('data-item-id');
            if (wasEditing === true && tracking.data.sharedView.temp !== null && tracking.data.sharedView.current !== null) {
                // closed out of editing
                console.log('- canceled shared view temp edit');
                compareSharedViews(tracking.data.sharedView.current, tracking.data.sharedView.temp);
                tracking.data.sharedView.temp = null;
                // may have also closed secondary panel
            } else if (wasEditing === true && tracking.data.sharedView.temp !== null) {
                console.log('- canceled out of new shared view');
                deselectSharedView();
            } else if (currentSharedViewId !== null) {
                var newSharedViewId = parseInt(currentSharedViewId);
                if ((sharedView === null && currentSharedViewId === null) || (sharedView !== null && newSharedViewId !== sharedView.Id)) {
                    console.log('- change in shared view', newSharedViewId, sharedView.Id);
                    clearSharedViewData(true);
                    tracking.data.sharedView.current = null;
                    $(tracking.data.domNodes.infoDialogs.sharedViewInformation).dialog('close');
                    if (newSharedViewId === null) {
                        // resetting map type to prior
                        tracking.data.isSatelliteLabelOverlayEnabled = tracking.data.sharedView.priorIsSatelliteLabelOverlayEnabled;
                        changeMapType(tracking.data.sharedView.priorMapType);
                    }
                } else {
                    console.log('- no change in shared view');
                }
            }

            if (currentSharedViewId === null) {
                // user has closed secondary panel.. re-show shared view information
                console.log('- reshow shared view info');
                showSharedViewInformationOnMap(tracking.data.sharedView.current, true);
            }
        };
    }

    function openSharedViewSettingsPanel(sharedView) {
        openDialogPanel(null, sharedView.Name, sharedView, false, checkForShareViewChange(sharedView), 'shared-view', 'shared-view-options', openSharedViewSettingsPanel);

        tracking.data.domNodes.panels.secondary.setAttribute('data-chat-enabled', sharedView.IsMessagingEnabled);

        if (tracking.data.sharedView.current !== sharedView) {
            querySharedViewData(sharedView, null, false);
        }
        tracking.data.sharedView.current = sharedView;
        $(tracking.data.domNodes.infoDialogs.sharedViewInformation).dialog('close');

        var sharedViewMenuDom = {
            sharedViewVisitorStatistics: document.getElementById('shared-view-function-statistics'),
            disableSharedView: document.getElementById('shared-view-function-disable'),
            sharedViewAccessControls: document.getElementById('shared-view-function-access-controls'),
            sharedViewDataVisualization: document.getElementById('shared-view-function-data-visualization'),
            sharedViewDetails: document.getElementById('shared-view-function-details'),
            sharedViewMapSettings: document.getElementById('shared-view-function-map-settings'),
            sharedViewPermissions: document.getElementById('shared-view-function-permissions'),
            sharedViewPreferences: document.getElementById('shared-view-function-preferences'),
            deleteSharedView: document.getElementById('shared-view-function-delete')
        };

        // enable/disable menu items based on user permissions and shared view status

        var statusButtonIcon = sharedViewMenuDom.disableSharedView.querySelector('use');
        var statusButtonText = sharedViewMenuDom.disableSharedView.querySelector('span');
        if (sharedView.IsEnabled) {
            statusButtonIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgPath('ban'));
            statusButtonText.textContent = tracking.strings.DISABLE;
        } else {
            statusButtonIcon.setAttributeNS('http://www.w3.org/1999/xlink', 'href', svgPath('check'));
            statusButtonText.textContent = tracking.strings.ENABLE;
        }

        // populate share links
        if (tracking.user.isAnonymous || tracking.user.canEditSharedViews === undefined || !tracking.user.canEditSharedViews || tracking.options.enabledFeatures.indexOf('VIEWS_SHARED') === -1) {
            _.each(sharedViewMenuDom, function (item) {
                item.classList.add('disabled');
            });
        } else {
            _.each(sharedViewMenuDom, function (item) {
                item.classList.remove('disabled');
            });
        }

        // remove some options altogether based on menu exclusion options
        _.each(tracking.options.assetMenuExclude, function (excluded) {
            var domName = dashToCamelCase(excluded);
            if (sharedViewMenuDom[domName] !== undefined) {
                if (sharedViewMenuDom[domName].parentNode !== null) {
                    sharedViewMenuDom[domName].parentNode.removeChild(sharedViewMenuDom[domName]);
                }
            }
        });
    }

    function openPlaceSettingsPanel(place) {
        openDialogPanel(null, place.Name, place, false, null, 'place', 'place-options', openPlaceSettingsPanel);

        var placeMenuDom = {
            routeAsset: document.getElementById('place-function-route-asset'),
            editPlace: document.getElementById('place-function-edit-place'),
            deletePlace: document.getElementById('place-function-delete-place')
        };

        _.each(placeMenuDom, function (item) {
            item.classList.remove('disabled');
        });

        // enable/disable menu items based on user permissions and place status
        if (tracking.data.places === null
            || tracking.data.places.length === 0
            || tracking.data.assets === null
            || tracking.data.assets.length === 0
            || tracking.options.enabledFeatures.indexOf('ASSET_ROUTING') === -1) {
            disableSettingsMenuItem(placeMenuDom.routeAsset);
        }

        if (tracking.user.isAnonymous || tracking.user.canEditPlaces === undefined || !tracking.user.canEditPlaces) {
            disableSettingsMenuItem(placeMenuDom.editPlace);
            disableSettingsMenuItem(placeMenuDom.deletePlace);
        }

        // remove some options altogether based on menu exclusion options
        _.each(tracking.options.assetMenuExclude, function (excluded) {
            var domName = dashToCamelCase(excluded);
            if (placeMenuDom[domName] !== undefined) {
                if (placeMenuDom[domName].parentNode !== null) {
                    placeMenuDom[domName].parentNode.removeChild(placeMenuDom[domName]);
                }
            }
        });
    }

    function openTripSettingsPanel(trip) {
        openDialogPanel(null, trip.Name, trip, false, null, 'trip', 'trip-options', openTripSettingsPanel);

        var itemMenuDom = {
            editTrip: document.getElementById('trip-function-edit-trip'),
            deleteTrip: document.getElementById('trip-function-delete-trip')
        };

        _.each(itemMenuDom, function (item) {
            item.classList.remove('disabled');
        });

        if (tracking.user.isAnonymous || tracking.user.canEditJourneys === undefined || !tracking.user.canEditJourneys) {
            disableSettingsMenuItem(itemMenuDom.editTrip);
            disableSettingsMenuItem(itemMenuDom.deleteTrip);
        }

        // remove some options altogether based on menu exclusion options
        _.each(tracking.options.assetMenuExclude, function (excluded) {
            var domName = dashToCamelCase(excluded);
            if (itemMenuDom[domName] !== undefined) {
                if (itemMenuDom[domName].parentNode !== null) {
                    itemMenuDom[domName].parentNode.removeChild(itemMenuDom[domName]);
                }
            }
        });
    }

    function openJourneySettingsPanel(journey) {
        openDialogPanel(null, journey.Name, journey, false, null, 'journey', 'journey-options', openJourneySettingsPanel);

        var itemMenuDom = {
            editJourney: document.getElementById('journey-function-edit-journey'),
            deleteJourney: document.getElementById('journey-function-delete-journey')
        };

        _.each(itemMenuDom, function (item) {
            item.classList.remove('disabled');
        });

        if (tracking.user.isAnonymous || tracking.user.canEditJourneys === undefined || !tracking.user.canEditJourneys) {
            disableSettingsMenuItem(itemMenuDom.editJourney);
            disableSettingsMenuItem(itemMenuDom.deleteJourney);
        }

        // remove some options altogether based on menu exclusion options
        _.each(tracking.options.assetMenuExclude, function (excluded) {
            var domName = dashToCamelCase(excluded);
            if (itemMenuDom[domName] !== undefined) {
                if (itemMenuDom[domName].parentNode !== null) {
                    itemMenuDom[domName].parentNode.removeChild(itemMenuDom[domName]);
                }
            }
        });
    }

    function disableSettingsMenuItem(item) {
        if (item !== undefined && item !== null) {
            item.classList.add('disabled');
            item.querySelector('a').classList.add('disabled');
        }
    }

    function openFenceSettingsPanel(fence) {
        openDialogPanel(null, fence.Name, fence, false, null, 'fence', 'fence-options', openFenceSettingsPanel);

        var fenceMenuDom = {
            message: document.getElementById('fence-function-message'),
            historyReplay: document.getElementById('fence-function-history-replay'),
            groupAssets: document.getElementById('fence-function-group-assets'),
            addAlert: document.getElementById('fence-function-add-alert'),
            locationReport: document.getElementById('fence-function-location-report'),
            editFence: document.getElementById('fence-function-edit-fence'),
            deleteFence: document.getElementById('fence-function-delete-fence')
        };

        _.each(fenceMenuDom, function (item) {
            item.classList.remove('disabled');
            item.querySelector('a').classList.remove('disabled');
        });

        var hasNoAssets = findAssetIdsInGeofence(fence).length == 0;

        // enable/disable menu items based on user permissions and fence status
        if (tracking.user.isAnonymous || hasNoAssets) {
            disableSettingsMenuItem(fenceMenuDom.message);
            disableSettingsMenuItem(fenceMenuDom.groupAssets);
            disableSettingsMenuItem(fenceMenuDom.locationReport);
        }

        if (hasNoAssets) {
            disableSettingsMenuItem(fenceMenuDom.historyReplay);
        }

        if (tracking.user.isAnonymous) {
            disableSettingsMenuItem(fenceMenuDom.addAlert);
        }

        if (tracking.user.isAnonymous || tracking.user.canEditGeofences === undefined || !tracking.user.canEditGeofences) {
            disableSettingsMenuItem(fenceMenuDom.editFence);
            disableSettingsMenuItem(fenceMenuDom.deleteFence);
        }

        // remove some options altogether based on menu exclusion options
        _.each(tracking.options.assetMenuExclude, function (excluded) {
            var domName = dashToCamelCase(excluded);
            if (fenceMenuDom[domName] !== undefined) {
                if (fenceMenuDom[domName].parentNode !== null) {
                    fenceMenuDom[domName].parentNode.removeChild(fenceMenuDom[domName]);
                }
            }
        });
    }

    function openAssetSettingsPanel(asset) {
        openDialogPanel(null, asset.Name, asset, false, null, 'asset', 'asset-options', openAssetSettingsPanel);

        var assetMenuDom = {
            alerts: document.getElementById('asset-function-alerts'),
            events: document.getElementById('asset-function-events'),
            status: document.getElementById('asset-function-status'),
            messages: document.getElementById('asset-function-messages'),
            currentPosition: document.getElementById('asset-function-current-position'),
            followAsset: document.getElementById('asset-function-follow-asset'),
            historyReplay: document.getElementById('asset-function-history-replay'),
            sendWaypoint: document.getElementById('asset-function-send-waypoint'),
            setWaypoint: document.getElementById('asset-function-set-waypoint'),
            addPosition: document.getElementById('asset-function-add-position'),
            routeAsset: document.getElementById('asset-function-route-asset'),
            sendMessage: document.getElementById('asset-function-send-message'),
            sendCommand: document.getElementById('asset-function-send-command'),
            setOutput: document.getElementById('asset-function-set-output'),
            setDriver: document.getElementById('asset-function-set-driver'),
            viewLogs: document.getElementById('asset-function-view-logs'),
            viewLogsMessage: document.getElementById('asset-function-view-logs-message'),
            viewLogsIO: document.getElementById('asset-function-view-logs-io'),
            viewLogsDriver: document.getElementById('asset-function-view-logs-driver'),
            viewLogsWaypoint: document.getElementById('asset-function-view-logs-waypoint'),
            viewLogsRefuel: document.getElementById('asset-function-view-logs-refuel'),
            viewLogsServiceMeters: document.getElementById('asset-function-view-logs-service-meters'),
            viewLogsGarminForms: document.getElementById('asset-function-view-logs-garmin-forms'),
            editAsset: document.getElementById('asset-function-edit-asset'),
            deleteAsset: document.getElementById('asset-function-delete-asset')
        };

        _.each(assetMenuDom, function (item) {
            if (item !== null) {
                item.classList.remove('disabled');
                item.querySelector('a').classList.remove('disabled');
            }
        });

        // enable/disable menu items based on asset capabilities
        var device = findDeviceById(asset.DeviceId);

        var currentPosition = tracking.data.live.latestPositionsByAssetId[asset.Id];
        if (currentPosition === undefined) {
            disableSettingsMenuItem(assetMenuDom.currentPosition);
        }

        if (_.indexOf(tracking.state.liveFollow.assets, asset) === -1) {
            assetMenuDom.followAsset.querySelector('.item-action-text').textContent = tracking.strings.FOLLOW_LIVE;
        } else {
            assetMenuDom.followAsset.querySelector('.item-action-text').textContent = tracking.strings.UNFOLLOW_LIVE;
        }

        if (tracking.data.places === null || tracking.data.places.length === 0 || tracking.options.enabledFeatures.indexOf('ASSET_ROUTING') === -1) {
            disableSettingsMenuItem(assetMenuDom.routeAsset);
        }

        if ((tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging) || !isMessagingEnabledForAsset(asset)) {
            if (assetMenuDom.messages !== null) {
                disableSettingsMenuItem(assetMenuDom.messages);
            }
            disableSettingsMenuItem(assetMenuDom.sendMessage);
        }

        if (tracking.user.isAnonymous || !isWaypointEnabledForAsset(asset) || tracking.options.enabledFeatures.indexOf('WAYPOINTS') === -1) {
            disableSettingsMenuItem(assetMenuDom.sendWaypoint);
        }

        if (tracking.user.isAnonymous || tracking.options.enabledFeatures.indexOf('WAYPOINTS') === -1) {
            disableSettingsMenuItem(assetMenuDom.setWaypoint);
        }

        if (tracking.user.isAnonymous
            || tracking.user.canSendCommands === undefined
            || !tracking.user.canSendCommands
            || !isOutputEnabledForAsset(asset)
            || tracking.options.enabledFeatures.indexOf('REMOTE_MANAGEMENT') === -1) {
            disableSettingsMenuItem(assetMenuDom.setOutput);
        }

        if (tracking.user.isAnonymous
            || tracking.user.canEditAssets === undefined
            || !tracking.user.canEditAssets
            || tracking.options.enabledFeatures.indexOf('DRIVER_MANAGEMENT') === -1) {
            disableSettingsMenuItem(assetMenuDom.setDriver);
        }

        if (assetMenuDom.addPosition !== null) {
            if (tracking.user.isAnonymous
                || tracking.user.canEditAssets === undefined
                || !tracking.user.canEditAssets) {
                disableSettingsMenuItem(assetMenuDom.addPosition);
            }
        }

        if (tracking.user.isAnonymous || tracking.options.enabledFeatures.indexOf('LOGS_OBDII') === -1) {

        }
        if (tracking.user.isAnonymous || tracking.options.enabledFeatures.indexOf('LOGS_FUEL') === -1) {
            disableSettingsMenuItem(assetMenuDom.viewLogsRefuel);
        }
        if (tracking.user.isAnonymous || tracking.options.enabledFeatures.indexOf('LOGS_SERVICE_METER') === -1) {
            disableSettingsMenuItem(assetMenuDom.viewLogsServiceMeters);
        }
        if ((tracking.user.isAnonymous && !tracking.options.allowAnonymousMessaging) || tracking.options.enabledFeatures.indexOf('LOGS_MESSAGE') === -1) {
            disableSettingsMenuItem(assetMenuDom.viewLogsMessage);
        }
        if (tracking.user.isAnonymous || tracking.options.enabledFeatures.indexOf('LOGS_DRIVER') === -1) {
            disableSettingsMenuItem(assetMenuDom.viewLogsDriver);
        }
        if (tracking.user.isAnonymous || tracking.options.enabledFeatures.indexOf('LOGS_WAYPOINT') === -1) {
            disableSettingsMenuItem(assetMenuDom.viewLogsWaypoint);
        }
        if (tracking.user.isAnonymous || tracking.options.enabledFeatures.indexOf('IO_MAPPING') === -1) {
            disableSettingsMenuItem(assetMenuDom.viewLogsIO);
        }

        if (assetMenuDom.viewLogsGarminForms !== null) {
            if (tracking.user.isAnonymous || tracking.options.enabledFeatures.indexOf('GARMIN_INTEGRATION') === -1) {
                disableSettingsMenuItem(assetMenuDom.viewLogsGarminForms);
            }
        }

        if (!isServiceMeterEnabledForAsset(asset)) {
            disableSettingsMenuItem(assetMenuDom.viewLogsServiceMeters);
        }

        if (!device.SupportsGarminForms) {
            disableSettingsMenuItem(assetMenuDom.viewLogsGarminForms);
        }

        if (isSendCommandDisabledForDevice(device)) {
            disableSettingsMenuItem(assetMenuDom.sendCommand);
        }

        if (tracking.user.isAnonymous
            || tracking.user.canEditAssets === undefined
            || !tracking.user.canEditAssets) {
            disableSettingsMenuItem(assetMenuDom.editAsset);
        }

        if (tracking.user.isAnonymous
            || tracking.user.canDeleteAssets === undefined
            || !tracking.user.canDeleteAssets
            || tracking.user.limitAssetEditing
            || tracking.user.assetControlFederated) {
            disableSettingsMenuItem(assetMenuDom.deleteAsset);
        }

        // remove some options altogether based on menu exclusion options
        _.each(tracking.options.assetMenuExclude, function (excluded) {
            var domName = dashToCamelCase(excluded);
            if (assetMenuDom[domName] !== undefined && assetMenuDom[domName] !== null) {
                if (assetMenuDom[domName].parentNode !== null) {
                    assetMenuDom[domName].parentNode.removeChild(assetMenuDom[domName]);
                }
            }
        });
    }

    function addOrUpdatePlaceClick(e) {
        console.warn('addOrUpdatePlaceClick');
        if (e !== null && e !== undefined) {
            e.preventDefault();
        }
        // open place marker, if it exists, otherwise new (Garmin Waypoint)
        var location = $(this).data('location');
        if (location !== undefined) {
            var name = $(this).data('name');
            var latlng = L.latLng(location.Lat, location.Lng);
            var place = findPlaceByLatLng(latlng);
            if (place == null) {
                openPlaceDialog(null);
                $('#txtPlaceName').val(name);
                updateChosenLocation(latlng, tracking.state.mapClickHandlers.PLACE);
            } else {
                showPlaceInformationOnMap(latlng, place);
            }
        } else {
            var lat = this.getAttribute('data-lat');
            var lng = this.getAttribute('data-lng');
            //var positionId = this.getAttribute('data-position-id');
            //var assetId = this.getAttribute('data-asset-id');
            var latlng = L.latLng(lat, lng);
            var place = findPlaceByLatLng(latlng);
            if (place == null) {
                openPlaceDialog(null);
                updateChosenLocation(latlng, tracking.state.mapClickHandlers.PLACE);
            } else {
                showPlaceInformationOnMap(latlng, place);
            }
        }
    }

    function itemSettingsClick(e) {
        var li = this.parentNode.parentNode;
        if (this.hasAttribute('data-asset-id')
            || this.hasAttribute('data-group-id')
            || this.hasAttribute('data-fence-id')
            || this.hasAttribute('data-place-id')
            || this.hasAttribute('data-trip-id')
            || this.hasAttribute('data-shared-view-id')) {
            li = this;
        }
        if (li.hasAttribute('data-asset-id')) {
            var assetId = li.getAttribute('data-asset-id');
            var asset = findAssetById(assetId);
            if (asset === null) {
                return;
            }

            openAssetSettingsPanel(asset);
        } else if (li.hasAttribute('data-group-id')) {
            var groupId = li.getAttribute('data-group-id');
            var groupType = li.getAttribute('data-group-for');

            if (groupType === 'journeys') {
                var journeyId = parseInt(groupId.substring(8));
                var journey = findJourneyById(journeyId);
                if (journey !== null) {
                    openJourneySettingsPanel(journey);
                    return;
                }
            }
            var group = findGroupById(groupId);
            if (group === null) {
                return;
            }

            switch (groupType) {
                case 'assets':
                    openAssetGroupSettingsPanel(group);
                    break;
                case 'fences':
                    break;
                case 'places':
                    break;
                case 'shared-views':
                    break;
                default:
                    break;
            }

        } else if (li.hasAttribute('data-fence-id')) {
            var fenceId = li.getAttribute('data-fence-id');
            var fence = findFenceById(fenceId);
            if (fence === null) {
                return;
            }

            openFenceSettingsPanel(fence);
        } else if (li.hasAttribute('data-place-id')) {
            var placeId = li.getAttribute('data-place-id');
            var place = findPlaceById(placeId);
            if (place === null) {
                return;
            }

            openPlaceSettingsPanel(place);
        } else if (li.hasAttribute('data-trip-id')) {
            var tripId = li.getAttribute('data-trip-id');
            var trip = findTripById(tripId);
            if (trip === null) {
                return;
            }
            openTripSettingsPanel(trip);
        } else if (li.hasAttribute('data-shared-view-id')) {
            var sharedViewId = li.getAttribute('data-shared-view-id');
            var sharedView = findSharedViewById(sharedViewId);
            if (sharedView === null) {
                return;
            }
            openSharedViewSettingsPanel(sharedView);
        }
    }

    function loadPanelCommand(name, showLoadingMessage) {

    }

    function wrapUrl(url) {
        if (!tracking.user.isImpersonated) {
            return url;
        }
        if (url.indexOf('?') === -1) {
            url += '?';
        } else {
            url += '&';
        }
        url += 'ishr=' + encodeURIComponent(tracking.user.id);
        return url;
    }

    tracking.testLoadDialog = loadDialog;

    function fallbackCopyTextToClipboard(text) {
        var textArea = document.createElement("textarea");
        textArea.value = text;
        document.body.appendChild(textArea);
        textArea.focus();
        textArea.select();

        var success = false;
        try {
            var successful = document.execCommand('copy');
            var msg = successful ? 'successful' : 'unsuccessful';
            console.log('Fallback: Copying text command was ' + msg);
            success = true;
        } catch (err) {
            console.error('Fallback: Oops, unable to copy', err);
        }

        document.body.removeChild(textArea);
        return success;
    }
    function copyTextToClipboard(text) {
        var def = $.Deferred();

        if (text === undefined || text === null || text === '') {
            def.reject();
            return def;
        }

        if (!navigator.clipboard) {
            if (fallbackCopyTextToClipboard(text)) {
                console.log('fallback success');
                def.resolve(true);
            } else {
                console.log('fallback fail');
                def.reject();
            }
            return def;
        }

        navigator.clipboard.writeText(text).then(function () {
            def.resolve(true);
            console.log('Async: Copying to clipboard was successful!');
        }, function (err) {
            def.reject();
            console.error('Async: Could not copy text: ', err);
        });
        return def;
    }

    function loadDialog(name, showLoadingMessage) {
        var dialog = tracking.dialogs[name];
        if (dialog === undefined) {
            console.error('Dialog ' + name + ' has not been defined.');
            return;
        }

        var dialogLoaded = $.Deferred();

        if (tracking.data.domNodes.dialogs[name] === undefined) {
            // load and initialize the dialog, returning a deferred/promise for when it is complete
            var dialogContainer = document.createElement('div');
            var dialogInitialize = $.Deferred();
            if (showLoadingMessage === true) {
                toggleLoadingMessage(true, 'dialog-' + name);
            }
            $.when($.get(dialog.url, function (data) {
                dialogContainer.innerHTML = data;
                var dialogNode = dialogContainer.childNodes[0];
                document.getElementById('track-root').appendChild(dialogNode);
                dialog.domNode = dialogNode;
                tracking.data.domNodes.dialogs[name] = dialogNode;
                dialog.initialize(dialogNode);
                dialog.isLoaded = true;
                tracking.log('Dialog ' + name + ' loaded & initialized.');
                dialogInitialize.resolve(true);
                $('label,input,select', dialogNode).bsTooltip();
            }), dialogInitialize).done(function (getRequestResult, initDialogResult) {
                dialogLoaded.resolve(true);
                if (showLoadingMessage === true) {
                    toggleLoadingMessage(false, 'dialog-' + name);
                }
            });
        } else {
            // dialog has already been loaded
            dialogLoaded.resolve(true);
        }
        return dialogLoaded;
    }

    function formatIntro(header, content, subs) {
        for (var sub in subs) {
            var placeholder = '{' + sub + '}';
            header = header.replace(placeholder, subs[sub]);
            content = content.replace(placeholder, subs[sub]);
        }
        return '<h6>' + header + '</h6><p>' + content + '</p>';
    }

    tracking.dialogs.gsatmicro = {
        url: wrapUrl('track/dialogs/gsatmicro'),
        isLoaded: false,
        domNode: null,
        dialog: null,

        requestGSatMicroInformation: function (assetId) {
            var data = {
                assetId: assetId
            };
            var status = document.getElementById('gsatmicro-query-status');
            toggleLoadingMessage(true, 'micro-information');
            return $j.ajax({
                type: 'POST',
                url: wrapUrl('/services/GPSService.asmx/GSatMicroGetConfiguration'),
                data: JSON.stringify(data),
                contentType: 'application/json; charset=utf-8',
                dataType: 'json',
                success: function (msg) {
                    var result = msg.d;
                    if (result) {
                        if (result.Success == true) {
                            if (result.LastRetrievedOn == null) {
                                $j('#GSatMicroConfigLastRetrieved').text(tracking.strings.NEVER);
                            } else {
                                $j('#GSatMicroConfigLastRetrieved').text(result.LastRetrievedOn);
                            }
                            if (result.LastQueriedOn == null) {
                                $j('#GSatMicroConfigLastQueried').text(tracking.strings.NEVER);
                            } else {
                                $j('#GSatMicroConfigLastQueried').text(result.LastQueriedOn);
                            }

                            var config = result.Configuration;
                            tracking.dialogs.gsatmicro.populateGSatMicroConfig(config, assetId);
                        } else {
                            // message failure, keep text field to allow retry
                            formShowErrorMessage(status, tracking.strings.MSG_GET_DEVICE_INFORMATION_ERROR);
                            if (result.ErrorMessage != null && result.ErrorMessage != '') {
                                formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                            }
                        }
                    }
                    toggleLoadingMessage(false, 'micro-information');
                },
                error: function (xhr, status, error) {
                    utility.handleWebServiceError(tracking.strings.MSG_GET_DEVICE_INFORMATION_ERROR);
                    toggleLoadingMessage(false, 'micro-information');
                }
            });
        },

        requestGSatMicroConfig: function (id) {
            var dialog = $j(tracking.dialogs.gsatmicro.domNode);
            var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                var data = {
                    assetId: id,
                    gateway: gateway,
                    gatewayTimeout: gatewayTimeout,
                    gatewayRetries: gatewayRetries,
                    groupIds: groupIds,
                    assetIds: assetIds
                };
                toggleLoadingMessage(true, 'gsatmicro-get-config');
                var status = document.getElementById('gsatmicro-query-status');
                return $j.ajax({
                    type: 'POST',
                    url: wrapUrl('/services/GPSService.asmx/GSatMicroSendGetConfigurationRequest'),
                    data: JSON.stringify(data),
                    contentType: 'application/json; charset=utf-8',
                    dataType: 'json',
                    success: function (msg) {
                        var result = msg.d;
                        if (result) {
                            if (result.Success == true) {
                                tracking.dialogs.gsatmicro.requestGSatMicroInformation(id);
                                formShowSuccessMessage(status, tracking.strings.MSG_GET_PARAMETERS_SUCCESS);
                            } else {
                                // message failure, keep text field to allow retry
                                formShowErrorMessage(status, tracking.strings.MSG_GET_PARAMETERS_ERROR);
                                if (result.ErrorMessage != null && result.ErrorMessage != '') {
                                    formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                                }
                            }
                        }
                        toggleLoadingMessage(false, 'gsatmicro-get-config');
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    },
                    error: function (xhr, status, error) {
                        utility.handleWebServiceError(tracking.strings.MSG_GET_PARAMETERS_ERROR);
                        toggleLoadingMessage(false, 'gsatmicro-get-config');
                        $(tracking.data.domNodes.modals.messageAction).modal('hide');
                    }
                });
            }
            var asset = findAssetById(id);
            var satelliteOnly = false;
            if (asset != null) {
                if (_.indexOf(tracking.devices.GSATMICRO_GSM, asset.DeviceId) === -1) {
                    satelliteOnly = true;
                }
            }
            openActionDialog(tracking.strings.GET_PARAMETERS, tracking.strings.GET_PARAMETERS, callback, id, null, satelliteOnly, tracking.devices.GSAT_MICROS);
        },

        populateGSatMicroConfig: function (config, assetId) {
            if (config == null) {
                $j('#GSatMicroProtocolVersion').text(tracking.strings.UNKNOWN);
                $j('#GSatMicroSettingsVersion').text(tracking.strings.UNKNOWN);
                return;
            }

            var asset = findAssetById(assetId);
            var dialog = $j(tracking.dialogs.gsatmicro.domNode);
            dialog.find('.v2,.v3,.v4,.v5,.v6,.v7,.v8,.v9,.v10').hide();
            if (config.SettingsVersion != null) {
                for (var i = 2; i <= 10; i++) {
                    if (config.SettingsVersion >= i) {
                        dialog.find('.v' + i).show();
                    }
                }
            }

            // protocol version
            if ((config.Version == null) || (config.Version == '')) {
                $j('#GSatMicroProtocolVersion').text(tracking.strings.UNKNOWN);
            } else {
                $j('#GSatMicroProtocolVersion').text(config.Version);
            }

            // settings/software version
            if ((config.SettingsVersion == null) || (config.SettingsVersion == '')) {
                $j('#GSatMicroSettingsVersion').text(tracking.strings.UNKNOWN);
            } else {
                $j('#GSatMicroSettingsVersion').text(config.SettingsVersion);
            }

            $j('#txtGSatMicroSleep').val(config.SleepInterval);
            $j('#txtGSatMicroSOSSleep').val(config.SOSSleepInterval);
            $j('#txtGSatMicroGPSTimeout').val(config.GPSTimeout);
            $j('#txtGSatMicroGPSHDOP').val(config.GPSHDOP);
            $j('#txtGSatMicroGPSSettle').val(config.GPSSettle);
            $j('#txtGSatMicroIridiumTransmitTimeout').val(config.IridiumTxTimeout);
            $j('#txtGSatMicroIridiumSignalTimeout').val(config.IridiumSignalTimeout);
            $j('#txtGSatMicroIridiumTransmitRetries').val(config.IridiumTxRetries);
            $j('input:radio[name=rbGSatMicroSleepWhenPowered][value=' + config.SleepWhenPowered + ']').prop('checked', true);
            $j('input:radio[name=rbGSatMicroAltitude][value=' + config.IncludeAltitude + ']').prop('checked', true);
            if (config.LEDMask != null) {
                $j('input:radio[name=rbGSatMicroLEDMaskGPS][value=' + config.LEDMask.GPS + ']').prop('checked', true);
                $j('input:radio[name=rbGSatMicroLEDMaskMessage][value=' + config.LEDMask.Message + ']').prop('checked', true);
                $j('input:radio[name=rbGSatMicroLEDMaskPower][value=' + config.LEDMask.Power + ']').prop('checked', true);
                $j('input:radio[name=rbGSatMicroLEDMaskSatellite][value=' + config.LEDMask.Satellite + ']').prop('checked', true);
                $j('input:radio[name=rbGSatMicroLEDMaskAlarm][value=' + config.LEDMask.Alarm + ']').prop('checked', true);
            }
            $j('input:radio[name=rbGSatMicroGPSHibernateSleep][value=' + config.GPSHibernateSleep + ']').prop('checked', true);
            $j('input:radio[name=rbGSatMicroCacheReports][value=' + config.CacheReports + ']').prop('checked', true);
            $j('input:radio[name=rbGSatMicroKeepRadioAwake][value=' + config.KeepRadioAwake + ']').prop('checked', true);
            $j('input:radio[name=rbGSatMicroGPSOnAlways][value=' + config.GPSOnAlways + ']').prop('checked', true);
            $j('input:radio[name=rbGSatMicroSleepWithBat][value=' + config.SleepWithBat + ']').prop('checked', true);
            $j('input:radio[name=rbGSatMicroRequireEncryptedMT][value=' + config.RequireEncryptedMT + ']').prop('checked', true);
            $j('#txtGSatMicroMovingSleepInterval').val(config.MovingSleepInterval);
            $j('#txtGSatMicroMovingThreshSpeed').val(config.MovingThreshSpeed);
            $j('input:radio[name=rbGSatMicroIncludeSeconds][value=' + config.IncludeSeconds + ']').prop('checked', true);
            $j('input:radio[name=rbGSatMicroReportFormat][value=' + config.ReportFormat + ']').prop('checked', true);
            $j('#txtGSatMicroExternalPowerSleep').val(config.ExternalPowerSleep);
            $j('input:radio[name=rbGSatMicroExternalPowerAutoStart][value=' + config.ExternalPowerAutoStart + ']').prop('checked', true);
            $j('input:radio[name=rbGSatMicroNMEA][value=' + config.NMEA + ']').prop('checked', true);
            $j('input:radio[name=rbGSatMicroBLEOff][value=' + config.BLEOff + ']').prop('checked', true);
            $j('#txtGSatMicroAccelThreshold').val(config.AccelerometerThreshold);
        },

        initialize: function (dialogNode) {
            // events
            bindParameterSelectionEvents('.parameters-form');

            $j(this.domNode).on('click', '#QueryGSatMicroConfig', function (e) {
                e.preventDefault();

                var id = $j(tracking.dialogs.gsatmicro.domNode).data('assetId');
                tracking.dialogs.gsatmicro.requestGSatMicroConfig(id);
            });

            $j(this.domNode).on('click', '#micro-set-message', function (e) {
                e.preventDefault();
                $j('#GSatMicroCommandText').val($j('#micro-message').val());
            });

            $j(this.domNode).on('click', '#GSatMicroSendCommand', function (e) {
                e.preventDefault();

                var dialog = $j(tracking.dialogs.gsatmicro.domNode);
                var status = document.getElementById('gsatmicro-query-status');
                var form = $j('#gsatmicro-commands-form');
                var isFormValid = form.valid();
                if (!isFormValid)
                    return;
                var command = $j('#GSatMicroCommandText').val();

                var assetId = $j(dialog).data('assetId');
                var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                    var data = {
                        assetId: assetId,
                        command: command,
                        gateway: gateway,
                        gatewayTimeout: gatewayTimeout,
                        gatewayRetries: gatewayRetries,
                        groupIds: groupIds,
                        assetIds: assetIds
                    };

                    toggleLoadingMessage(true, 'gsatmicro-command');
                    $j.ajax({
                        type: 'POST',
                        url: wrapUrl('/services/GPSService.asmx/GSatMicroSendCommandRequest'),
                        data: JSON.stringify(data),
                        contentType: 'application/json; charset=utf-8',
                        dataType: 'json',
                        success: function (msg) {
                            var result = msg.d;
                            if (result) {
                                if (result.Success == true) {
                                    formShowSuccessMessage(status, tracking.strings.MSG_COMMAND_SUCCESS);
                                } else {
                                    // message failure, keep text field to allow retry
                                    formShowErrorMessage(status, tracking.strings.MSG_COMMAND_ERROR);
                                    if (result.ErrorMessage != null && result.ErrorMessage != '') {
                                        formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                                    }
                                }
                            }
                            toggleLoadingMessage(false, 'gsatmicro-command');
                            $(tracking.data.domNodes.modals.messageAction).modal('hide');
                        },
                        error: function (xhr, status, error) {
                            utility.handleWebServiceError(tracking.strings.MSG_COMMAND_ERROR);
                            toggleLoadingMessage(false, 'gsatmicro-command');
                            $(tracking.data.domNodes.modals.messageAction).modal('hide');
                        }
                    });
                }

                var asset = findAssetById(assetId);
                var satelliteOnly = false;
                if (asset != null) {
                    if (_.indexOf(tracking.devices.GSATMICRO_GSM, asset.DeviceId) === -1) {
                        satelliteOnly = true;
                    }
                }

                openActionDialog(tracking.strings.SEND_COMMAND, tracking.strings.SEND_COMMAND, callback, assetId, null, satelliteOnly, tracking.devices.GSAT_MICROS);
            });

            $j(this.domNode).on('click', '#btnGSatMicroSetParameters', function (e) {
                e.preventDefault();

                var dialog = $j(tracking.dialogs.gsatmicro.domNode);
                var status = document.getElementById('gsatmicro-query-status');
                var form = $j('#gsatmicro-settings-form');
                var isFormValid = form.valid();
                if (!isFormValid)
                    return;

                //var mask = getValueIfCheckboxSelected('#txtGSatMicroLEDMask');
                //if (mask != null) {
                //    mask = parseInt(mask);
                //}
                var LEDMask = null;
                if ($j('#chkGSatMicroLEDMask').is(':checked')) {
                    LEDMask = {
                        GPS: $j('input[name=rbGSatMicroLEDMaskGPS]:checked').val(),
                        Message: $j('input[name=rbGSatMicroLEDMaskMessage]:checked').val(),
                        Power: $j('input[name=rbGSatMicroLEDMaskPower]:checked').val(),
                        Satellite: $j('input[name=rbGSatMicroLEDMaskSatellite]:checked').val(),
                        Alarm: $j('input[name=rbGSatMicroLEDMaskAlarm]:checked').val()
                    };
                }
                var parameters = {
                    GPSHDOP: getValueIfCheckboxSelected('#txtGSatMicroGPSHDOP'),
                    GPSSettle: getValueIfCheckboxSelected('#txtGSatMicroGPSSettle'),
                    GPSTimeout: getValueIfCheckboxSelected('#txtGSatMicroGPSTimeout'),
                    IncludeAltitude: getValueIfCheckboxSelected('input[name=rbGSatMicroAltitude]:checked'),
                    IridiumTxRetries: getValueIfCheckboxSelected('#txtGSatMicroIridiumTransmitRetries'),
                    IridiumTxTimeout: getValueIfCheckboxSelected('#txtGSatMicroIridiumTransmitTimeout'),
                    IridiumSignalTimeout: getValueIfCheckboxSelected('#txtGSatMicroIridiumSignalTimeout'),
                    LEDMask: LEDMask,
                    SleepInterval: getValueIfCheckboxSelected('#txtGSatMicroSleep'),
                    SleepWhenPowered: getValueIfCheckboxSelected('input[name=rbGSatMicroSleepWhenPowered]:checked'),
                    SOSSleepInterval: getValueIfCheckboxSelected('#txtGSatMicroSOSSleep'),
                    KeepRadioAwake: getValueIfCheckboxSelected('input[name=rbGSatMicroKeepRadioAwake]:checked'),
                    GPSHibernateSleep: getValueIfCheckboxSelected('input[name=rbGSatMicroGPSHibernateSleep]:checked'),
                    CacheReports: getValueIfCheckboxSelected('input[name=rbGSatMicroCacheReports]:checked'),
                    GPSOnAlways: getValueIfCheckboxSelected('input[name=rbGSatMicroGPSOnAlways]:checked'),
                    SleepWithBat: getValueIfCheckboxSelected('input[name=rbGSatMicroSleepWithBat]:checked'),
                    RequireEncryptedMT: getValueIfCheckboxSelected('input[name=rbGSatMicroRequireEncryptedMT]:checked'),
                    MovingSleepInterval: getValueIfCheckboxSelected('#txtGSatMicroMovingSleepInterval'),
                    MovingThreshSpeed: getValueIfCheckboxSelected('#txtGSatMicroMovingThreshSpeed'),
                    IncludeSeconds: getValueIfCheckboxSelected('input[name=rbGSatMicroIncludeSeconds]:checked'),
                    ReportFormat: getValueIfCheckboxSelected('input[name=rbGSatMicroReportFormat]:checked'),
                    ExternalPowerSleep: getValueIfCheckboxSelected('#txtGSatMicroExternalPowerSleep'),
                    ExternalPowerAutoStart: getValueIfCheckboxSelected('input[name=rbGSatMicroExternalPowerAutoStart]:checked'),
                    NMEA: getValueIfCheckboxSelected('input[name=rbGSatMicroNMEA]:checked'),
                    BLEOff: getValueIfCheckboxSelected('input[name=rbGSatMicroBLEOff]:checked'),
                    AccelerometerThreshold: getValueIfCheckboxSelected('#txtGSatMicroAccelThreshold')
                };

                var assetId = $j(dialog).data('assetId');
                var callback = function (id, groupIds, assetIds, gateway, gatewayTimeout, gatewayRetries) {
                    var data = {
                        assetId: assetId,
                        parameters: parameters,
                        gateway: gateway,
                        gatewayTimeout: gatewayTimeout,
                        gatewayRetries: gatewayRetries,
                        groupIds: groupIds,
                        assetIds: assetIds
                    };

                    toggleLoadingMessage(true, 'gsatmicro-parameters');
                    $j.ajax({
                        type: 'POST',
                        url: wrapUrl('/services/GPSService.asmx/GSatMicroSendSetParametersRequest'),
                        data: JSON.stringify(data),
                        contentType: 'application/json; charset=utf-8',
                        dataType: 'json',
                        success: function (msg) {
                            var result = msg.d;
                            if (result) {
                                if (result.Success == true) {
                                    formShowSuccessMessage(status, tracking.strings.MSG_PARAMETERS_SUCCESS);
                                } else {
                                    // message failure, keep text field to allow retry
                                    formShowErrorMessage(status, tracking.strings.MSG_PARAMETERS_ERROR);
                                    if (result.ErrorMessage != null && result.ErrorMessage != '') {
                                        formShowErrorMessage(status, status.textContent + ' ' + result.ErrorMessage);
                                    }
                                }
                            }
                            toggleLoadingMessage(false, 'gsatmicro-parameters');
                            $(tracking.data.domNodes.modals.messageAction).modal('hide');
                        },
                        error: function (xhr, status, error) {
                            utility.handleWebServiceError(tracking.strings.MSG_PARAMETERS_ERROR);
                            toggleLoadingMessage(false, 'gsatmicro-parameters');
                            $(tracking.data.domNodes.modals.messageAction).modal('hide');
                        }
                    });
                }

                var asset = findAssetById(assetId);
                var satelliteOnly = false;
                if (asset !== null) {
                    if (_.indexOf(tracking.devices.GSATMICRO_GSM, asset.DeviceId) === -1) {
                        satelliteOnly = true;
                    }
                }
                openActionDialog(tracking.strings.SET_PARAMETERS, tracking.strings.SET_PARAMETERS, callback, assetId, null, satelliteOnly, tracking.devices.GSAT_MICROS);
            });

            tracking.data.validation.gsatmicroSettings = $('#gsatmicro-settings-form').validate();
            tracking.data.validation.gsatmicroCommands = $('#gsatmicro-commands-form').validate();
            //this.dialog = $(dialogNode).dialog({
            //    autoOpen: false,
            //    modal: true,
            //    width: 850,
            //    buttons: [tracking.dialogs.closeButton],
            //    create: function (event, ui) {
            //        createAssetSelector($j(this));
            //        createDialogCloseButton(this);
            //        replaceDialogButtons(this, [tracking.dialogs.closeButton]);


            //    }
            //});


        }
    };

    tracking.walkthrough = {
        isLoaded: false,
        url: wrapUrl('track/dialogs/walkthrough'),
        intro: null,
        currentStep: null,
        previousStep: null,

        initialize: function () {
            tracking.walkthrough.intro = introJs();
            var introOptions = {
                tooltipPosition: 'auto',
                showStepNumbers: false,
                positionPrecedence: ['right', 'left', 'bottom', 'top']
            };

            var steps = [
                {
                    intro: formatIntro(tracking.strings.WT_INTRO_HEADER, tracking.strings.WT_INTRO_CONTENT),
                    onbeforechange: function () {
                        changeActivePanel(tracking.panels.ASSETS);
                    }
                },
                {
                    element: '#menu-help-button',
                    intro: formatIntro(tracking.strings.WT_REPLAY_HEADER, tracking.strings.WT_REPLAY_CONTENT, { icon: '<svg><use xlink:href="/content/svg/tracking.svg?v=15#question-circle-solid"></use></svg>' })
                },
                {
                    intro: formatIntro(tracking.strings.WT_BEGIN_HEADER, tracking.strings.WT_BEGIN_CONTENT)
                },
                {
                    element: '#nav-utility',
                    intro: formatIntro(tracking.strings.WT_NAVIGATION_HEADER, tracking.strings.WT_MAIN_NAVIGATION_CONTENT, { icon: '<svg><use xlink:href="/content/svg/tracking.svg?v=15#bars"></use></svg>' }),
                    onleavestep: function () {

                    }
                },
                {
                    element: '#menu-help-button',
                    intro: formatIntro(tracking.strings.WT_HELP_HEADER, tracking.strings.WT_HELP_CONTENT, { icon: '<svg><use xlink:href="/content/svg/tracking.svg?v=15#question-circle-solid"></use></svg>' }),
                    onbeforechange: function () {
                        $('#menu-help-button').dropdown('show');
                    },
                    onleavestep: function () {
                        $('#menu-help-button').dropdown('hide');
                    }
                },
                {
                    element: '#menu-user-button',
                    intro: formatIntro(tracking.strings.WT_USER_HEADER, tracking.strings.WT_USER_CONTENT, { icon: '<svg><use xlink:href="/content/svg/tracking.svg?v=15#user-alt-solid"></use></svg>' }),
                    onbeforechange: function () {
                        $('#menu-user-button').dropdown('show');
                    },
                    onleavestep: function () {
                        $('#menu-user-button').dropdown('hide');
                    }
                },
                {
                    element: '#nav-primary-container',
                    intro: formatIntro(tracking.strings.WT_NAVIGATION_HEADER, tracking.strings.WT_NAVIGATION_CONTENT),
                    onbeforechange: function () {
                        // ensure nav menu is open
                        if (!tracking.data.domNodes.nav.toggle.classList.contains('is-active')) {
                            openPrimaryPanel();
                            openPrimaryNavigation();
                        }
                    }
                },
                {
                    element: '#panel-primary',
                    intro: formatIntro(tracking.strings.WT_LIST_PANEL_HEADER, tracking.strings.WT_LIST_PANEL_CONTENT),
                    onbeforechange: function () {
                        openPrimaryPanel();
                        switchMapMode(true);
                    }
                },
                {
                    element: '#asset-filter-options',
                    intro: formatIntro(tracking.strings.WT_QUICK_ACTIONS_HEADER, tracking.strings.WT_QUICK_ACTIONS_CONTENT, { icon: '<svg><use xlink:href="/content/svg/tracking.svg?v=15#ellipsis-v"></use></svg>' }),
                    onbeforechange: function () {
                        openPrimaryPanel();
                    }
                },
                { // requires searching/filtering feature
                    element: '#filter-assets',
                    intro: formatIntro(tracking.strings.WT_SEARCH_BAR_HEADER, tracking.strings.WT_SEARCH_BAR_CONTENT),
                    onbeforechange: function () {
                        openPrimaryPanel();
                    }
                },
                //{
                //    element: '#filter-history-range',
                //    intro: formatIntro(tracking.strings.WT_DATE_RANGE_HEADER, tracking.strings.WT_DATE_RANGE_CONTENT),
                //    onbeforechange: function () {
                //        openPrimaryPanel();
                //        switchMapMode(false);
                //    }
                //},
                {
                    element: '#group-all-assets .group-header',
                    intro: formatIntro(tracking.strings.WT_GROUPS_HEADER, tracking.strings.WT_GROUPS_CONTENT),
                    onbeforechange: function () {
                        switchMapMode(true);
                        openPrimaryPanel();
                    }
                },
                {
                    element: '#group-all-assets .showhide',
                    intro: formatIntro(tracking.strings.WT_VISIBILITY_HEADER, tracking.strings.WT_VISIBILITY_CONTENT, { icon: '<svg><use xlink:href="/content/svg/tracking.svg?v=15#visible"></use></svg>' }),
                    onbeforechange: function () {
                        openPrimaryPanel();
                    }
                },
                {
                    element: '#group-all-assets .notifications',
                    intro: formatIntro(tracking.strings.WT_NOTIFICATION_HEADER, tracking.strings.WT_NOTIFICATION_CONTENT),
                    onbeforechange: function () {
                        openPrimaryPanel();
                        document.getElementById('group-all-assets').querySelector('.group-info .notifications').classList.add('light-up');
                    },
                    onleavestep: function () {
                        document.getElementById('group-all-assets').querySelector('.group-info .notifications').classList.remove('light-up');
                    }
                },                
                {
                    element: '#panel-secondary',
                    intro: formatIntro(tracking.strings.WT_DETAILS_PANEL_HEADER, tracking.strings.WT_DETAILS_PANEL_CONTENT),
                    onbeforechange: function () {
                        var group = findGroupById('all-assets');
                        openAssetGroupSettingsPanel(group);
                    }
                },
                {
                    element: '#panel-secondary-item-options',
                    intro: formatIntro(tracking.strings.WT_JUMP_NAVIGATION_HEADER, tracking.strings.WT_JUMP_NAVIGATION_CONTENT),
                    onbeforechange: function () {
                        var group = findGroupById('all-assets');
                        openAssetGroupSettingsPanel(group);
                    }
                },
                {
                    element: '#nav-options-tab',
                    intro: formatIntro(tracking.strings.WT_OPTIONS_TAB_HEADER, tracking.strings.WT_OPTIONS_TAB_CONTENT, { icon: '<svg><use xlink:href="/content/svg/tracking.svg?v=15#cogs"></use></svg>' }),
                    onbeforechange: function () {
                        var group = findGroupById('all-assets');
                        openAssetGroupSettingsPanel(group);
                    }
                },
                {
                    element: '#nav-positions-tab',
                    intro: formatIntro(tracking.strings.WT_NOTIFICATION_TABS_HEADER, tracking.strings.WT_NOTIFICATION_TABS_CONTENT),
                    onbeforechange: function () {
                        var group = findGroupById('all-assets');
                        openPositionsForGroup(group);
                    }
                },
                {
                    //element: '#main_map',
                    intro: formatIntro(tracking.strings.WT_MAP_VIEW_HEADER, tracking.strings.WT_MAP_VIEW_CONTENT),
                    onbeforechange: function () {
                        closeSecondaryPanel();
                        if (window.screen.width <= 768) {
                            hidePrimaryPanel();
                        }
                    }
                },
                {
                    element: '#map-mode',
                    intro: formatIntro(tracking.strings.WT_MAP_MODE_HEADER, tracking.strings.WT_MAP_MODE_CONTENT),
                    onbeforechange: function () {
                        // ensure map is visible
                        if (window.screen.width <= 768) {
                            hidePrimaryPanel();
                        }
                    }
                },
                {
                    element: '#map-mode',
                    intro: formatIntro(tracking.strings.WT_LIVE_MODE_HEADER, tracking.strings.WT_LIVE_MODE_CONTENT),
                    onbeforechange: function () {
                        // ensure nav menu is open
                        closeSecondaryPanel();
                        switchMapMode(true);
                        if (!tracking.data.domNodes.nav.toggle.classList.contains('is-active')) {
                            openPrimaryPanel();
                            openPrimaryNavigation();
                        }
                    }
                },
                {
                    element: '#map-mode',
                    intro: formatIntro(tracking.strings.WT_HISTORY_MODE_HEADER, tracking.strings.WT_HISTORY_MODE_CONTENT),
                    onbeforechange: function () {
                        // ensure nav menu is open
                        closeSecondaryPanel();
                        switchMapMode(false);
                        if (!tracking.data.domNodes.nav.toggle.classList.contains('is-active')) {
                            openPrimaryPanel();
                            openPrimaryNavigation();
                        }
                    },
                    onleavestep: function () {
                        closePrimaryNavigation();
                    }
                },
                {
                    element: '#map-tools',
                    intro: formatIntro(tracking.strings.WT_MAP_TOOLS_HEADER, tracking.strings.WT_MAP_TOOLS_CONTENT),
                    onbeforechange: function () {
                        // ensure map is visible
                        if (window.screen.width <= 768) {
                            hidePrimaryPanel();
                        }
                    }
                },
                {
                    intro: formatIntro(tracking.strings.WT_START_TRACKING_HEADER, tracking.strings.WT_START_TRACKING_CONTENT, { icon: '<svg><use xlink:href="/content/svg/tracking.svg?v=15#question-circle-solid"></use></svg>' }),
                    onbeforechange: function () {
                        if (tracking.options.defaultMode === 'live') {
                            switchMapMode(true);
                        } else {
                            switchMapMode(false);
                        }
                    }
                }
            ];

            // remove unnecessary steps in reverse order to not change the ordering
            if (tracking.options.enabledFeatures.indexOf('UI_FILTERING_SEARCHING_SORTING') === -1) {
                steps.splice(9, 1); // search bar
            }

            if (tracking.user.isAnonymous) {
                steps.splice(5, 1); // user menu
            }

            tracking.walkthrough.intro.setOption('steps', steps);
            tracking.walkthrough.intro.setOptions(introOptions);

            // todo: remove steps based on user permissions, availability of panels...

            _.each(['onchange', 'onbeforechange'], function (event) {
                // add some per-step event handler support
                tracking.walkthrough.intro[event](function () {
                    var steps = this._options.steps;
                    var currentStep = this._currentStep;
                    if (event === 'onchange') {
                        tracking.walkthrough.currentStep = currentStep;
                    } else if (event === 'onbeforechange' && tracking.walkthrough.previousStep !== null) {
                        if (_.isFunction(steps[tracking.walkthrough.previousStep]['onleavestep'])) {
                            steps[tracking.walkthrough.previousStep]['onleavestep'](this);
                        }
                    }
                    if (_.isFunction(steps[currentStep][event])) {
                        steps[currentStep][event](this);
                    }
                    if (event === 'onchange') {
                        if (tracking.walkthrough.currentStep !== null) {
                            tracking.walkthrough.previousStep = currentStep;
                        }
                    }
                });
            });
            tracking.walkthrough.intro.onexit(function () {
                var steps = this._options.steps;
                if (steps[tracking.walkthrough.currentStep] !== undefined) {
                    if (_.isFunction(steps[tracking.walkthrough.currentStep]['onleavestep'])) {
                        steps[tracking.walkthrough.currentStep]['onleavestep'](this);
                    }
                }
                tracking.walkthrough.previousStep = null;
                tracking.walkthrough.currentStep = null;

                if (tracking.user.showWalkthrough) {
                    $.ajax({
                        type: 'POST',
                        url: wrapUrl('/services/GPSService.asmx/ShownWalkthrough'),
                        data: JSON.stringify({}),
                        contentType: 'application/json; charset=utf-8',
                        dataType: 'json'
                    });
                    tracking.user.showWalkthrough = false;
                }
                if (tracking.options.defaultMode === 'live') {
                    switchMapMode(true);
                } else {
                    switchMapMode(false);
                }
            });

            $(document).on('click', '#walkthrough-start', function (e) {
                e.preventDefault();
                tracking.walkthrough.intro.start();
            });
        }
    };
}(window.tracking = window.tracking || {}, jQuery));
;
var PasswordStrengthMeter = function (meter, errorMessage) {
	this.meter = meter;
	this.input = document.getElementById(this.meter.getAttribute('data-meter-for'));
	this.label = document.getElementById(this.meter.getAttribute('aria-labelledby'));

	var self = this;

	function checkStrength() {
		if (self.input.value === '') {
			self.meter.value = '0';
			self.label.classList.remove('is-visible');
			self.input.classList.remove('has-meter');
			self.input.removeAttribute('data-pwd-str');
			return;
		}
		var data = { pwd: self.input.value };
		$.ajax({
			type: 'POST',
			url: '/api/ui/pwdstr',
			data: JSON.stringify(data),
			contentType: 'application/json; charset=utf-8',
			dataType: 'json',
			success: function (msg) {
				self.input.classList.add('has-meter');
				self.input.setAttribute('data-pwd-str', msg.str);
				self.meter.value = msg.str + 1;
				_.each(self.label.querySelectorAll('strong'), function (item) {
					if (item.getAttribute('data-strength') == msg.str) {
						item.classList.remove('toggle-content');
					} else {
						item.classList.add('toggle-content');
                    }
				});
				self.label.classList.add('is-visible');
			}
		});
	}

	function init() {
		self.input.addEventListener('input', self.check);

		$.validator.addMethod('min-pwd-str', function (value, element) {
			if (!element.hasAttribute('data-pwd-str') || !element.hasAttribute('data-min-pwd-str')) {
				return true;
			}
			var minStr = parseInt(element.getAttribute('data-min-pwd-str'));
			var currStr = parseInt(element.getAttribute('data-pwd-str'));
			if (minStr !== NaN && currStr !== NaN) {

			}
			return !(currStr < minStr);
		}, errorMessage);
	}

	this.check = _.throttle(checkStrength, 200);
	init();
};
