/* Minification failed. Returning unminified contents.
(2787,21-24): run-time error JS1009: Expected '}': ...
(2787,20): run-time error JS1004: Expected ';'
(2792,1-2): run-time error JS1002: Syntax error: }
 */
// Case-insensitive contains, because jQuery won't add an option themselves http://bugs.jquery.com/ticket/278
jQuery.expr[":"].icontains = jQuery.expr.createPseudo(function (arg) {
	return function (elem) {
		var sText = (elem.textContent || elem.innerText || "");
		return sText.toUpperCase().indexOf(arg.toUpperCase()) >= 0;
	};
});

// Case-insensitive equals
jQuery.expr[":"].iequals = jQuery.expr.createPseudo(function (arg) {
	return function (elem) {
		var sText = (elem.textContent || elem.innerText || "");
		return sText.toUpperCase() == arg.toUpperCase();
	};
});

(function ($) {
	// Override public interface for Sizzle object and catch 'Permission Denied' exception. (case 18206) 
	var $oldFind = $.fn.find;

	$.fn.find = function () {
		try {
			document === document;
		} catch (e) {
			document = window.document;
		}

		return $oldFind.apply(this, arguments);
	};
})(jQuery);;
var SearchContext;
var suppressGetLastSearch = false;

var Conjugations = {
	HasAnyOf: '1',
	DoesNotHaveAnyOf: '2', 
	NotAny: '4',
	HasAny: '5'
};

var Validity = {
	Any: '0',
	ValidOnly: '1',
	InvalidOnly: '2'
};

var Headers = {
	Name: 1,
	ProductType: 2,
	Location: 3,
	TownCounty: 4,
	OpeningStartDate: 5,
	OpeningEndDate: 6
};

var SortDirection = {
	Asc: 0,
	Desc: 1
};

// Search query model 
var queryModel = new ProductSearchQuery();
function ProductSearchQuery() {
	this.StatusId = null;
	this.TypeIds = null;
	this.Conjugation = 0;
	this.ChannelsExtended = null;
	this.ExternalID = null;
	this.Categories = null;
	this.CrmCategories = null;
	this.Awards = null;
	this.Locations = null;
	this.IsBrochureInStock = null;
	this.IsInBasket = null;
	this.HasGeoPosition = null;
	this.CreatedDate = null;
	this.AmendedDate = null;
	this.Keywords = null;
	this.Name = null;
	this.MediaTypes = null;
	this.Estates = null;
	this.Polygons = null;
	this.Proximity = null;
	this.CombinedNameKeyIDs = null;
	this.Openings = null;
	this.WidgetID = null;
	this.Facilities = null;
	this.FacilityGroups = null;
	this.MarketLanguages = null;
	this.Gradings = null;
	this.CategoryGroups = null;
	this.SocialMedia = null;
	this.RelatedToProduct = null;
	this.Memberships = null;
	this.MembershipsExtended = null;
	this.DescriptionLanguages = null;
	this.Media = null;
	this.ExternalLinkTypes = null;
	this.DescriptionAutoTranslateLanguages = null;
	this.GroupTravel = null;
	this.BedCount = null;
	this.RoomCount = null;
	this.AvailabilityProviders = null;
	this.HasImageMediaWithNoAltText = null;
	this.SpecialOfferCategories = null;
	this.ShortTermLets = null;
	this.HeaderSort = {
		Header: Headers.Name,
		Direction: SortDirection.Asc
	}
}

// Search filters functions
var numFormat = function (number) {
	return Number(number) === number && number % 1 !== 0
		? parseFloat(number).toFixed(5)
		: number;
};

var dateFormat = function (src) {
	if (src === undefined || src === null)
		return src;
	// Remove all non-numeric (except the plus)
	src = src.replace(/[^0-9 +]/g, '');
	// Create a js date
	var date = new Date(parseInt(src));
	// Format date
	return kendo.toString(date, kendo.culture().calendar.patterns.d);
};

var isEmptyOrWhiteSpace = function (str) {
	return !str || !jQuery.trim(String(str));
};

var isIntegerValid = function (val, minValue, maxValue) {
	var numValue = Number(val);
	return (numValue >= minValue)
		&& (numValue <= maxValue)
		&& /[0-9]+/.test(val);
};

var isLatitudeValid = function (val) {
	var numValue = Number(val);
	return (numValue == val) && (numValue >= -90) && (numValue <= 90);
};
var isLongitudeValid = function (val) {
	var numValue = Number(val);
	return (numValue == val) && (numValue >= -180) && (numValue <= 180);
};

var attachKOfunctions = function (searchViewModel) {
	var getSelectedOptionText = function (optionsArray, selectedOptionValue) {
		var selectedOptionText = '';
		var selectedOptionArray = NewMind.Array.filter(optionsArray, function (o) { return o.Key == selectedOptionValue; });
		if (selectedOptionArray.length > 0) {
			selectedOptionText = selectedOptionArray[0].Value;
			return selectedOptionText;
		}
	};

	var getMultiSelectedOptionsText = function (optionsArray, selectedOptionsValue, optionsType, bShort) {
		var selectedMultiOptionsText = new Array();
		var selectedMultiOptionsArray = NewMind.Array.filter(optionsArray, function (o) { return NewMind.Array.indexOf(selectedOptionsValue, o.Item1) > -1; });
		if (selectedMultiOptionsArray.length > 0) {
			if (bShort) {
				return selectedMultiOptionsArray.length === 1 ? selectedMultiOptionsArray[0].Item2 : selectedMultiOptionsArray.length + ' ' + optionsType;
			}
			NewMind.Array.forEach(selectedMultiOptionsArray, function (o) { selectedMultiOptionsText.push(o.Item2); });
			return selectedMultiOptionsText.join('\n');
		}
		return '';
	};

	var getMultiSelectedSimpleOptionsText = function (optionsArray, selectedOptionsValue, optionsType, bShort) {
		var selectedMultiOptionsText = new Array();
		var selectedMultiOptionsArray = NewMind.Array.filter(optionsArray, function (o) { return NewMind.Array.indexOf(selectedOptionsValue, o.Key) > -1; });
		if (selectedMultiOptionsArray.length > 0) {
			if (bShort) {
				return selectedMultiOptionsArray.length === 1 ? selectedMultiOptionsArray[0].Value : selectedMultiOptionsArray.length + ' ' + optionsType;
			}
			NewMind.Array.forEach(selectedMultiOptionsArray, function (o) { selectedMultiOptionsText.push(o.Value); });
			return selectedMultiOptionsText.join('\n');
		}
		return '';
	};

	var CompositeSelectedOptionObservable = function (filter) {
		var that = this;

		this.FilterId = filter.Id;
		this.OptionsKey = filter.MultiOptions;
		this.OptionsType = filter.OptionsThree;
		this.OptionsIsType = filter.OptionsTwo;

		this.SelectedOptionKey = ko.observable();
		this.SelectedOptionKey.Summary = function () {
			var selectedOptionText = '';
			if (typeof (that.OptionsKey) !== 'undefined' && that.OptionsKey !== null) {
				var selectedOptionArray = NewMind.Array.filter(that.OptionsKey, function (o) { return o.Item1 == that.SelectedOptionKey(); });
				if (selectedOptionArray.length > 0 && selectedOptionArray[0].Item1 !== 0) {
					selectedOptionText = selectedOptionArray[0].Item2;
				}
			}
			return selectedOptionText;
		};

		this.SelectedOptionType = ko.observable();
		this.SelectedOptionType.Summary = function () {
			return (typeof (that.OptionsType) !== 'undefined' && that.OptionsType !== null)
				? getSelectedOptionText(that.OptionsType, that.SelectedOptionType()) : '';
		};

		this.SelectedOptionIsType = ko.observable();
		this.SelectedOptionIsType.Summary = function () {
			return (typeof (that.OptionsIsType) !== 'undefined' && that.OptionsIsType !== null)
				? getSelectedOptionText(that.OptionsIsType, that.SelectedOptionIsType()) : '';
		};

		this.TypeVisible = ko.computed(function () {
			return that.OptionsType !== null;
		});
		this.IsTypeVisible = ko.computed(function () {
			return that.OptionsIsType !== null;
		});
		this.Summary = ko.computed(function () {
			return that.SelectedOptionKey.Summary() !== ''
				? (that.SelectedOptionKey.Summary() + ', ' + that.SelectedOptionIsType.Summary() + ', ' + that.SelectedOptionType.Summary())
					.replace(/,\s*$/, "") // Remove the last comma and any whitespace after it as SelectedOptionType Summary could be empty
				: '';
		});
	};

	var ChannelCompositeSelectedOptionObservable = function (filter) {
		var that = this;

		this.FilterId = filter.Id;
		this.OptionsKey = filter.MultiOptions;
		this.OptionsListingLevels = filter.OptionsThree;
		this.OptionsIsKey = filter.OptionsTwo;
		this.OptionsDoNotIndex = filter.OptionsFour;

		this.SelectedOptionKey = ko.observable();
		this.SelectedOptionKey.Summary = function () {
			var selectedOptionText = '';
			if (typeof (that.OptionsKey) !== 'undefined' && that.OptionsKey !== null) {
				var selectedOptionArray = NewMind.Array.filter(that.OptionsKey, function (o) { return o.Item1 == that.SelectedOptionKey(); });
				if (selectedOptionArray.length > 0 && selectedOptionArray[0].Item1 !== '0') {
					selectedOptionText = selectedOptionArray[0].Item2;
				}
			}
			return selectedOptionText;
		};

		this.SelectedListingLevel = ko.observable();
		this.SelectedListingLevel.Summary = function () {
			return (typeof (that.OptionsListingLevels) !== 'undefined' && that.OptionsListingLevels !== null)
				? getSelectedOptionText(that.OptionsListingLevels, that.SelectedListingLevel()) : '';
		};

		this.SelectedIsKey = ko.observable();
		this.SelectedIsKey.Summary = function () {
			return (typeof (that.OptionsIsKey) !== 'undefined' && that.OptionsIsKey !== null && !isEmptyOrWhiteSpace(that.SelectedIsKey()))
				? getSelectedOptionText(that.OptionsIsKey, that.SelectedIsKey()) : '';
		};

		this.SelectedDoNotIndex = ko.observable();
		this.SelectedDoNotIndex.Summary = function () {
			return (typeof (that.OptionsDoNotIndex) !== 'undefined' && that.OptionsDoNotIndex !== null && !isEmptyOrWhiteSpace(that.SelectedDoNotIndex()))
				? getSelectedOptionText(that.OptionsDoNotIndex, that.SelectedDoNotIndex()) : '';
		};

		this.ListingLevelVisible = ko.computed(function () {
			return that.OptionsListingLevels !== null;
		});

		this.IsKeyVisible = ko.computed(function () {
			return that.OptionsIsKey !== null;
		});

		this.IsDoNotIndexVisible = ko.computed(function () {
			var selectedOptionArray = NewMind.Array.filter(that.OptionsKey, function (o) { return o.Item1 == that.SelectedOptionKey(); });
			var selectedChannelAllowsDoNotIndex = false;
			if (selectedOptionArray.length > 0) {
				if (selectedOptionArray[0].Item3 === "1")
					selectedChannelAllowsDoNotIndex = true;
			}

			return that.OptionsDoNotIndex !== null && selectedChannelAllowsDoNotIndex;
		});

		this.Summary = ko.computed(function () {

			if (that.SelectedOptionKey.Summary() === '')
				return '';

			var summary = that.SelectedOptionKey.Summary() + ', ' + that.SelectedListingLevel.Summary();

			var isKeySummary = that.SelectedIsKey.Summary();
			if (!isEmptyOrWhiteSpace(isKeySummary))
				summary += ', ' + isKeySummary;

			var doNotIndexSummary = that.SelectedDoNotIndex.Summary();
			if (!isEmptyOrWhiteSpace(doNotIndexSummary))
				summary += ', ' + doNotIndexSummary;

			return summary.replace(/,\s*$/, ""); // Remove the last comma and any whitespace after it as SelectedOptionType Summary could be empty
		});
	};

	var MembershipCompositeSelectedOptionObservable = function (filter) {
		var that = this;

		this.FilterId = filter.Id;
		this.OptionsKey = filter.MultiOptions;
		this.OptionsPaymentStatus = filter.OptionsTwo;
		this.OptionsSaleStatus = filter.OptionsThree;
		this.OptionsSaleType = filter.OptionsFour;
		this.OptionsPaymentMethod = filter.OptionsFive;
		this.OptionsScheme = filter.OptionsSix;

		this.SelectedOptionKey = ko.observable();
		this.SelectedOptionKey.Summary = function () {
			var selectedOptionText = '';
			if (typeof (that.OptionsKey) !== 'undefined' && that.OptionsKey !== null && that.SelectedOptionKey() !== '0') {
				var selectedOptionArray = NewMind.Array.filter(that.OptionsKey, function (o) { return o.Item1 == that.SelectedOptionKey(); });
				if (selectedOptionArray.length > 0 && selectedOptionArray[0].Item1 !== 0) {
					selectedOptionText = selectedOptionArray[0].Item2;
				}
			}
			return selectedOptionText;
		};

		this.SelectedPaymentStatus = ko.observable();
		this.SelectedPaymentStatus.Summary = function () {
			return (typeof (that.OptionsPaymentStatus) !== 'undefined' && that.OptionsPaymentStatus !== null)
				? getSelectedOptionText(that.OptionsPaymentStatus, that.SelectedPaymentStatus()) : '';
		};

		this.SelectedSaleStatus = ko.observable();
		this.SelectedSaleStatus.Summary = function () {
			return (typeof (that.OptionsSaleStatus) !== 'undefined' && that.OptionsSaleStatus !== null)
				? getSelectedOptionText(that.OptionsSaleStatus, that.SelectedSaleStatus()) : '';
		};

		this.SelectedSaleType = ko.observable();
		this.SelectedSaleType.Summary = function () {
			return (typeof (that.OptionsSaleType) !== 'undefined' && that.OptionsSaleType !== null)
				? getSelectedOptionText(that.OptionsSaleType, that.SelectedSaleType()) : '';
		};

		this.SelectedPaymentMethod = ko.observable();
		this.SelectedPaymentMethod.Summary = function () {
			return (typeof (that.OptionsPaymentMethod) !== 'undefined' && that.OptionsPaymentMethod !== null)
				? getSelectedOptionText(that.OptionsPaymentMethod, that.SelectedPaymentMethod()) : '';
		};

		this.SelectedScheme = ko.observable();
		this.SelectedScheme.Summary = function () {
			return (typeof (that.OptionsScheme) !== 'undefined' && that.OptionsScheme !== null)
				? getSelectedOptionText(that.OptionsScheme, that.SelectedScheme()) : '';
		};

		this.filterByScheme = function (schemeKey) {
			var filteredOptions = ko.utils.arrayFilter(filter.MultiOptions, function (item) { return item.Item3 === schemeKey; });
			return NewMind.Array.map(filteredOptions, function (_) { { return JSON.parse(JSON.stringify(_)); } });
		};

		this.SchemesWithMemberships = function () {
			var schemes = [];
			NewMind.Array.forEach(filter.OptionsSix, function (scheme) {
				schemes.push({ Key: scheme.Key, Value: scheme.Value, Memberships: that.filterByScheme(scheme.Key) });
			});
			return schemes;
		}();

		this.OptionsKeyFiltered = ko.computed(function () {
			var selectedScheme = that.SelectedScheme();
			if (typeof (selectedScheme) === "undefined" || selectedScheme === "0")
				return filter.MultiOptions;

			return that.filterByScheme(selectedScheme);
		});

		this.Summary = ko.computed(function () {
			return that.SelectedOptionKey.Summary() !== ''
				? (that.SelectedOptionKey.Summary() + ', ' + that.SelectedPaymentStatus.Summary() + ', ' + that.SelectedSaleStatus.Summary() + ', ' + that.SelectedSaleType.Summary() + ', ' + that.SelectedPaymentMethod.Summary())
					.replace(/,\s*$/, "") // Remove the last comma and any whitespace after it as SelectedOptionType Summary could be empty
				: '';
		});
	};

	var FilterOptionObservable = function (o) {
		var self = this;
		self.Key = o.Key;
		self.Value = o.Value;
	};

	var FilterObservable = function (f) {
		var that = this;

		this.Filter = f;

		this.FilterOptions = new ko.observableArray();
		NewMind.Array.forEach(f.Options, function (o) {
			that.FilterOptions.push(new FilterOptionObservable(o));
		});

		if (f.Exclusions != null) {
			var exclusions = new FilterObservable(f.Exclusions);
			this.Exclusions = ko.observable(exclusions);
		}

		this.addOptionClass = function (optionElement) { return; };
		if (f.Id === 'ChannelsExtended' || f.Id === 'ChannelsExtendedExclusions' || f.Id === 'MembershipsExtended' || f.Id === 'MembershipsExtendedExclusions') {
			this.OptionsName = ko.observable(f.Id);
			this.SelectedOption = ko.observable(that.FilterOptions()[0].Key);

			if (f.Id === 'ChannelsExtended' || f.Id === 'ChannelsExtendedExclusions') {
				this.addOptionClass = function (optionElement) {
					if (optionElement.value === "-1") {
						optionElement.classList.add("websiteChannels");
					}
				};
			}
		}
		else {
			this.SelectedOption = ko.observable();
		}

		this.MultiOptionsHasCompositeKey = f.MultiOptionsHasCompositeKey;


		this.SelectedOption.Summary = function () {
			if (typeof (that.Filter.Options) !== 'undefined' && that.Filter.Options !== null) {
				return getSelectedOptionText(that.Filter.Options, that.SelectedOption());
			}
		};

		if (this.Filter.OptionsTwoDefaultValue != null) {
			this.SelectedOptionTwo = ko.observable(this.Filter.OptionsTwoDefaultValue);
		}
		else {
			this.SelectedOptionTwo = ko.observable();
		}

		this.SelectedOptionTwo.Summary = function () {
			if (typeof (that.Filter.OptionsTwo) !== 'undefined' && that.Filter.OptionsTwo !== null) {
				return getSelectedOptionText(that.Filter.OptionsTwo, that.SelectedOptionTwo());
			}
		};

		if (this.Filter.OptionsThreeDefaultValue != null) {
			this.SelectedOptionThree = ko.observable(this.Filter.OptionsThreeDefaultValue);
		}
		else {
			this.SelectedOptionThree = ko.observable();
		}

		this.SelectedOptionThree.Summary = function () {
			if (typeof (that.Filter.OptionsThree) !== 'undefined' && that.Filter.OptionsThree !== null) {
				return getSelectedOptionText(that.Filter.OptionsThree, that.SelectedOptionThree());
			}
		};

		if (this.Filter.OptionsFourDefaultValue != null) {
			this.SelectedOptionFour = ko.observable(this.Filter.OptionsFourDefaultValue);
		}
		else {
			this.SelectedOptionFour = ko.observable();
		}

		this.SelectedOptionFour.Summary = function () {
			if (typeof (that.Filter.OptionsFour) !== 'undefined' && that.Filter.OptionsFour !== null) {
				return getSelectedOptionText(that.Filter.OptionsFour, that.SelectedOptionFour());
			}
		};

		this.DateFrom = ko.observable();
		this.DateFrom.HasValue = ko.computed(function () { return !isEmptyOrWhiteSpace(that.DateFrom()); }, this);
		this.DateFrom.IsValid = ko.computed(function () { return !that.DateFrom.HasValue() || kendo.parseDate(that.DateFrom()) != null; }, this);

		this.DateTo = ko.observable();
		this.DateTo.HasValue = ko.computed(function () { return !isEmptyOrWhiteSpace(that.DateTo()); }, this);
		this.DateTo.IsValid = ko.computed(function () { return !that.DateTo.HasValue() || kendo.parseDate(that.DateTo()) != null; }, this);

		this.RelativeDatesSelected = ko.observable("false");

		if (this.Filter.TextBoxDefaultValue != null) {
			this.TextBoxValue = ko.observable(this.Filter.TextBoxDefaultValue);
		}
		else {
			this.TextBoxValue = ko.observable();
		}

		var minimumRangeValue = 1;
		var maximumRangeValue = 20000000;

		this.MinimumValue = ko.observable();
		this.MinimumValue.HasValue = ko.computed(function () { return !isEmptyOrWhiteSpace(that.MinimumValue()); }, this);
		this.MinimumValue.IsValid = ko.computed(function () { return !that.MinimumValue.HasValue() || isIntegerValid(that.MinimumValue(), minimumRangeValue, maximumRangeValue); }, this);

		this.MaximumValue = ko.observable();
		this.MaximumValue.HasValue = ko.computed(function () { return !isEmptyOrWhiteSpace(that.MaximumValue()); }, this);
		this.MaximumValue.IsValid = ko.computed(function () { return !that.MaximumValue.HasValue() || isIntegerValid(that.MaximumValue(), minimumRangeValue, maximumRangeValue); }, this);

		this.TextBoxValue.HasValue = ko.computed(function () { return !isEmptyOrWhiteSpace(that.TextBoxValue()); }, this);
		this.TextBoxValue.IsValid = ko.computed(function () {
			if (that.Filter.Id === 'Openings') {
				if (that.RelativeDatesSelected() === "false")
					return true;

				var regex = /^[0-9]*$/;
				return that.TextBoxValue.HasValue() && regex.test(that.TextBoxValue());
			}
			else if (that.Filter.Id === 'Media') {
				return that.TextBoxValue.HasValue() && !isNaN(that.TextBoxValue());
			}
			else

				return true;
		}, this);

		this.TextBoxValueTwo = ko.observable();
		this.TextBoxValueThree = ko.observable();

		this.IsPostcodeProximitySelected = ko.computed(function () { return this.SelectedOptionTwo() === 'POSTCODE'; }, this);
		this.IsMapProximitySelected = ko.computed(function () { return this.SelectedOptionTwo() === 'MAPPOINT'; }, this);
		this.IsLatLongProximitySelected = ko.computed(function () { return this.SelectedOptionTwo() === 'LATLONG'; }, this);
		this.IsLatLongProximityValid = ko.computed(function () {
			return (isEmptyOrWhiteSpace(that.TextBoxValueTwo()) && isEmptyOrWhiteSpace(that.TextBoxValueThree()))
				|| (!isEmptyOrWhiteSpace(that.TextBoxValueTwo()) && !isEmptyOrWhiteSpace(that.TextBoxValueThree()) && isLatitudeValid(that.TextBoxValueTwo()) && isLongitudeValid(that.TextBoxValueThree()));
		}, this);

		this.SelectedOptions = ko.observableArray();
		this.SelectedOptions.Summary = function (args) {
			args = args || {};
			var bShort = args.short || false;
			if (typeof (that.Filter.MultiOptions) !== 'undefined' && that.Filter.MultiOptions !== null)
				return getMultiSelectedOptionsText(that.Filter.MultiOptions, that.SelectedOptions(), that.Filter.Name, bShort);
			else if (typeof (that.Filter.Options) !== 'undefined' && that.Filter.Options !== null)
				return getMultiSelectedSimpleOptionsText(that.Filter.Options, that.SelectedOptions(), that.Filter.Name, bShort);
			else
				return '';

		};

		this.CompositeSelectedOptions = ko.observableArray([ko.observable(new CompositeSelectedOptionObservable(this.Filter))]);
		this.CompositeSelectedOptions.Summary = function (args) {
			args = args || {};
			var bShort = args.short || false;
			if (typeof (that.CompositeSelectedOptions()) !== 'undefined') {
				var selectedMultiOptionsArray = that.CompositeSelectedOptions();
				if (selectedMultiOptionsArray.length > 0) {
					var selectedMultiOptionsText = new Array();
					for (var i = 0; i < selectedMultiOptionsArray.length; i++) {
						var compositeSelectedOptionSummary = that.CompositeSelectedOptions()[i]().Summary();
						if (compositeSelectedOptionSummary !== '')
							selectedMultiOptionsText.push(compositeSelectedOptionSummary);
					}
					if (that.SelectedOption() !== Conjugations.NotAny && selectedMultiOptionsText.length > 0) {
						if (bShort) {
							return selectedMultiOptionsText.length === 1 ? selectedMultiOptionsText[0] : selectedMultiOptionsText.length + ' ' + that.Filter.Name;
						}
						return selectedMultiOptionsText.join(';\n') + ';';
					}
				}
			}
			return '';
		};

		this.MembershipCompositeSelectedOptions = ko.observableArray([ko.observable(new MembershipCompositeSelectedOptionObservable(this.Filter))]);
		this.MembershipCompositeSelectedOptions.Summary = function (args) {
			args = args || {};
			var bShort = args.short || false;
			if (typeof (that.MembershipCompositeSelectedOptions()) !== 'undefined') {
				var selectedMultiOptionsArray = that.MembershipCompositeSelectedOptions();
				if (selectedMultiOptionsArray.length > 0) {
					var selectedMultiOptionsText = new Array();
					for (var i = 0; i < selectedMultiOptionsArray.length; i++) {
						var compositeSelectedOptionSummary = that.MembershipCompositeSelectedOptions()[i]().Summary();
						if (compositeSelectedOptionSummary !== '')
							selectedMultiOptionsText.push(compositeSelectedOptionSummary);
					}
					if (selectedMultiOptionsText.length > 0) {
						if (bShort) {
							return selectedMultiOptionsText.length === 1 ? selectedMultiOptionsText[0] : selectedMultiOptionsText.length + ' ' + that.Filter.Name;
						}
						return selectedMultiOptionsText.join(';\n') + ';';
					}
				}
			}
			return '';
		};

		this.ChannelCompositeSelectedOptions = ko.observableArray([ko.observable(new ChannelCompositeSelectedOptionObservable(this.Filter))]);
		this.ChannelCompositeSelectedOptions.Summary = function (args) {
			args = args || {};
			var bShort = args.short || false;
			if (typeof (that.ChannelCompositeSelectedOptions()) !== 'undefined') {
				var selectedMultiOptionsArray = that.ChannelCompositeSelectedOptions();
				if (selectedMultiOptionsArray.length > 0) {
					var selectedMultiOptionsText = new Array();
					for (var i = 0; i < selectedMultiOptionsArray.length; i++) {
						var compositeSelectedOptionSummary = that.ChannelCompositeSelectedOptions()[i]().Summary();
						if (compositeSelectedOptionSummary !== '')
							selectedMultiOptionsText.push(compositeSelectedOptionSummary);
					}
					if (selectedMultiOptionsText.length > 0) {
						if (bShort) {
							return selectedMultiOptionsText.length === 1 ? selectedMultiOptionsText[0] : selectedMultiOptionsText.length + ' ' + that.Filter.Name;
						}
						return selectedMultiOptionsText.join(';\n') + ';';
					}
				}
			}
			return '';
		};

		this.updateDateRanges = function () {
			var startDate = new Date();
			if (that.SelectedOptionTwo() === "0")
				startDate = new Date();
			else if (that.SelectedOptionTwo() === "1")
				startDate = startDate.addDays(1);
			else if (that.SelectedOptionTwo() === "2") {
				// monday is day '1' in JS
				var daysUntilMonday = (1 - startDate.getDay() + 7) % 7;
				startDate = startDate.addDays(daysUntilMonday);
			}
			else if (that.SelectedOptionTwo() === "3") {
				var nextMonth = new Date();
				nextMonth.setMonth(startDate.getMonth() + 1);
				startDate = new Date(nextMonth.getFullYear(), nextMonth.getMonth(), 1);
			}


			that.DateFrom(kendo.toString(startDate, kendo.culture().calendar.patterns.d));

			var interval = that.TextBoxValue();
			var endDate = startDate;
			if (interval !== undefined && interval !== "") {
				if (that.SelectedOptionFour() === "0")
					endDate = endDate.addDays(parseInt(interval));
				else if (that.SelectedOptionFour() === "1")
					endDate = endDate.addDays((parseInt(interval) * 7));
				else if (that.SelectedOptionFour() === "2")
					endDate.setMonth(endDate.getMonth() + parseInt(interval));
			}

			that.DateTo(kendo.toString(endDate, kendo.culture().calendar.patterns.d));
		};

		this.updateDateTo = function () {
			if (!isEmptyOrWhiteSpace(that.DateTo()))
				return;

			that.DateTo(that.DateTo());
		};

		this.updateRelativeDates = function (value) {
			if (!firstLoad)
				that.RelativeDatesSelected(value);
		};

		this.updateDatesFromIntervalsMappings = function (preserveAbsoluteDates) {
								
			var absoluteDatesSelected = that.SelectedOptionTwo() === absoluteDatesIntervalValue;
			that.RelativeDatesSelected(!absoluteDatesSelected);
			if (absoluteDatesSelected) {
				if (!preserveAbsoluteDates) {
					// Reset From and To dates
					that.DateFrom(null);
					that.DateTo(null);
				}
			}
			else
			{
				var startDate = searchViewModel.GetStartDateFromIntervalsMappings(that.SelectedOptionTwo());
				var endDate = searchViewModel.GetEndDateFromIntervalsMappings(that.SelectedOptionTwo());

				that.DateFrom(kendo.toString(startDate, kendo.culture().calendar.patterns.d));
				that.DateTo(kendo.toString(endDate, kendo.culture().calendar.patterns.d));
			}			
			searchViewModel.ToggleEnabledForDateRangeDatePickers(absoluteDatesSelected);
		};

		this.addChannelMultiSelectOption = function () {
			var emptyRowSelected = false;
			var selectedOptionKeys = [];
			NewMind.Array.forEach(that.ChannelCompositeSelectedOptions(), function (o) {
				var option = o();
				var optionValue = o().SelectedOptionKey();

				if (optionValue === "0")
					emptyRowSelected = true;
				else
					selectedOptionKeys.push(optionValue);
			});

			if (emptyRowSelected)
				return;

			var ms = new ChannelCompositeSelectedOptionObservable(that.Filter);
			that.ChannelCompositeSelectedOptions.push(ko.observable(ms));

			// Enable 'Chosen' on the filter select elements
			destroyChosen('select', '.filterContainer');
			applyChosen('select', '.filterContainer');
		};

		this.addMembershipMultiSelectOption = function () {
			var emptyRowSelected = false;
			var selectedOptionKeys = [];
			NewMind.Array.forEach(that.MembershipCompositeSelectedOptions(), function (o) {
				var optionValue = o().SelectedOptionKey();

				if (optionValue === "0")
					emptyRowSelected = true;
				else
					selectedOptionKeys.push(optionValue);
			});

			if (!emptyRowSelected) {
				var ms = new MembershipCompositeSelectedOptionObservable(that.Filter);
				that.MembershipCompositeSelectedOptions.push(ko.observable(ms));
			}

			// Enable 'Chosen' on the filter select elements
			destroyChosen('select', '.filterContainer');
			applyChosen('select', '.filterContainer');
		};

		this.addMultiSelectOption = function () {
			var emptyRowSelected = false;
			var selectedOptionKeys = [];
			NewMind.Array.forEach(that.CompositeSelectedOptions(), function (o) {
				var optionValue = o().SelectedOptionKey();

				if (optionValue === "0")
					emptyRowSelected = true;
				else
					selectedOptionKeys.push(optionValue);
			});

			if (emptyRowSelected)
				return;

			var ms = new CompositeSelectedOptionObservable(that.Filter);
			that.CompositeSelectedOptions.push(ko.observable(ms));

			// Enable 'Chosen' on the filter select elements
			destroyChosen('select', '.filterContainer');
			applyChosen('select', '.filterContainer');
		};

		this.Proximity_ShowMap = function () {
			$('#modalSelectProximity').modal();
			$("#ifrmMap").attr("src", SearchContext.Urls.MapSearch + "?lat=" + that.TextBoxValueTwo() + "&long=" + that.TextBoxValueThree());

			$('#modalSelectProximity').one('hidden.bs.modal', function () {
				var frameDocument = $('#ifrmMap').contents();
				var latitude = parseFloat($(frameDocument).find('#txtLatitude').val());
				var longitude = parseFloat($(frameDocument).find('#txtLongitude').val());

				$("#ifrmMap").attr("src", "");
				$('#lat').val(latitude);
				$('#long').val(longitude);
				$('#modalSelectProximity').modal('hide').trigger('hide');

				that.TextBoxValueTwo(latitude);
				that.TextBoxValueThree(longitude);
			});
		};

		this.ShowOptions = function () {
			$('#modalFilterOptions').modal();
			// Enable 'Chosen' on the filter select elements
			applyChosen('select', '.filterContainer');
			// Init date pickers
			wireUpDatePickers();
		};

		this.HasOptionsSelected = ko.computed(function () {
			switch (that.Filter.Template) {
				case "CompositeProximity":
					return (that.IsPostcodeProximitySelected() && !isEmptyOrWhiteSpace(that.TextBoxValue()) ||
						(!that.IsPostcodeProximitySelected() && !isEmptyOrWhiteSpace(that.TextBoxValueTwo()) && !isEmptyOrWhiteSpace(that.TextBoxValueThree())));
				case "DateRange":
					return that.DateFrom.HasValue() || that.DateTo.HasValue();
				case "DateRangeWithRelativeDates":
					if (that.RelativeDatesSelected() == "false")
						return that.DateFrom.HasValue() || that.DateTo.HasValue();

					return !isEmptyOrWhiteSpace(that.TextBoxValue());
				case "TextBox":
				case "Hidden":
					return !isEmptyOrWhiteSpace(that.TextBoxValue());
				case "SingleSelectWithTextBox":
					return !isEmptyOrWhiteSpace(that.TextBoxValue());
				case "TwoSingleSelectsWithTextBox":
					return !isEmptyOrWhiteSpace(that.TextBoxValue());
				case "MediaCountMultiSingleSelect":
					return that.SelectedOption() || that.SelectedOptionTwo() || that.SelectedOptionThree() || !isEmptyOrWhiteSpace(that.TextBoxValue());
				case "CompositeMultiSelect":
					return !isEmptyOrWhiteSpace(that.CompositeSelectedOptions.Summary())
						|| that.SelectedOption() === Conjugations.NotAny;
				case "MultiSelect":
				case "MultiSelectSimple":
					return that.SelectedOptions().length > 0
						|| that.SelectedOption() === Conjugations.NotAny
						|| that.SelectedOption() === Conjugations.HasAny;
				case "MultiSelectSingleSelect":
					// Either it's not a "XXXAnyOf" conjugation, or the user has selected options.
					return ((that.SelectedOption() !== Conjugations.HasAnyOf && that.SelectedOption() !== Conjugations.DoesNotHaveAnyOf)
						|| that.SelectedOptions().length > 0
					) && that.SelectedOptionTwo();
				case "SingleSelect":
					return that.SelectedOption() != undefined;
				case "SingleSelectWithOptionalTextBox":
					return that.SelectedOption() || that.SelectedOptionTwo() || !isEmptyOrWhiteSpace(that.TextBoxValue());
				case "MembershipCompositeMultiSelect":
					return !isEmptyOrWhiteSpace(that.MembershipCompositeSelectedOptions.Summary()) || !isEmptyOrWhiteSpace(that.Exclusions().MembershipCompositeSelectedOptions.Summary());
				case "ChannelCompositeMultiSelect":
					return !isEmptyOrWhiteSpace(that.ChannelCompositeSelectedOptions.Summary()) || !isEmptyOrWhiteSpace(that.Exclusions().ChannelCompositeSelectedOptions.Summary());
				case "NumericRange":
					return that.MinimumValue.HasValue() || that.MaximumValue.HasValue();
				case "NumericRangeWithToggle":
					return (that.SelectedOption() === 'false')
						|| that.MinimumValue.HasValue()
						|| that.MaximumValue.HasValue();
				default:
					return true;
			}

		}, this);

		this.Summary = ko.pureComputed(function () {
			switch (that.Filter.Template) {
				case "CompositeProximity":
					if (!that.HasOptionsSelected())
						return '';

					if (that.IsLatLongProximitySelected() && !that.IsLatLongProximityValid())
						return SearchContext.Translations.CoordinatesNotValid;

					var proximityCentre = that.IsPostcodeProximitySelected()
						? that.TextBoxValue()
						: '(' + numFormat(that.TextBoxValueTwo()) + ', ' + numFormat(that.TextBoxValueThree()) + ')';
					return that.SelectedOption.Summary() + ' ' + SearchContext.Translations.Proximity_Of + ' ' + proximityCentre;

				case "DateRange":				
					return !that.HasOptionsSelected() ? ''
						: !that.DateFrom.IsValid() || !that.DateTo.IsValid() ? SearchContext.Translations.InvalidDate
							: !that.DateFrom.HasValue() ? that.SelectedOption.Summary() + ' ' + SearchContext.Translations.DateRange_Before + ' ' + that.DateTo()
								: !that.DateTo.HasValue() ? that.SelectedOption.Summary() + ' ' + SearchContext.Translations.DateRange_After + ' ' + that.DateFrom()
									: that.SelectedOptionTwo() === absoluteDatesIntervalValue 
										? that.SelectedOption.Summary() + ' ' + SearchContext.Translations.DateRange_Between + ' ' + that.DateFrom() + ' ' + SearchContext.Translations.DateRange_And + ' ' + that.DateTo() 
										: that.SelectedOption.Summary() + ' ' + SearchContext.Translations.DateRange_Within + ' ' + that.SelectedOptionTwo.Summary() + ' (' + (that.DateFrom() === that.DateTo() ? that.DateFrom() : that.DateFrom() + ' - ' + that.DateTo()) + ')';
				case "DateRangeWithRelativeDates":
					if (!that.HasOptionsSelected())
						return '';

					if (that.RelativeDatesSelected() == "false") {
						if (!(that.DateFrom.HasValue() || that.DateTo.HasValue()))
							return SearchContext.Translations.InvalidDate;

						return !that.DateFrom.IsValid() || !that.DateTo.IsValid() ? SearchContext.Translations.InvalidDate
							: !that.DateFrom.HasValue() ? that.SelectedOption.Summary() + ' ' + SearchContext.Translations.DateRange_Before + ' ' + that.DateTo()
								: !that.DateTo.HasValue() ? that.SelectedOption.Summary() + ' ' + SearchContext.Translations.DateRange_After + ' ' + that.DateFrom()
									: that.SelectedOption.Summary() + ' ' + SearchContext.Translations.DateRange_Between + ' ' + that.DateFrom() + ' ' + SearchContext.Translations.DateRange_And + ' ' + that.DateTo();
					}

					if (!that.TextBoxValue.IsValid())
						return SearchContext.Translations.InvalidDate;

					return that.SelectedOption.Summary() + ' ' + SearchContext.Translations.DateRange_From + ' ' + that.SelectedOptionTwo.Summary() + ' ' + SearchContext.Translations.DateRange_InTheFollowing + ' ' + that.TextBoxValue() + ' ' + that.SelectedOptionFour.Summary();
				case "TextBox":
					return !that.HasOptionsSelected() ? '' : that.TextBoxValue();
				case "Hidden":
					return !that.HasOptionsSelected() || isNaN(that.TextBoxValue()) ? 'Invalid key' : that.Filter.Name + '\n' + that.TextBoxValue();
				case "SingleSelectWithTextBox":
					return !that.HasOptionsSelected() ? '' : that.SelectedOption.Summary() + '\n' + that.TextBoxValue();
				case "TwoSingleSelectsWithTextBox":
					return !that.HasOptionsSelected() ? '' : that.SelectedOption.Summary() + ' ' + that.TextBoxValue() + ' ' + SearchContext.Translations.In + ' ' + that.SelectedOptionTwo.Summary();
				case "MediaCountMultiSingleSelect":
					return !that.HasOptionsSelected() ? '' : that.SelectedOption.Summary() + ' ' + that.SelectedOptionTwo.Summary() + ' ' + that.SelectedOptionThree.Summary() + ' ' + that.TextBoxValue();
				case "SingleSelectWithOptionalTextBox":
					return !that.HasOptionsSelected() ? '' : that.SelectedOption.Summary() + ' ' + that.SelectedOptionTwo.Summary() + ' ' + that.TextBoxValue();
				case "CompositeMultiSelect":
					return !that.HasOptionsSelected() ? '' : that.SelectedOption.Summary() + '\n' + that.CompositeSelectedOptions.Summary();
				case "MultiSelect":
				case "MultiSelectSimple":
					return !that.HasOptionsSelected() ? '' : that.SelectedOption.Summary() + '\n' + that.SelectedOptions.Summary();
				case "MultiSelectSingleSelect":
					return !that.HasOptionsSelected() ? '' : that.SelectedOption.Summary() + '\n'
						+ that.SelectedOptions.Summary()
						+ ' ' + that.SelectedOptionTwo.Summary();
				case "SingleSelect":
					return !that.HasOptionsSelected() ? '' : that.SelectedOption.Summary();
				case "NumericRange":
				case "NumericRangeWithToggle": {
					if (!that.HasOptionsSelected()) return '';
					var selectedOption = that.SelectedOption();
					var acceptGroupTravelLabel = '';
					var rangeIsRequired = (selectedOption === 'true') || (selectedOption === undefined);
					if (selectedOption !== undefined) {
						var selectedOptionItem = that.Filter.Options.filter(function (option) { return option.Key === selectedOption; })[0];
						if (selectedOptionItem) {
							acceptGroupTravelLabel = selectedOptionItem.Value;
						}
					}
					if (!rangeIsRequired) {
						return acceptGroupTravelLabel;
					}
					var invalidMinimumValue = that.MinimumValue.HasValue() && !that.MinimumValue.IsValid();
					var invalidMaximumValue = that.MaximumValue.HasValue() && !that.MaximumValue.IsValid();
					if (invalidMinimumValue && !invalidMaximumValue) return SearchContext.Translations.InvalidMinimumValue;
					if (!invalidMinimumValue && invalidMaximumValue) return SearchContext.Translations.InvalidMaximumValue;
					if (invalidMinimumValue && invalidMaximumValue) return SearchContext.Translations.InvalidMinimumAndMaximumValues;

					if (that.MinimumValue.HasValue() && !that.MaximumValue.HasValue()) return acceptGroupTravelLabel
						+ ' ' + SearchContext.Translations.WithMinimumOf
						+ ' ' + that.MinimumValue();

					if (!that.MinimumValue.HasValue() && that.MaximumValue.HasValue()) return acceptGroupTravelLabel
						+ ' ' + SearchContext.Translations.WithMaximumOf
						+ ' ' + that.MaximumValue();

					return acceptGroupTravelLabel
						+ ' ' + SearchContext.Translations.Between
						+ ' ' + that.MinimumValue()
						+ ' ' + SearchContext.Translations.And
						+ ' ' + that.MaximumValue();
				}
				case "ChannelCompositeMultiSelect": 
					var channelFilterSummary = '';
					if (!isEmptyOrWhiteSpace(that.ChannelCompositeSelectedOptions.Summary()))
						channelFilterSummary = SearchContext.Translations.PublishedTo + ': ' + that.SelectedOption.Summary() + '\n' + that.ChannelCompositeSelectedOptions.Summary();
					if (!isEmptyOrWhiteSpace(that.Exclusions().ChannelCompositeSelectedOptions.Summary()))
						channelFilterSummary += '\n' + SearchContext.Translations.NotPublishedTo + ': ' + that.Exclusions().SelectedOption.Summary() + '\n' + that.Exclusions().ChannelCompositeSelectedOptions.Summary();
					return channelFilterSummary;
				case "MembershipCompositeMultiSelect":
					var membershipFilterSummary = '';
					if (!isEmptyOrWhiteSpace(that.MembershipCompositeSelectedOptions.Summary()))
						membershipFilterSummary = SearchContext.Translations.PurchasedMembership + ': ' + that.SelectedOption.Summary() + '\n' + that.MembershipCompositeSelectedOptions.Summary();
					if (!isEmptyOrWhiteSpace(that.Exclusions().MembershipCompositeSelectedOptions.Summary()))
						membershipFilterSummary += '\n' + SearchContext.Translations.NotPurchasedMembership + ': ' + that.Exclusions().SelectedOption.Summary() + '\n' + that.Exclusions().MembershipCompositeSelectedOptions.Summary();
					return membershipFilterSummary;
				default:
					return '';
			}

		}, this);

		this.HasError = ko.computed(function () {
			switch (that.Filter.Template) {
				case "CompositeProximity":
					return that.IsLatLongProximitySelected() && !that.IsLatLongProximityValid();
				case "DateRange":
					return !that.DateFrom.IsValid() || !that.DateTo.IsValid();
				case "DateRangeWithRelativeDates":
					if (that.RelativeDatesSelected() == "false")
						return !that.DateFrom.IsValid() || !that.DateTo.IsValid();

					return !that.TextBoxValue.IsValid();
				case "NumericRange":
					return !that.MinimumValue.IsValid() || !that.MaximumValue.IsValid();
				case "NumericRangeWithToggle":
					return that.SelectedOption() === 'true' // Numeric range enabled
						&& (!that.MinimumValue.IsValid() || !that.MaximumValue.IsValid());
				case "Hidden":
					return isNaN(that.TextBoxValue());
				default:
					return false;
			}

		}, this);
	};


	searchViewModel.NewProductLink = function () {
		if (this.SelectedProductType() !== '')
			window.location = SearchContext.Urls.NewProductUrl + "?type=" + this.SelectedProductType();
		else
			window.location = SearchContext.Urls.NewProductUrl;
	};

	searchViewModel.SelectedFilters = ko.observableArray();
	searchViewModel.CurrentSelectedFilter = ko.observable();


	searchViewModel.filterByProductType = function (option, item) {
		if (item.Item3 === null)
			return;

		ko.applyBindingsToNode(option, { attr: { prodtype: item.Item3 }, visible: searchViewModel.SelectedProductType() === '' || item.Item3 === '' || NewMind.Array.indexOf(item.Item3.split(" "), searchViewModel.SelectedProductType()) >= 0 }, item);
	};

	// Header Sorting
	searchViewModel.selectedSortHeader = ko.observable(queryModel.HeaderSort.Header);
	searchViewModel.selectedSortDirection = ko.observable(queryModel.HeaderSort.Direction);

	searchViewModel.UpdateHeaderOrder = function (header) {
		if (searchViewModel.selectedSortHeader() == header) {
			// Swap direction.
			searchViewModel.selectedSortDirection(1 - searchViewModel.selectedSortDirection());
		}
		else {
			searchViewModel.selectedSortDirection(SortDirection.Asc);
		}
		searchViewModel.selectedSortHeader(header);

		queryModel.HeaderSort.Header = searchViewModel.selectedSortHeader();
		queryModel.HeaderSort.Direction = searchViewModel.selectedSortDirection();
		searchViewModel.runSearch()
	}

	searchViewModel.RenderSortDirection = ko.computed(function () {
		if (searchViewModel.selectedSortDirection() == SortDirection.Asc) {
			return '<i class="glyphicon glyphicon-arrow-up"></i>';
		}
		else {
			return '<i class="glyphicon glyphicon-arrow-down"></i>';
		}
	});

	searchViewModel.filterNotApplicable = function (filter) {
		return Object.prototype.toString.call(filter.ApplicableProductTypeIds) === '[object Array]' && NewMind.Array.indexOf(filter.ApplicableProductTypeIds, searchViewModel.SelectedProductType()) < 0;
	};

	searchViewModel.filterSelected = function (filter) {
		var selectedFilter = NewMind.Array.filter(searchViewModel.SelectedFilters(), function (o) { return o().Filter.Id === filter.Id; });
		return selectedFilter.length > 0 && selectedFilter[0]().HasOptionsSelected();
	};

	searchViewModel.filterHasError = function (filter) {
		var selectedFilter = NewMind.Array.filter(searchViewModel.SelectedFilters(), function (o) { return o().Filter.Id === filter.Id; });
		return selectedFilter.length > 0 && selectedFilter[0]().HasError();
	};

	searchViewModel.filterSummary = function (filter) {
		var selectedFilter = NewMind.Array.filter(searchViewModel.SelectedFilters(), function (o) { return o().Filter.Id === filter.Id; });
		return selectedFilter.length > 0 ? selectedFilter[0]().Summary() : '';
	};

	searchViewModel.SelectedProductType = ko.computed(function () {
		var prodTypeFilter = NewMind.Array.filter(searchViewModel.SelectedFilters(), function (o) { return o().Filter.Id === 'TypeIds'; });
		return (prodTypeFilter.length > 0 && prodTypeFilter[0]().SelectedOptions() != undefined && prodTypeFilter[0]().SelectedOptions().length === 1) ? prodTypeFilter[0]().SelectedOptions()[0] : '';
	});

	searchViewModel.SelectedProductTypeName = ko.computed(function () {
		var prodTypeFilter = NewMind.Array.filter(searchViewModel.SelectedFilters(), function (o) { return o().Filter.Id === 'TypeIds'; });
		return (prodTypeFilter.length > 0 && prodTypeFilter[0]().SelectedOptions() != undefined && prodTypeFilter[0]().SelectedOptions().length === 1) ? prodTypeFilter[0]().SelectedOptions.Summary() : '';
	});

	searchViewModel.IsOrganisationSearch = ko.computed(function () {
		return searchViewModel.SelectedProductType() === 'BUSI';
	});

	searchViewModel.IsRelatedToProductSearch = ko.computed(function () {
		var relatedToProductFilter = NewMind.Array.filter(searchViewModel.SelectedFilters(), function (o) { return o().Filter.Id === 'RelatedToProduct'; });
		return (relatedToProductFilter.length > 0 && relatedToProductFilter[0]().HasOptionsSelected());
	});

	searchViewModel.GetSearchTitle = function (searchTitle, orgsTitle, productsTitle) {
		// This string concatonation is bad, but was how it was previously done. Ideally we'd have
		// decent translation titles for each type, but not worth dong unless these genreated ones
		// turn out to be bad.
		return ko.computed(function () {
			if (searchViewModel.IsOrganisationSearch())
				return searchTitle + ' ' + orgsTitle;
			else if (!searchViewModel.SelectedProductType())
				return searchTitle + ' ' + productsTitle;
			else
				return searchViewModel.SelectedProductTypeName() + ' ' + searchTitle;
		});
	};

	searchViewModel.SelectedFiltersCount = ko.computed(function () {
		var filtersWithOptionsSelected = NewMind.Array.filter(searchViewModel.SelectedFilters(), function (o) { return o().HasOptionsSelected(); });
		return searchViewModel.IsOrganisationSearch() ? filtersWithOptionsSelected.length - 1 : filtersWithOptionsSelected.length;
	});

	searchViewModel.HasVisibleSelectedFilters = function () {
		return !searchViewModel.AreFiltersCollapsed() && searchViewModel.SelectedFiltersCount() > 0;
	};

	searchViewModel.addFilter = function (filter) {
		if (typeof (filter) === 'undefined') {
			filter = this;
		}

		var filterObservable = new FilterObservable(filter);
		var selectedFilters = NewMind.Array.filter(searchViewModel.SelectedFilters(), function (o) { return o().Filter.Id === filter.Id; });

		var selectedFilterObservable;
		if (selectedFilters.length > 0) {
			selectedFilterObservable = selectedFilters[0];
		} else {
			selectedFilterObservable = ko.observable(filterObservable);
			searchViewModel.SelectedFilters.push(selectedFilterObservable);
		}
		return selectedFilterObservable();
	};

	searchViewModel.selectFilter = function (filter) {
		var selectedFilterObservable = searchViewModel.addFilter(filter);
		searchViewModel.CurrentSelectedFilter(selectedFilterObservable);
		selectedFilterObservable.ShowOptions();
		searchViewModel.AreFiltersCollapsed(!$('.filters').is(":visible"));
		if (filter.Template === 'DateRange') {
			searchViewModel.ToggleEnabledForDateRangeDatePickers(selectedFilterObservable.SelectedOptionTwo() === absoluteDatesIntervalValue);
		}
	};

	searchViewModel.initialiseFromQueryModel = function () {
		// Set Conjugation
		searchViewModel.Conjugation(queryModel.Conjugation);
		$('.auto-apply-chosen select').trigger('chosen:updated');

		// Set Combined Name Key IDs filter
		if (jQuery.trim(queryModel.CombinedNameKeyIDs)) {
			searchViewModel.CombinedNameKeyIDsValue(queryModel.CombinedNameKeyIDs);
		}

		// Update sorting
		if (queryModel.HeaderSort.Header != searchViewModel.selectedSortHeader() || queryModel.HeaderSort.Direction != searchViewModel.selectedSortDirection())
		{
			searchViewModel.selectedSortHeader(queryModel.HeaderSort.Header);
			searchViewModel.selectedSortDirection(queryModel.HeaderSort.Direction);
		}

		// Iterate though all the filters and initialise those which have a not-null value in the queryModel
		NewMind.Array.forEach(searchViewModel.Filters, function (item) {
			var template = item.Template;
			var id = item.Id;
			value = queryModel[id];
			if (value === null && !((template === 'MembershipCompositeMultiSelect' || template === 'ChannelCompositeMultiSelect') && queryModel[id + 'Exclusions'] != null)) {
				return;
			}
			var selectedFilter = searchViewModel.addFilter(item);
			switch (template) {
				case 'SingleSelect':
					switch (id) {
						case 'StatusId':
						case 'WidgetID':
							selectedFilter.SelectedOption(value);
							break;						
						case 'HasImageMediaWithNoAltText':
							if(value !== "") selectedFilter.SelectedOption(value);
							break;
						default:
							selectedFilter.SelectedOption(value ? 'true' : 'false');
					}
					break;
				case 'TextBox':
				case "Hidden":
					selectedFilter.TextBoxValue(value);
					break;
				case 'SingleSelectWithTextBox':
					selectedFilter.SelectedOption(value.Key);
					selectedFilter.TextBoxValue(value.Text);
					break;
				case 'TwoSingleSelectsWithTextBox':
					selectedFilter.SelectedOption(value.FilterType);
					if (value.TextLang != null) {
						selectedFilter.TextBoxValue(value.TextLang.Text);
						selectedFilter.SelectedOptionTwo(value.TextLang.LanguageId);
					}
					break;
				case 'MediaCountMultiSingleSelect':
					selectedFilter.SelectedOption(value.CountConjugation);
					selectedFilter.SelectedOptionTwo(value.MediaCount);
					selectedFilter.SelectedOptionThree(value.SizeConjugation);
					selectedFilter.TextBoxValue(value.ImageWidthInPixels);

					break;
				case 'SingleSelectWithOptionalTextBox':
					selectedFilter.SelectedOption(value.ExternaIDKey);
					selectedFilter.SelectedOptionTwo(value.ExternalIDSearchType);
					selectedFilter.TextBoxValue(value.Value);
					break;
				case 'CompositeProximity':
					selectedFilter.SelectedOption(value.DistanceInKilometres);
					if (isEmptyOrWhiteSpace(value.PostCode)) {
						selectedFilter.SelectedOptionTwo('LATLONG');
						selectedFilter.TextBoxValueTwo(value.Latitude);
						selectedFilter.TextBoxValueThree(value.Longitude);
					} else {
						selectedFilter.SelectedOptionTwo('POSTCODE');
						selectedFilter.TextBoxValue(value.PostCode);
					}
					break;
				case 'DateRange':
					selectedFilter.SelectedOption(value.InRange ? 'true' : 'false');
					selectedFilter.SelectedOptionTwo(value.Interval !== null ? value.Interval.toString() : selectedFilter.Filter.OptionsTwoDefaultValue);
					selectedFilter.DateFrom(dateFormat(value.From));
					selectedFilter.DateTo(dateFormat(value.To));
					selectedFilter.updateDatesFromIntervalsMappings(true);					
					break;
				case 'DateRangeWithRelativeDates':
					selectedFilter.RelativeDatesSelected(value.RelativeDates ? 'true' : 'false');
					selectedFilter.SelectedOption(value.InRange ? 'true' : 'false');
					selectedFilter.DateFrom(dateFormat(value.From));
					selectedFilter.DateTo(dateFormat(value.To));
					selectedFilter.SelectedOptionTwo(value.Interval == null ? selectedFilter.Filter.OptionsFourDefaultValue : value.Interval.toString());
					selectedFilter.TextBoxValue(value.PeriodValue == null ? selectedFilter.Filter.TextBoxDefaultValue : value.PeriodValue.toString());
					selectedFilter.SelectedOptionFour(value.Period == null ? selectedFilter.Filter.OptionsFourDefaultValue : value.Period.toString());
					break;
				case 'MultiSelectSimple':
					selectedFilter.SelectedOptions(value.Options);
					break;
				case 'MultiSelect':
				case 'CompositeMultiSelect': {
					var arrayOptionValues = new Array();
					NewMind.Array.forEach(value.Options, function (option) {
						if (template === 'MultiSelect') {
							arrayOptionValues.push(option.Key);
						}
						else {
							var compositeSelectedOption = new CompositeSelectedOptionObservable(item);
							compositeSelectedOption.SelectedOptionKey(option.Key);
							compositeSelectedOption.SelectedOptionType(option.TypeKey);
							compositeSelectedOption.SelectedOptionIsType(option.IsType == null ? '' : option.IsType.toString());
							arrayOptionValues.push(ko.observable(compositeSelectedOption));
						}
					});
					if (template === 'MultiSelect') {
						selectedFilter.SelectedOptions(arrayOptionValues);
					}
					else {
						// Add an empty row 
						arrayOptionValues.push(ko.observable(new CompositeSelectedOptionObservable(item)));
						selectedFilter.CompositeSelectedOptions(arrayOptionValues);
					}
					selectedFilter.SelectedOption(value.FilterType);
					break;
				}
				case "MultiSelectSingleSelect":
					selectedFilter.SelectedOption(value.FilterType);

					var arrayOptionValues = value.Options
						? value.Options.map(function (option) { return option.Key; })
						: [];

					selectedFilter.SelectedOptions(arrayOptionValues);

					selectedFilter.SelectedOptionTwo(value.Option);

					break;
				case 'MembershipCompositeMultiSelect': {
					var getOptionValues = function (v, f) {
						var options = new Array();
						NewMind.Array.forEach(v.Options, function (option) {
							var compositeSelectedOption = new MembershipCompositeSelectedOptionObservable(f);
							compositeSelectedOption.SelectedOptionKey((option.Key || 0).toString());
							compositeSelectedOption.SelectedPaymentStatus(option.PaymentStatus == null ? '' : option.PaymentStatus.toString());
							compositeSelectedOption.SelectedSaleStatus(option.SaleStatus || 0);
							compositeSelectedOption.SelectedSaleType((option.SaleType || 0).toString());
							compositeSelectedOption.SelectedPaymentMethod((option.PaymentMethod || 0).toString());
							compositeSelectedOption.SelectedScheme((option.Scheme || 0).toString());

							options.push(ko.observable(compositeSelectedOption));
						});
						options.push(ko.observable(new MembershipCompositeSelectedOptionObservable(f)));
						return options;
					};
					if (value !== null) {
						var optionValues = getOptionValues(value, item);
						selectedFilter.MembershipCompositeSelectedOptions(optionValues);
						selectedFilter.SelectedOption(value.FilterType.toString());
					}
					var exclusions = queryModel[id + 'Exclusions'];
					if (exclusions != null) {
						optionValues = getOptionValues(exclusions, item);
						selectedFilter.Exclusions().MembershipCompositeSelectedOptions(optionValues);
						selectedFilter.Exclusions().SelectedOption(exclusions.FilterType.toString());
					}
					break;
				}
				case 'ChannelCompositeMultiSelect': {
					var getOptionValues = function (v, f) {
						var options = new Array();
						NewMind.Array.forEach(v.Options, function (option) {
							var compositeSelectedOption = new ChannelCompositeSelectedOptionObservable(f);
							compositeSelectedOption.SelectedOptionKey(option.Key);
							compositeSelectedOption.SelectedListingLevel(option.ListingLevelKey);
							compositeSelectedOption.SelectedIsKey(option.IsKey == null ? '' : option.IsKey.toString());
							compositeSelectedOption.SelectedDoNotIndex(option.DoNotIndex == null ? '' : option.DoNotIndex.toString());
							options.push(ko.observable(compositeSelectedOption));
						});
						options.push(ko.observable(new ChannelCompositeSelectedOptionObservable(f)));
						return options;
					};
					if (value !== null) {
						var optionValues = getOptionValues(value, item);
						selectedFilter.ChannelCompositeSelectedOptions(optionValues);
						selectedFilter.SelectedOption(value.FilterType.toString());
					}
					var exclusions = queryModel[id + 'Exclusions'];
					if (exclusions != null) {
						optionValues = getOptionValues(exclusions, item);
						selectedFilter.Exclusions().ChannelCompositeSelectedOptions(optionValues);
						selectedFilter.Exclusions().SelectedOption(exclusions.FilterType.toString());
					}
					break;
				}
				case 'NumericRange':
					selectedFilter.MinimumValue(value.Min);
					selectedFilter.MaximumValue(value.Max);
					break;
				case 'NumericRangeWithToggle':
					selectedFilter.SelectedOption(value.HasGroupTravel.toString());
					if (value.Range) {
						selectedFilter.MinimumValue(value.Range.Min);
						selectedFilter.MaximumValue(value.Range.Max);
					}
					break;
				default:
					if (window.console) {
						console.log('Unsupported Template: ' + template);
					}
			}
		});
	};

	searchViewModel.removeFilter = function () {
		var that = this;
		searchViewModel.SelectedFilters.remove(function (item) { return item().Filter.Id == that.Id; });
	};

	searchViewModel.buildSearchQuery = function () {
		var hasFilters = false;
		var thisQueryModel = new ProductSearchQuery();

		// Check if we have a value for the CombinedNameKeyIDs filter
		var combinedNameKeyIDsValue = jQuery.trim(searchViewModel.CombinedNameKeyIDsValue());
		if (combinedNameKeyIDsValue.length > 0) {
			thisQueryModel.CombinedNameKeyIDs = combinedNameKeyIDsValue;
			hasFilters = true;
		}

		if (thisQueryModel.HeaderSort != queryModel.HeaderSort)
		{
			thisQueryModel.HeaderSort = queryModel.HeaderSort;
			hasFilters = true;
		}

		NewMind.Array.forEach(searchViewModel.SelectedFilters(), function (item) {
			if (!item().HasOptionsSelected()) {
				return;
			}

			var template = item().Filter.Template;
			var id = item().Filter.Id;

			var buildMultiSelectQueryPart = function (filter) {
				var multiSelectQueryPart = function (type, values, hasCompositeKey) {
					this.FilterType = isNaN(type) ? 1 : parseInt(type);
					this.Options = (values instanceof Array) ? values : null;
					this.MultiOptionsHasCompositeKey = hasCompositeKey;
				};

				var multiSelectSingleSelectQueryPart = function (type, values, option) {
					this.FilterType = isNaN(type) ? 1 : parseInt(type);

					if ((this.FilterType.toString() === Conjugations.HasAnyOf || this.FilterType.toString() === Conjugations.DoesNotHaveAnyOf)
						&& values instanceof Array) {
						this.Options = values;
					} else {
						this.Options = null;
					}

					if (this.FilterType.toString() === Conjugations.DoesNotHaveAnyOf || this.FilterType.toString() === Conjugations.NotAny) {
						this.Option = parseInt(Validity.Any);
					} else {
						this.Option = isNaN(option) ? Validity.Any : parseInt(option);
					}
				};

				var multiSelectStringQueryPart = function (type, values) {
					this.FilterType = isNaN(type) ? 1 : parseInt(type);
					this.Options = (values instanceof Array) ? values : null;
				};

				var multiSelectQueryDetail = function (val) {
					this.Key = val;
				};

				var compositeMultiSelectQueryDetail = function (val) {
					var keyVal = val().SelectedOptionKey();
					var typeKeyVal = val().SelectedOptionType();
					var isTypeVal = val().SelectedOptionIsType();

					this.Key = keyVal;
					this.TypeKey = isNaN(typeKeyVal) ? null : parseInt(typeKeyVal);
					this.IsType = jQuery.trim(isTypeVal) === '' ? null : isTypeVal === 'true';
				};

				var channelCompositeMultiSelectQueryDetail = function (val) {
					var keyVal = val().SelectedOptionKey();
					var listingLevelVal = val().SelectedListingLevel();
					var isKeyVal = val().SelectedIsKey();
					var doNotIndexVal = val().SelectedDoNotIndex();

					this.Key = keyVal;
					this.ListingLevelKey = isNaN(listingLevelVal) ? null : parseInt(listingLevelVal);
					this.IsKey = jQuery.trim(isKeyVal) === '' ? null : isKeyVal === 'true';
					this.DoNotIndex = jQuery.trim(doNotIndexVal) === '' ? null : doNotIndexVal === 'true';
				};

				var membershipCompositeMultiSelectQueryDetail = function (val) {
					this.Key = val().SelectedOptionKey();
					this.PaymentStatus = val().SelectedPaymentStatus();
					this.SaleStatus = val().SelectedSaleStatus();
					this.SaleType = val().SelectedSaleType();
					this.PaymentMethod = val().SelectedPaymentMethod();
					this.Scheme = val().SelectedScheme();
				};

				var values;
				if (template === 'MultiSelect'
					|| template === 'MultiSelectSimple'
					|| template === 'MultiSelectSingleSelect') {
					values = filter.SelectedOptions();
				} else if (template === 'MembershipCompositeMultiSelect') {
					values = filter.MembershipCompositeSelectedOptions();
				} else if (template === 'ChannelCompositeMultiSelect') {
					values = filter.ChannelCompositeSelectedOptions();
				} else {
					values = filter.CompositeSelectedOptions();
				}

				var queryPart;
				var groupingType = filter.SelectedOption();
				if (values.length > 0) {
					var array = new Array();

					NewMind.Array.forEach(values, function (value) {
						var thisValue = value;
						var option;

						if ((template === 'MultiSelect' || template === 'MultiSelectSingleSelect') && thisValue > 0)
							option = new multiSelectQueryDetail(thisValue);
						else if ((template === 'MultiSelectSimple' && thisValue !== ""))
							option = thisValue;
						else if (template === 'MembershipCompositeMultiSelect' && (thisValue().SelectedOptionKey() != "0" || thisValue().SelectedOptionKey() == "-1")) // we need to allow "-1" for Channels Filter Option "My Website Channels"
							option = new membershipCompositeMultiSelectQueryDetail(thisValue);
						else if (template === 'ChannelCompositeMultiSelect' && (thisValue().SelectedOptionKey() != "0" || thisValue().SelectedOptionKey() == "-1")) // we need to allow "-1" for Channels Filter Option "My Website Channels"
							option = new channelCompositeMultiSelectQueryDetail(thisValue);
						else if (thisValue().SelectedOptionKey() != "0" || thisValue().SelectedOptionKey() == "-1") // we need to allow "-1" for Channels Filter Option "My Website Channels"
							option = new compositeMultiSelectQueryDetail(thisValue);

						if (typeof (option) !== 'undefined')
							array.push(option);
					});

					if (template === 'MultiSelectSimple')
						queryPart = new multiSelectStringQueryPart(groupingType, array);
					else if (template === 'MultiSelectSingleSelect') {
						var option = filter.SelectedOptionTwo();

						queryPart = new multiSelectSingleSelectQueryPart(groupingType, array, option);
					}
					else
						queryPart = new multiSelectQueryPart(groupingType, array, filter.MultiOptionsHasCompositeKey);
				} else if (groupingType === Conjugations.NotAny || groupingType === Conjugations.HasAny) {
					if (template === 'MultiSelectSingleSelect') {
						var option = filter.SelectedOptionTwo();

						queryPart = new multiSelectSingleSelectQueryPart(groupingType, null, option);
					}
					else
						queryPart = new multiSelectQueryPart(groupingType, null, filter.MultiOptionsHasCompositeKey);
				}
				return queryPart;
			};

			switch (template) {
				case 'TextBox':
				case "Hidden": {
					var value = jQuery.trim(item().TextBoxValue());
					// Ignore empty values
					if (value.length > 0) {
						thisQueryModel[id] = value;
						hasFilters = true;
					}
					break;
				}
				case 'SingleSelectWithTextBox': {
					var textValue = jQuery.trim(item().TextBoxValue());
					var optionValue = item().SelectedOption();
					// Ignore empty values
					if (textValue.length > 0 && !isNaN(optionValue)) {
						var SingleSelectWithTextBoxQueryPart = function (key, val) { this.Key = parseInt(key); this.Text = val; };
						thisQueryModel[id] = new SingleSelectWithTextBoxQueryPart(optionValue, textValue);
						hasFilters = true;
					}
					break;
				}
				case 'TwoSingleSelectsWithTextBox': {
					var textValue = jQuery.trim(item().TextBoxValue());
					var optionValue = item().SelectedOption();
					var optionValueTwo = jQuery.trim(item().SelectedOptionTwo());

					// Ignore empty values
					if (textValue.length > 0 && optionValueTwo.length > 0 && !isNaN(optionValue)) {
						var TextLangQueryDetail = function (text, langID) { this.Text = text; this.LanguageId = langID; };
						var TwoSingleSelectsWithTextBoxQueryPart = function (type, text, value) {
							this.FilterType = isNaN(type) ? 1 : parseInt(type);
							this.TextLang = new TextLangQueryDetail(text, value);
						};
						thisQueryModel[id] = new TwoSingleSelectsWithTextBoxQueryPart(optionValue, textValue, optionValueTwo);
						hasFilters = true;
					}
					break;
				}
				case 'MediaCountMultiSingleSelect': {
					var imageWidthInPixels = jQuery.trim(item().TextBoxValue());
					var countConjugation = item().SelectedOption();
					var count = jQuery.trim(item().SelectedOptionTwo());
					var sizeConjugation = jQuery.trim(item().SelectedOptionThree());

					// Ignore empty values
					if (count.length > 0 && countConjugation.length > 0) {
						var MediaCountQueryPart = function (count, countConjugation, sizeConjugation, imageWidthInPixels) {
							this.MediaCount = count;
							this.CountConjugation = countConjugation;

							if (sizeConjugation.length > 0 && IsNumeric(parseInt(imageWidthInPixels))) {
								this.SizeConjugation = sizeConjugation;
								this.ImageWidthInPixels = imageWidthInPixels;
							}
						};
						thisQueryModel[id] = new MediaCountQueryPart(count, countConjugation, sizeConjugation, imageWidthInPixels);
						hasFilters = true;
					}
					break;
				}
				case 'SingleSelectWithOptionalTextBox': {
					var textValue = jQuery.trim(item().TextBoxValue());
					var optionValue = item().SelectedOption();
					var optionValueTwo = jQuery.trim(item().SelectedOptionTwo());

					// Ignore empty values
					if (!isNaN(optionValue)) {
						var ExternalIDQueryDetailQueryPart = function (key, type, value) {
							this.ExternaIDKey = parseInt(key);
							this.ExternalIDSearchType = parseInt(type);
							this.Value = value;
						};
						thisQueryModel[id] = new ExternalIDQueryDetailQueryPart(optionValue, optionValueTwo, textValue);
						hasFilters = true;
					}
					break;
				}
				case 'CompositeProximity': {
					var optionValue = item().SelectedOption();
					var textValue = jQuery.trim(item().TextBoxValue());
					var textValueTwo = jQuery.trim(item().TextBoxValueTwo());
					var textValueThree = jQuery.trim(item().TextBoxValueThree());

					// Ignore empty values
					if (!isNaN(optionValue) && (textValue.length > 0
						|| (textValueTwo.length > 0
							&& isLatitudeValid(textValueTwo)
							&& textValueThree.length > 0
							&& isLatitudeValid(textValueThree)))
					) {

						var ProximityQueryPart = function (distance, postcode, lat, long) {
							this.DistanceInKilometres = isNaN(distance) ? 1 : parseInt(distance);
							if (item().IsPostcodeProximitySelected()) {
								this.PostCode = postcode;
							} else {
								this.Latitude = parseFloat(lat);
								this.Longitude = parseFloat(long);
							}
						};
						thisQueryModel[id] = new ProximityQueryPart(optionValue, textValue, textValueTwo, textValueThree);
						hasFilters = true;
					}
					break;
				}
				case 'DateRange': {
					var dateFrom = utcDate(item().DateFrom());
					var dateTo = utcDate(item().DateTo());
					var relativeDatesSelected = item().RelativeDatesSelected();
					var interval = item().SelectedOptionTwo();

					if (dateFrom !== null || dateTo !== null) {
						var optionValue = item().SelectedOption();
						var DateRangeQueryPart = function (isBetween, relativeDatesSelected, interval, from, to) {
							this.RelativeDates = relativeDatesSelected;
							this.Interval = interval;
							this.InRange = isBetween === 'true'; this.From = from; this.To = to;
						};
						thisQueryModel[id] = new DateRangeQueryPart(optionValue, relativeDatesSelected, interval, dateFrom, dateTo);
						hasFilters = true;
					}
					break;
				}
				case 'DateRangeWithRelativeDates': {
					var DateRangeQueryPart = function (isBetween, relativeDatesSelected, interval, periodValue, period, from, to) {
						this.RelativeDates = relativeDatesSelected;
						this.InRange = isBetween === 'true';
						this.Interval = interval;
						this.PeriodValue = periodValue;
						this.Period = period;
						this.From = from;
						this.To = to;
					};
					var optionValue = item().SelectedOption();

					if (item().RelativeDatesSelected() === 'true') {
						var optionTwoValue = item().SelectedOptionTwo();
						var textBoxValue = item().TextBoxValue();
						var optionFourValue = item().SelectedOptionFour();
						thisQueryModel[id] = new DateRangeQueryPart(optionValue, true, optionTwoValue, textBoxValue, optionFourValue);
						hasFilters = true;
					}
					else {
						var dateFrom = utcDate(item().DateFrom());
						var dateTo = utcDate(item().DateTo());

						if (dateFrom !== null || dateTo !== null) {
							thisQueryModel[id] = new DateRangeQueryPart(optionValue, false, null, null, null, dateFrom, dateTo);
							hasFilters = true;
						}
					}
					break;
				}
				case 'SingleSelect': {
					var value = item().SelectedOption();
					if (value !== undefined) {
						hasFilters = true;
						switch (id) {
							case 'StatusId':
							case 'WidgetID':
							case 'HasImageMediaWithNoAltText':
								thisQueryModel[id] = value;
								break;
							default:
								thisQueryModel[id] = value === 'true';
						}
					}
					break;
				}
				case 'MultiSelect':
				case 'MultiSelectSimple':
				case 'CompositeMultiSelect':
				case 'MultiSelectSingleSelect': {
					var queryPart = buildMultiSelectQueryPart(item());
					if (typeof (queryPart) !== 'undefined') {
						thisQueryModel[id] = queryPart;
						hasFilters = true;
					}
					break;
				}
				case 'MembershipCompositeMultiSelect': {
					var firstQueryPart = buildMultiSelectQueryPart(item());
					if (typeof (firstQueryPart) !== 'undefined') {
						thisQueryModel[id] = firstQueryPart;
						hasFilters = true;
					}
					var secondQueryPart = buildMultiSelectQueryPart(item().Exclusions());
					if (typeof (secondQueryPart) !== 'undefined') {
						thisQueryModel[id + "Exclusions"] = secondQueryPart;
						hasFilters = true;
					}
					break;
				}
				case 'ChannelCompositeMultiSelect': {
					var firstQueryPart = buildMultiSelectQueryPart(item());
					if (typeof (firstQueryPart) !== 'undefined') {
						thisQueryModel[id] = firstQueryPart;
						hasFilters = true;
					}
					var secondQueryPart = buildMultiSelectQueryPart(item().Exclusions());
					if (typeof (secondQueryPart) !== 'undefined') {
						thisQueryModel[id + "Exclusions"] = secondQueryPart;
						hasFilters = true;
					}

					break;
				}
				case 'NumericRange': {
					var minimumValue = item().MinimumValue();
					var maximumValue = item().MaximumValue();

					if (minimumValue !== null || maximumValue !== null) {
						var NumericRangeQueryDetail = function (min, max) {
							this.Min = min;
							this.Max = max;
						};
						thisQueryModel[id] = new NumericRangeQueryDetail(minimumValue, maximumValue);
						hasFilters = true;
					}
					break;
				}
				case 'NumericRangeWithToggle': {
					var selectedOption = item().SelectedOption();
					var minimumValue = item().MinimumValue();
					var maximumValue = item().MaximumValue();

					if (selectedOption && (minimumValue !== null || maximumValue !== null)) {
						var GroupTravelQueryDetail = function (hasGroupTravel, range) {
							this.HasGroupTravel = hasGroupTravel;
							this.Range = range;
						};
						var rangeIsRequired = (selectedOption === 'true') || (selectedOption === undefined);
						var range = rangeIsRequired
							? {
								min: minimumValue,
								max: maximumValue
							}
							: undefined;
						thisQueryModel[id] = new GroupTravelQueryDetail(selectedOption, range);
						hasFilters = true;
					}
					break;
				}
				default:
					if (window.console) {
						console.log('Unsupported Template: ' + template);
					}
			}
		});

		if (hasFilters) {
			return thisQueryModel;
		} else {
			return null;
		}
	};

	Date.prototype.addDays = function (days) {
		var dat = new Date(this.valueOf());
		dat.setDate(dat.getDate() + days);
		return dat;
	};

	Date.prototype.addMonths = function (months) {
		var dat = new Date(this.valueOf());
		dat.setMonth(dat.getMonth() + months);
		return dat;
	};

	// NB: As we care about dates only and not the time,
	// we need to ignore user's local time zone by converting the dates to UTC format 
	var utcDate = function (dateString) {
		var localDate = kendo.parseDate(dateString);
		// Convert to UTC date, so timezones don't change the date :(
		var utcDate = localDate != null ? new Date(Date.UTC(localDate.getFullYear(), localDate.getMonth(), localDate.getDate())) : null;
		return utcDate;
	};

	searchViewModel.runSearch = function () {
		var thisQueryModel = this.buildSearchQuery();
		if (thisQueryModel != null) {
			queryModel = thisQueryModel;
		} else {
			queryModel = new ProductSearchQuery();
		}
		queryModel.Conjugation = isNaN(searchViewModel.Conjugation()) ? 0 : parseInt(searchViewModel.Conjugation());
		searchViewModel.CollapseFilters();
		RunSearchQuery();
	};

	searchViewModel.HasError = ko.computed(function () {
		var invalidFilters = NewMind.Array.filter(searchViewModel.SelectedFilters(), function (o) { return o().HasError(); });
		return invalidFilters.length > 0;
	});

	searchViewModel.CombinedNameKeyIDsValue = ko.observable('');

	searchViewModel.AreFiltersCollapsed = ko.observable($('.filters').is(":visible"));

	searchViewModel.CollapseFilters = function () { $('.filters').hide(); searchViewModel.AreFiltersCollapsed(true); };
	searchViewModel.ToggleFilters = function () { $('.filters').toggle(); searchViewModel.AreFiltersCollapsed(!$('.filters').is(":visible")); };

	searchViewModel.Conjugation = ko.observable(0);

	searchViewModel.GetConjunction = function () { return this.ConjugationOptions()[this.Conjugation()].Conjunction().toUpperCase(); };

	searchViewModel.GetStartDateFromIntervalsMappings = function (dateInterval) { return this.DateIntervalsMappings[dateInterval].StartDate(); };
	searchViewModel.GetEndDateFromIntervalsMappings = function (dateInterval) { return this.DateIntervalsMappings[dateInterval].EndDate(); };
	searchViewModel.ToggleEnabledForDateRangeDatePickers = function (enabled) {
		$('#DateRangeTemplate input.auto-date-picker').each(function () {
			if ($(this).data('kendoDatePicker'))
				$(this).data('kendoDatePicker').enable(enabled);
		});
	};

	searchViewModel.showJson = function () { alert(ko.toJSON(searchViewModel)); };

	searchViewModel.OpeningPeriodSearch = function (isLater) {
		var openingFilters = NewMind.Array.filter(searchViewModel.SelectedFilters(), function (o) { return o().Filter.Id === 'Openings'; });

		if (openingFilters.length === 0)
			return;

		var filter = openingFilters[0]();

		if (filter.RelativeDatesSelected() === 'true') {
			filter.updateDateRanges();
			filter.RelativeDatesSelected('false');
		}

		var dateFrom = utcDate(filter.DateFrom());
		var dateTo = utcDate(filter.DateTo());

		var newDateFrom, newDateTo;
		if (dateFrom.getDate() === dateTo.getDate() && dateFrom.getMonth() !== dateTo.getMonth()) {
			var monthDiff = monthsDiff(dateFrom, dateTo);
			if (isLater) {
				newDateFrom = dateTo;
				newDateTo = dateTo.addMonths(monthDiff);
			} else {
				newDateFrom = dateFrom.addMonths(monthDiff * -1);
				newDateTo = dateFrom;
			}
		} else {
			var dateDiff = daysDiff(dateFrom, dateTo);

			if (dateDiff === 0)
				dateDiff = 1;

			if (isLater) {
				newDateFrom = dateTo.addDays(1);
				newDateTo = dateTo.addDays(dateDiff);
			} else {
				newDateFrom = dateFrom.addDays(dateDiff * -1);
				newDateTo = dateFrom.addDays(-1);
			}
		}
		filter.DateFrom(kendo.toString(newDateFrom, kendo.culture().calendar.patterns.d));
		filter.DateTo(kendo.toString(newDateTo, kendo.culture().calendar.patterns.d));

		searchViewModel.runSearch();
	};
};

function daysDiff(dateFrom, dateTo) {
	return Math.round((dateTo - dateFrom) / (1000 * 60 * 60 * 24));
}

function monthsDiff(d1, d2) {
	var months;
	months = (d2.getFullYear() - d1.getFullYear()) * 12;
	months -= d1.getMonth();
	months += d2.getMonth();
	return months <= 0 ? 0 : months;
}

// Search results functions

String.prototype.repeat = function (count) {
	if (count < 1) return '';
	var result = '', pattern = this.valueOf();
	while (count > 0) {
		if (count & 1) result += pattern;
		count >>= 1, pattern += pattern;
	}
	return result;
};

function LastSearch() {
	this.Query = new ProductSearchQuery();
}

function getLastSearch() {
	if (suppressGetLastSearch)
		return;

	// Determine if we need to load the last saved search or initialise
	if (location.hash === '') {						 /*No hash means that this is a first load of the page, as such we should load the search page as normal. If there is
																a hash it means that the usert got here by clicking "Back" in their browser so attempt to load the last saved search*/

		// Because this function is bound to 'hashchange' window event, 
		// we need to suppress it prior changing the location hash
		// so it does not get re-run again (Case 21978).
		suppressGetLastSearch = true;
		location.replace("#s");

		var multiSelectStringQueryPart = function (type, values) {
			this.FilterType = isNaN(type) ? 1 : parseInt(type);
			this.Options = (values instanceof Array) ? values : null;
		};

		// Handle any querystring-provided searches
		if (SearchContext.Request.Search != undefined || SearchContext.Request.ProdType != undefined || SearchContext.Request.InBasket === true || SearchContext.Request.RelatedToProduct != undefined) // Don't do Whitespace check here, as "search=" is a valid request from Epicentre that should search.
		{
			if (SearchContext.Request.Search !== '')
				queryModel.CombinedNameKeyIDs = SearchContext.Request.Search;
			if (SearchContext.Request.ProdType != undefined && SearchContext.Request.ProdType !== '')
				queryModel.TypeIds = new multiSelectStringQueryPart(1, new Array(SearchContext.Request.ProdType));
			if (SearchContext.Request.InBasket === true) {
				queryModel.IsInBasket = true;
				queryModel.StatusId = ''; // Include both active+archived
			}
			if (SearchContext.Request.RelatedToProduct > 0) {
				queryModel.RelatedToProduct = SearchContext.Request.RelatedToProduct;
			}
			searchViewModel.initialiseFromQueryModel();
			if (SearchContext.Request.DisableAutoSearch !== true) {
				searchViewModel.runSearch();
			}
		}

		return;
	}
	var isOrganisationSearch = SearchContext.Request.ProdType === 'BUSI';
	var savedSearchHash = location.hash.substring(0, 3);
	var searchUrl = SearchContext.Urls.GetLastSearch + '?isOrganisationSearch=' + isOrganisationSearch;

	if (savedSearchHash === '#ss') {
		var savedSearchKey = location.hash.substring(4);
		searchUrl = searchUrl + '&savedSearchKey=' + savedSearchKey;
	}

	$.ajax({
		url: searchUrl,
		success: function (data, status, jqxhr) {
			if (data != null && data != undefined && jQuery.trim(data) != "") {
				LoadSearch(data.Query);

				// Set main Saved Search detauls.
				$('#savedSearchName').val(data.SavedSearchName);
				$('#savedSearchKey').val(data.SavedSearchKey);

				// Set checkbox for whether this saved search is private.
				if (data.SavedSearchIsPrivate)
					setCustomCheckboxChecked($('#savedSearchPrivateCustomCheckbox'));
				else
					setCustomCheckboxUnchecked($('#savedSearchPrivateCustomCheckbox'));

				// Show overwrite options if appropriate.
				if (data.SavedSearchKey)
					$('#overwriteSavedSearch').show();
				if (data.SavedSearchCanBeOverwritten) {
					document.getElementById('rdoOverwrite').disabled = false;
					document.getElementById('rdoOverwrite').checked = true;
				}
				else {
					document.getElementById('rdoOverwrite').disabled = true;
					document.getElementById('rdoSaveNew').checked = true;
				}
			}
		},
		error: function (jqXHR, textStatus, errorThrown) {
			alert("Error: " + textStatus + " " + errorThrown);
		},
		datatype: 'JSON',
		cache: false
	});
}

function LoadSearch(searchQuery) {
	if (searchQuery != null) queryModel = searchQuery;
	initialisingSearch = true;
	searchViewModel.initialiseFromQueryModel();
	initialisingSearch = false;
	RunSearchQuery();
	searchViewModel.AreFiltersCollapsed(!$('.filters').is(":visible"));
}

function RunSearchQuery(pagenum, pagesize) {
	$('body').trigger('click');

	if (initialisingSearch)	// Don't send any queries to the server if we are inflating an initial search object
		return;

	// On first load, the animation of the button stalls (not clear why). To avoid the user seeing a mis-sized button for the
	// duration; skip the spinner for this first instance.
	// Even if we call .finish() immediately after this; it's still not rendered correctly; so it seems like something
	// (the KO binding?) is hanging the rendering in between these lines of code executing :(
	if (!firstLoad)
		NewMind.Buttons.setButtonStatusSearching(document.getElementById('search-find-button'));

	if (pagenum == undefined)
		currentPage = 1;
	else
		currentPage = parseInt(pagenum, 10);

	if (currentPage < 1)
		currentPage = 1;

	if (pagesize == undefined) {
		pagesize = pageSize;
	}

	if (pagesize < 15) {
		return;
	}

	if (currentPage > 1 && currentPage > Math.ceil(parseInt($("#NumResults").text()) / pagesize)) {
		currentPage = Math.ceil(parseInt($("#NumResults").text()) / pagesize);
		return;
	}

	if (currentPage !== 1) {
		_gaq.push(['_trackEvent', 'Search (UID: ' + SearchContext.CurrentUserKey + ')', 'Get New Page', dumpToString(queryModel), currentPage]);
	} else {
		_gaq.push(['_trackEvent', 'Search (UID: ' + SearchContext.CurrentUserKey + ')', 'Initial Search', dumpToString(queryModel)]);
	}

	var isOrgSearch = queryModel.TypeIds && queryModel.TypeIds.Options.length === 1 && queryModel.TypeIds.Options[0] === 'BUSI';

	var searchData = { 'page': currentPage, 'pagesize': pagesize, 'queryAsJson': JSON.stringify(queryModel), 'isOrganisationSearch': isOrgSearch };

	// Only display loading message on the first page load as this is normally the only time we have a significant delay.
	// The rest of the time, the load is quite responsive so adding the loading fade in / out is annoying.
	// See case 9667.
	if (firstLoad) {
		$('#WithResults').show();
		$("#WithResults").block({ message: '<div style="padding: 10px;"><img src="' + SearchContext.Urls.SpinnerImage + '" /> ' + SearchContext.Translations.Loading + '</div>', fadeIn: 0 });
		firstLoad = false;
	}

	$.ajax({
		url: SearchContext.Urls.Search,
		data: JSON.stringify(searchData),
		type: "POST",
		datatype: 'JSON',
		contentType: "application/json",
		success: function (data, status, jqxhr) {
			$("#WithResults").unblock();
			$('#ActionMenu').show();
			// Adding an object check here because if the session has timed out we get the HTML contents of the login page returned here as a string
			// Therefore, if we are a successful AJAX request with no JSON data we are assuming a page timeout and redirecting to login page. Case 11623
			if (typeof (data) !== "object")
				window.location = SearchContext.Urls.Login;

			ApplyConditionalColumns(queryModel);

			BuildResults(data);

			// Sometimes this search is so fast; the animation doesn't show, and the button just looks like it goes mad.
			// Force a 200ms delay to ensure it's obvious what's happening (this doesn't delay the results, only the button resetting).
			setTimeout(function () { NewMind.Buttons.resetButtonStatus(document.getElementById('search-find-button')); }, 500);

			var results = data.TotalResults;
			if (results <= 0) {
				return;
			}

			if (pagenum > 1) {
				$("#PageFirst").removeAttr("disabled");
				$("#PageBack").removeAttr("disabled");
			} else {
				$("#PageFirst").attr("disabled", "disabled");
				$("#PageBack").attr("disabled", "disabled");
			}

			if (pagenum == Math.ceil(results / pagesize)) {
				$("#PageForward").attr("disabled", "disabled");
			} else {
				$("#PageForward").removeAttr("disabled");
			}

			// Reset any search results header checkboxes
			$('table.search-results th.checkboxcell').removeClass('checked');
		},
		error: function (jqXHR, textStatus, errorThrown) {
			$("#WithResults").unblock();
			alert("Error: " + textStatus + " " + errorThrown);
			NewMind.Buttons.resetButtonStatus(document.getElementById('search-find-button'));
		},
		cache: false
	});
}

function BuildResults(jsonData) {
	lastResultSize = jsonData.TotalResults;
	lastResultData = jsonData;

	if (jsonData.TotalResults <= 0) {

		// Search returned 0 results
		if (jsonData.TotalResults === 0) {
			$('#ActionMenu').show();
			SetActionMenuForNoResults();
			$('#noResultsText').show();
			$('#indexBuildingText').hide();
		} else { // Index is still building
			$('#ActionMenu').hide();
			$('#noResultsText').hide();
			$('#indexBuildingText').show();
		}

		$('.NumResults').text("0");
		$('#NoResults').show();
		$('#WithResults').hide();
		$('#Paging').hide();

		return;
	}

	var resultsDisplay = jsonData.TotalResults + "";

	$('#NoResults').hide();
	$('#WithResults').hide();
	$('#Paging').hide();
	$("#results").html($("#resultTemplate").render(jsonData.Results, { editUrl: SearchContext.Urls.EditProduct, viewUrl: SearchContext.Urls.ViewProduct, referer: "&ref=s" }));
	$('.NumResults').text(resultsDisplay);
	$('.search-response').show();
	$('#WithResults').show();
	$('#ActionMenu').show();
	SetActionMenuForResults();
	UpdatePaging(jsonData);
	$('#Paging').show();

	if (jsonData.ExclusivelyBrochures) {
		$("table.search-results").addClass("brochures");
	} else {
		$("table.search-results").removeClass("brochures");
	}

	if (jsonData.ShowEarlierLaterOpeningOptions) {
		$("#LaterOpeningPeriod").css('display', 'block');
		$("#EarlierOpeningPeriod").css('display', 'block');
	} else {
		$("#LaterOpeningPeriod").css('display', 'none');
		$("#EarlierOpeningPeriod").css('display', 'none');
	}
}

function UpdatePaging(jsonData) {
	var totalPages = Math.ceil(jsonData.TotalResults / pageSize);
	var clampPage = function (i) { return Clamp(i, 1, totalPages); };
	var endPage = clampPage(currentPage + 2);
	var startPage = clampPage(endPage - 4); // -4 for inclusive
	endPage = clampPage(startPage + 4); // Recalculate, as we might go start+5 now (eg. if we're at page 1).

	var pageLink = 1;
	var pageToLinkTo = startPage;
	while (pageLink <= 5) {
		var showLink = pageToLinkTo <= endPage;
		var isCurrentPage = pageToLinkTo == currentPage;
		var $pageLink = $('#pagelink-' + pageLink);

		$pageLink.text(pageToLinkTo); // Set page number on box
		$pageLink[showLink ? 'show' : 'hide'](); // Set link visibility
		$pageLink.parent()[isCurrentPage ? 'addClass' : 'removeClass']('active'); // Set active for current page

		// Set the data-page-to-link for the click handler.
		$pageLink[0].setAttribute('data-page-to-link', pageToLinkTo);

		pageToLinkTo++;
		pageLink++;
	}

	// First link
	$('#pagelink-first').prop('disabled', currentPage == 1);
	$('#pagelink-first').parent()[currentPage == 1 ? 'addClass' : 'removeClass']('disabled');

	// Last link
	$('#pagelink-last').prop('disabled', currentPage == totalPages);
	$('#pagelink-last').parent()[currentPage == totalPages ? 'addClass' : 'removeClass']('disabled');
	$('#pagelink-last')[0].setAttribute('data-page-to-link', totalPages);
}

function Clamp(i, min, max) {
	if (i < min)
		return min;
	if (i > max)
		return max;
	return i;
}

function ApplyConditionalColumns(queryModel) {
	// Map of class names and conditions for them to be applied.
	var conditionalClasses = {
		'multi-product-type-search': !queryModel.TypeIds || !queryModel.TypeIds.Options.length !== 1,
		'show-opening-date': queryModel.TypeIds && queryModel.TypeIds.Options.length === 1 && queryModel.TypeIds.Options[0] === 'EVEN'
	};
	var $resultsTable = $('table.search-results');

	for (var className in conditionalClasses) {
		if (conditionalClasses.hasOwnProperty(className))
			$resultsTable[conditionalClasses[className] ? 'addClass' : 'removeClass'](className); // Add or remove the class depending on the condition.
	}
}

function dumpToString(obj, depth) {
	if (depth == undefined) {
		depth = 0;
	}

	var text = "";
	for (var key in obj) {
		if (typeof (obj[key]) === 'object') {
			text += " ".repeat(depth * 4) + "[" + key + "]\n";
			text += dumpToString(obj[key], depth + 1);
		}
		else {
			text += " ".repeat(depth * 4) + key + " - " + obj[key] + "\n";
		}
	}

	return text;
}

function markRowAsApplied($row) {
	$row.animate({ backgroundColor: "#00587E" }, 200);
	$row.animate({ backgroundColor: "transparent" }, 200);
	$row.animate({ backgroundColor: "#F9F9F9" }, 200);
}

// Batch actions functions
function ApplyAction(ajaxUrl, selected, callback, callbackItem) {
	$.ajax({
		url: ajaxUrl,
		success: function (data, status, jqxhr) {
			var key = 0;
			var rows = [];
			for (var i = 0; i < selected.length; i++) {
				key = selected[i];
				if (typeof (callbackItem) === 'function') {
					callbackItem(key);
				}

				if (rows.indexOf(key) == -1) {
					rows.push(key);
				}
			}

			rows.forEach(function (x) {
				markRowAsApplied($(".row" + x));
			});

			if (typeof (callback) === 'function') {
				callback(data);
			}
		},
		error: function (jqXHR, textStatus, errorThrown) {
			alert("Error: " + textStatus + " " + errorThrown);
		},
		datatype: 'JSON',
		cache: false
	});
}

// Basket actions
function AddToBasket(currentUserKey, urlSelected, urlAll) {
	var selected = new Array();
	$('#results tr td input:checked').each(function () {
		selected.push($(this).attr('id'));
	});

	if (selected.length > 0) {
		AddSelectedToBasket(currentUserKey, urlSelected, selected);
	} else {
		AddAllToBasket(currentUserKey, urlAll);
	}
}

function AddAllToBasket(currentUserKey, url) {
	if (lastResultSize == 0) {
		return;
	}

	_gaq.push(['_trackEvent', 'Search (UID: ' + currentUserKey + ' )', 'Add All To Basket', dumpToString(queryModel)]);

	var ajaxUrl = url + "?queryAsJson=" + encodeURIComponent(JSON.stringify(queryModel));

	var thisPage = new Array();
	$('#results td input').each(function () {
		thisPage.push($(this).attr('id'));
	});

	ApplyAction(ajaxUrl, thisPage, RefreshBasketControl, ShowBasketIcon);
}

function AddSelectedToBasket(currentUserKey, url, selected) {
	_gaq.push(['_trackEvent', 'Search (UID: ' + currentUserKey + ')', 'Add To Basket', selected.join(', ')]);

	var ajaxUrl = url + "?productIds=" + encodeURIComponent(JSON.stringify(selected));

	ApplyAction(ajaxUrl, selected, RefreshBasketControl, ShowBasketIcon);
}

function RemoveFromBasket(currentUserKey, urlSelected, urlAll) {
	var selected = new Array();
	$('#results tr td input:checked').each(function () {
		selected.push($(this).attr('id'));
	});

	if (selected.length > 0) {
		RemoveSelectedFromBasket(currentUserKey, urlSelected, selected);
	} else {
		RemoveAllFromBasket(currentUserKey, urlAll);
	}
}

function RemoveSelectedFromBasket(currentUserKey, url, selected) {
	_gaq.push(['_trackEvent', 'Search (UID: ' + currentUserKey + ')', 'Remove From Basket', selected.join(', ')]);

	var ajaxUrl = url + "?productIds=" + encodeURIComponent(JSON.stringify(selected));

	ApplyAction(ajaxUrl, selected, RefreshBasketControl, HideBasketIcon);
}

function RemoveAllFromBasket(currentUserKey, url) {
	if (lastResultSize == 0) {
		return;
	}

	_gaq.push(['_trackEvent', 'Search (UID: ' + currentUserKey + ')', 'Remove All From Basket', dumpToString(queryModel)]);

	var ajaxUrl = url + "?queryAsJson=" + encodeURIComponent(JSON.stringify(queryModel));

	var thisPage = new Array();
	$('#results tr td input').each(function () {
		thisPage.push($(this).attr('id'));
	});

	ApplyAction(ajaxUrl, thisPage, RefreshBasketControl, HideBasketIcon);
}

function ShowBasketIcon(key) {
	$(".resBsk" + key).html("<span class=\"icon basket inbasket\"></span>");
	$(".resBsk" + key + " i").hide();
	$(".resBsk" + key + " i").fadeIn(600);
}

function HideBasketIcon(key) {
	$(".resBsk" + key).html("");
	$(".resBsk" + key + " i").hide();
	$(".resBsk" + key + " i").fadeIn(600);
}

// Log Telephone
function LogTelephone(link, currentUserKey, urlSelected, urlAll) {
	if ($(link).parent().hasClass('disabled'))
		return;

	var selected = new Array();
	$('#results tr td input:checked').each(function () {
		selected.push($(this).attr('id'));
	});

	if (selected.length > 0) {
		LogTelephoneSelected(currentUserKey, urlSelected, selected);
	} else {
		LogTelephoneAll(currentUserKey, urlAll);
	}
}

function LogTelephoneSelected(currentUserKey, url, selected) {

	_gaq.push(['_trackEvent', 'Search (UID: ' + currentUserKey + ')', 'Register Telephone Provider Event (selected)', selected.join(', ')]);

	var ajaxUrl = url + "?productIds=" + encodeURIComponent(JSON.stringify(selected));

	ApplyAction(ajaxUrl, selected, alert);
}

function LogTelephoneAll(currentUserKey, url) {

	if (lastResultSize == 0) {
		return;
	}

	_gaq.push(['_trackEvent', 'Search (UID: ' + currentUserKey + ')', 'Register Telephone Provider Event (all)', dumpToString(queryModel)]);

	var ajaxUrl = url + "?queryAsJson=" + encodeURIComponent(JSON.stringify(queryModel));

	var thisPage = new Array();
	$('#results tr td input').each(function () {
		thisPage.push($(this).attr('id'));
	});

	ApplyAction(ajaxUrl, thisPage, alert);
}

function ApplyBatchAction(link, currentUserKey, batchAction, iWidth, iHeight, scrolling) {
	if ($(link).parent().hasClass('disabled'))
		return;

	if (typeof (iWidth) === 'undefined') { iWidth = 400; }
	if (typeof (iHeight) === 'undefined') { iHeight = 320; }

	var selected = new Array();
	$('#results tr td input:checked').each(function () {
		selected.push($(this).attr('id'));
	});

	var membershipSchemeKeys = new Array();
	if (queryModel.Memberships != null) {
		NewMind.Array.forEach(queryModel.Memberships.Options, function (o) {
			if (o.Key.indexOf('SCHE') >= 0) {
				var split = o.Key.split('-');
				membershipSchemeKeys.push(parseInt(split[1]));
			}
		});
	}

	if (selected.length > 0) {
		_gaq.push(['_trackEvent', 'Search (UID: ' + currentUserKey + ')', batchAction + ' (selected)', selected.join(', ')]);
		var productTypeIds = (queryModel.TypeIds || { Options: [] }).Options.reduce(function (total, current) {
			return total.concat(current);
		}, []);
		showIframe(this, SearchContext.Translations[batchAction], "/App/Search/ApplyBatchActionSelected?productKeys=" + encodeURIComponent(JSON.stringify(selected)) + "&batchAction=" + batchAction + '&productTypeIdsJson=' + JSON.stringify(productTypeIds) + "&membershipSchemeKeys=" + encodeURIComponent(JSON.stringify(membershipSchemeKeys)), iWidth, iHeight, false, null, null, scrolling);
	} else {
		if (lastResultSize == 0) {
			return;
		}

		var thisPage = new Array();
		$('#results tr td input').each(function () {
			thisPage.push($(this).attr('id'));
		});
		_gaq.push(['_trackEvent', 'Search (UID: ' + currentUserKey + ')', batchAction + ' (all)', dumpToString(queryModel)]);
		showIframe(this, SearchContext.Translations[batchAction], "/App/Search/ApplyBatchActionAll?queryAsJson=" + encodeURIComponent(JSON.stringify(queryModel)) + "&batchAction=" + batchAction + "&membershipSchemeKeys=" + encodeURIComponent(JSON.stringify(membershipSchemeKeys)), iWidth, iHeight, false, null, null, scrolling);
	}
}

function SetBatchActionItemsCountText(e, bDisableOverride) {
	if (e === null || e.currentTarget === null)
		return;

	var selectedCount = $('#results tr td input:checked').length;

	var $actionmenu = $(e.currentTarget).closest('div.actionmenu');
	if (selectedCount > 0) {
		$actionmenu.find('#all-results-text').hide();
		$actionmenu.find('#selected-items-text').text(selectedCount + ' ' + SearchContext.Translations.SelectedItems);
	}
	else {
		$actionmenu.find('#all-results-text').show();
		$actionmenu.find('#selected-items-text').text('');
	}

	var thereAreResults = parseInt($('#NumResults').text(), 10) > 0;

	/* Enable 'expensive' batch actions if the total results is less than 2000 *or* you've checked at least one checkbox. */

	var maxProducts = 2000;
	if (thereAreResults) {
		var allowBatchActions = (selectedCount > 0 && selectedCount <= maxProducts)
			|| parseInt($('#NumResults').text(), 10) <= maxProducts;

		if (!allowBatchActions || bDisableOverride)
			$('li.expensive-batch-action').addClass('disabled');
		else
			$('li.expensive-batch-action').removeClass('disabled');
	}
}


function SetActionMenuForNoResults() {
	$('li.batch-action').addClass('disabled');
}

function SetActionMenuForResults() {
	$('li.batch-action').removeClass('disabled');
}

function BatchActionCallback() {
	if (typeof window.parent.showStaleResultsMessage === 'function')
		window.parent.showStaleResultsMessage(false);
	window.parent.batchActionsDisabled = true;
};
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-21389996-1']);
_gaq.push(['_setDomainName', 'none']);
_gaq.push(['_trackPageview']);
(function () {
	var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
	ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';

	var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();;
// Taken from:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

// DT: These are not really polyfills, as adding to Array.prototype breaks a ton of code we have that foreachs over arrays!
var NewMind = NewMind || {};
NewMind.Array = NewMind.Array || {};

NewMind.Array.filter =  function (array, fun/*, thisArg*/) {
	'use strict';

	if (this === void 0 || this === null) {
		throw new TypeError();
	}

	var t = Object(array);
	var len = t.length >>> 0;
	if (typeof fun !== 'function') {
		throw new TypeError();
	}

	var res = [];
	var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
	for (var i = 0; i < len; i++) {
		if (i in t) {
			var val = t[i];

			// NOTE: Technically this should Object.defineProperty at
			//       the next index, as push can be affected by
			//       properties on Object.prototype and Array.prototype.
			//       But that method's new, and collisions should be
			//       rare, so use the more-compatible alternative.
			if (fun.call(thisArg, val, i, t)) {
				res.push(val);
			}
		}
	}

	return res;
};
	
;
// Taken from:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

// DT: These are not really polyfills, as adding to Array.prototype breaks a ton of code we have that foreachs over arrays!

var NewMind = NewMind || {};
NewMind.Array = NewMind.Array || {};

NewMind.Array.find = function (array, predicate) {
	if (typeof predicate !== 'function') {
		throw new TypeError('predicate must be a function');
	}
	var list = Object(array);
	var length = list.length >>> 0;
	var thisArg = arguments[2];
	var value;

	for (var i = 0; i < length; i++) {
		if (i in list) {
			value = list[i];
			if (predicate.call(thisArg, value, i, list)) {
				return value;
			}
		}
	}
	return undefined;
};;
// Taken from:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

// DT: These are not really polyfills, as adding to Array.prototype breaks a ton of code we have that foreachs over arrays!

var NewMind = NewMind || {};
NewMind.Array = NewMind.Array || {};

// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.com/#x15.4.4.18
NewMind.Array.forEach = function (array, callback, thisArg) {

	var T, k;

	// 1. Let O be the result of calling ToObject passing the |this| value as the argument.
	var O = Object(array);

	// 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
	// 3. Let len be ToUint32(lenValue).
	var len = O.length >>> 0;

	// 4. If IsCallable(callback) is false, throw a TypeError exception.
	// See: http://es5.github.com/#x9.11
	if (typeof callback !== "function") {
		throw new TypeError(callback + " is not a function");
	}

	// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
	if (thisArg) {
		T = thisArg;
	}

	// 6. Let k be 0
	k = 0;

	// 7. Repeat, while k < len
	while (k < len) {

		var kValue;

		// a. Let Pk be ToString(k).
		//   This is implicit for LHS operands of the in operator
		// b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
		//   This step can be combined with c
		// c. If kPresent is true, then
		if (k in O) {

			// i. Let kValue be the result of calling the Get internal method of O with argument Pk.
			kValue = O[k];

			// ii. Call the Call internal method of callback with T as the this value and
			// argument list containing kValue, k, and O.
			callback.call(T, kValue, k, O);
		}
		// d. Increase k by 1.
		k++;
	}
	// 8. return undefined
};
;
// Taken from:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf

// DT: These are not really polyfills, as adding to Array.prototype breaks a ton of code we have that foreachs over arrays!
var NewMind = NewMind || {};
NewMind.Array = NewMind.Array || {};

// Production steps of ECMA-262, Edition 5, 15.4.4.14
// Reference: http://es5.github.io/#x15.4.4.14
NewMind.Array.indexOf = function (array, searchElement, fromIndex) {

	var k;

	// 1. Let O be the result of calling ToObject passing
	//    the array value as the argument.
	if (array == null) {
		throw new TypeError('"array" is null or not defined');
	}

	var O = Object(array);

	// 2. Let lenValue be the result of calling the Get
	//    internal method of O with the argument "length".
	// 3. Let len be ToUint32(lenValue).
	var len = O.length >>> 0;

	// 4. If len is 0, return -1.
	if (len === 0) {
		return -1;
	}

	// 5. If argument fromIndex was passed let n be
	//    ToInteger(fromIndex); else let n be 0.
	var n = +fromIndex || 0;

	if (Math.abs(n) === Infinity) {
		n = 0;
	}

	// 6. If n >= len, return -1.
	if (n >= len) {
		return -1;
	}

	// 7. If n >= 0, then Let k be n.
	// 8. Else, n<0, Let k be len - abs(n).
	//    If k is less than 0, then let k be 0.
	k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);

	// 9. Repeat, while k < len
	while (k < len) {
		var kValue;
		// a. Let Pk be ToString(k).
		//   This is implicit for LHS operands of the in operator
		// b. Let kPresent be the result of calling the
		//    HasProperty internal method of O with argument Pk.
		//   This step can be combined with c
		// c. If kPresent is true, then
		//    i.  Let elementK be the result of calling the Get
		//        internal method of O with the argument ToString(k).
		//   ii.  Let same be the result of applying the
		//        Strict Equality Comparison Algorithm to
		//        searchElement and elementK.
		//  iii.  If same is true, return k.
		if (k in O && O[k] === searchElement) {
			return k;
		}
		k++;
	}
	return -1;
};
;
// Taken from:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

// DT: These are not really polyfills, as adding to Array.prototype breaks a ton of code we have that foreachs over arrays!

var NewMind = NewMind || {};
NewMind.Array = NewMind.Array || {};

NewMind.Array.map = function (array, callback, thisArg) {

	var T, A, k;
	
	// 1. Let O be the result of calling ToObject passing the |this| value as the argument.
	var O = Object(array);

	// 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
	// 3. Let len be ToUint32(lenValue).
	var len = O.length >>> 0;

	// 4. If IsCallable(callback) is false, throw a TypeError exception.
	// See: http://es5.github.com/#x9.11
	if (typeof callback !== "function") {
		throw new TypeError(callback + " is not a function");
	}

	// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
	if (thisArg) {
		T = thisArg;
	}

	// 6. Let A be a new array created as if by the expression new Array( len) where Array is
	// the standard built-in constructor with that name and len is the value of len.
	A = new Array(len);

	// 7. Let k be 0
	k = 0;

	// 8. Repeat, while k < len
	while (k < len) {

		var kValue, mappedValue;

		// a. Let Pk be ToString(k).
		//   This is implicit for LHS operands of the in operator
		// b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
		//   This step can be combined with c
		// c. If kPresent is true, then
		if (k in O) {

			var Pk = k.toString(); // This was missing per item a. of the above comment block and was not working in IE8 as a result

			// i. Let kValue be the result of calling the Get internal method of O with argument Pk.
			kValue = O[Pk];

			// ii. Let mappedValue be the result of calling the Call internal method of callback
			// with T as the this value and argument list containing kValue, k, and O.
			mappedValue = callback.call(T, kValue, k, O);

			// iii. Call the DefineOwnProperty internal method of A with arguments
			// Pk, Property Descriptor {Value: mappedValue, Writable: true, Enumerable: true, Configurable: true},
			// and false.

			// In browsers that support Object.defineProperty, use the following:
			// Object.defineProperty( A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });

			// For best browser support, use the following:
			A[Pk] = mappedValue;
		}
		// d. Increase k by 1.
		k++;
	}

	// 9. return A
	return A;
};
;
$(function() {
	var retryCount = 1;
	var img = document.createElement('img');
	img.style.position = 'absolute';
	img.style.top = '-1px';
	img.style.left = '-1px';
	img.style.width = '1px';
	img.style.height = '1px';
	document.body.appendChild(img);
	setInterval(function() {

		if (retryCount > 18) // this will keep the session up for 3 hours or inactivity which I feel is reasonable. This can be increased if anyone complains.
			return;

		img.src = "/keepalive.asp?" + (new Date()).getTime();
		retryCount++
	}, 600000);
});;
$(function () { applyChosen(); });

function applyChosen(selectorOrElements, container, chosenOptions) {
	if ($.fn.chosen) {
		container = container || document.body;
		selectorOrElements = selectorOrElements || '.auto-apply-chosen select';

		let chosenDefaults = {
			disable_search_threshold: 8,
			search_contains: true,
			width: '100%',
			allow_single_deselect: true,
			no_results_text: NewMind.CommonTranslations.ChosenPlaceholderNoResults,
			placeholder_text_single: NewMind.CommonTranslations.ChosenPlaceholderSingle,
			placeholder_text_multiple: NewMind.CommonTranslations.ChosenPlaceholderMultiple
		};
		
		chosenOptions = { ...chosenDefaults, ...chosenOptions };

		return $(selectorOrElements, container)
			.chosen(chosenOptions);
	}
}

function destroyChosen(selectorOrElements, container) {
	if ($.fn.chosen) {
		container = container || document.body;
		selectorOrElements = selectorOrElements || '.auto-apply-chosen select';

		return $(selectorOrElements, container).chosen("destroy");
	}
};
var NewMind = window.NewMind || {};

NewMind.Dms = function () {

	var hideMenuTimeout = 0;
	var hideUserTimeout = 0;
	var hideBasketTimeout = 0;
	var body, menu, menuButton, topbar, wall, userProfile, userOptions, basket, basketmenu;
	
	var init = function () {
		body = document.body;
		menu = document.getElementById('menu');
		topbar = document.getElementById('topbar');
		menuButton = document.getElementById('menubutton');
		wall = document.getElementById('wall');
		userProfile = document.getElementById('userprofile');
		userOptions = document.getElementById('useroptions');
		bindBasket();
		setUserFace();
		
		$(menuButton).click(function(e) {
			e.preventDefault();
			e.stopImmediatePropagation();
			toggleMenu();
		});
		$(userProfile).click(function (e) {
			e.preventDefault();
			e.stopImmediatePropagation();
			toggleUser();
		});
		$(wall).click(function (e) {
			e.preventDefault();
			e.stopImmediatePropagation();
			hideMenu();
			hideUser();
			hideBasket();
		});
		$(topbar).click(function(e) {
			e.stopImmediatePropagation();
			hideMenu();
			hideUser();
			hideBasket();
		});
	};

	var setUserFace = function () {
		var userEmail = $('#user-email').text();
		var $userFace = $('#user-face');
		
		var defaultProfileImage = location.protocol + '//' + (location.hostname || location.hostname) + '/App/Content/css/3.0/default-avatar.png';
		var profileImage = userEmail ? get_gravatar(userEmail, 64, defaultProfileImage) : defaultProfileImage;
		
		$userFace.css('background-image', 'url(\'' + profileImage.replace('\'', '') + '\')');
	};
	
	function get_gravatar(email, size, defaultProfileImage) {
		var size = size || 60;
		return 'https://www.gravatar.com/avatar/' + MD5(email) + '.jpg?s=' + size + '&d=' + escape(defaultProfileImage);
	}

	var toggleMenu = function() {
		if ($(menuButton).hasClass('open'))
			hideMenu();
		else {
			showMenu();
		}
	};

	var toggleUser = function() {
		if ($(userProfile).hasClass('open'))
			hideUser();
		else {
			showUser();
		}
	};

	var toggleBasket = function() {
		if ($(basket).hasClass('open')) {
			hideBasket();
		} else {
			showBasket();
		}
	};

	// Show the main meganav menu
	var showMenu = function () {
		clearTimeout(hideMenuTimeout); // If we had a timer to hide the menu, destroy it
		menu.style.display = '';
		$(menuButton).addClass('open');
		hideUser(function () { wall.style.display = 'block'; });
		hideBasket(function () { wall.style.display = 'block'; });
		resizeIframeToFitContent(document.getElementById("classicMenuFrame"));
		resizeIframeToFitContentWidth(document.getElementById("classicMenuFrame"));
	};

	// Set a timer to hide the main meganav menu
	var hideMenu = function (callback) {
		if (!menu || !menuButton)
			return;

		hideMenuTimeout = setTimeout(function () {
			menu.style.display = 'none';
			$(menuButton).removeClass('open');
			wall.style.display = 'none';
			if (callback) {
				callback();
			}
		}, 0);
	};

	// Show the main meganav menu
	var showBasket = function () {
		clearTimeout(hideBasketTimeout); // If we had a timer to hide the menu, destroy it
		basketmenu.style.display = '';
		$(basket).addClass('open');
		hideUser(function () { wall.style.display = 'block'; });
		hideMenu(function () { wall.style.display = 'block'; });
	};

	// Set a timer to hide the main meganav menu
	var hideBasket = function (callback) {
		if (!basket || !basketmenu)
			return;

		hideBasketTimeout = setTimeout(function () {
			basketmenu.style.display = 'none';
			$(basket).removeClass('open');
			wall.style.display = 'none';
			if (callback) {
				callback();
			}
		}, 0);
	};

	// Show the user drop-down stuff
	var showUser = function () {
		clearTimeout(hideUserTimeout); // If we had a timer to hide the user stuff, destroy it
		userOptions.style.display = '';
		$(userProfile).addClass('open');
		hideMenu(function () { wall.style.display = 'block'; });
		hideBasket(function () { wall.style.display = 'block'; });
	};

	// Hide the user drop-down stuff
	var hideUser = function (callback) {
		if (!userOptions)
			return;

		hideUserTimeout = setTimeout(function () {
			userOptions.style.display = 'none';
			$(userProfile).removeClass('open');
			wall.style.display = 'none';
			if (callback) {
				callback();
			}
		}, 0);
	};

	var bindBasket = function () {
		basket = document.getElementById('basket');
		basketmenu = document.getElementById('basketmenu');

		$(basket).click(function (e) {
			e.preventDefault();
			e.stopImmediatePropagation();
			toggleBasket();
		});
	};

	// Fire the init on ready
	$(init);

	return {
		hideMenu: hideMenu,
		bindBasket: bindBasket
	};

}();

// MD5 (Message-Digest Algorithm) by WebToolkit
var MD5 = function (s) { function L(k, d) { return (k << d) | (k >>> (32 - d)) } function K(G, k) { var I, d, F, H, x; F = (G & 2147483648); H = (k & 2147483648); I = (G & 1073741824); d = (k & 1073741824); x = (G & 1073741823) + (k & 1073741823); if (I & d) { return (x ^ 2147483648 ^ F ^ H) } if (I | d) { if (x & 1073741824) { return (x ^ 3221225472 ^ F ^ H) } else { return (x ^ 1073741824 ^ F ^ H) } } else { return (x ^ F ^ H) } } function r(d, F, k) { return (d & F) | ((~d) & k) } function q(d, F, k) { return (d & k) | (F & (~k)) } function p(d, F, k) { return (d ^ F ^ k) } function n(d, F, k) { return (F ^ (d | (~k))) } function u(G, F, aa, Z, k, H, I) { G = K(G, K(K(r(F, aa, Z), k), I)); return K(L(G, H), F) } function f(G, F, aa, Z, k, H, I) { G = K(G, K(K(q(F, aa, Z), k), I)); return K(L(G, H), F) } function D(G, F, aa, Z, k, H, I) { G = K(G, K(K(p(F, aa, Z), k), I)); return K(L(G, H), F) } function t(G, F, aa, Z, k, H, I) { G = K(G, K(K(n(F, aa, Z), k), I)); return K(L(G, H), F) } function e(G) { var Z; var F = G.length; var x = F + 8; var k = (x - (x % 64)) / 64; var I = (k + 1) * 16; var aa = Array(I - 1); var d = 0; var H = 0; while (H < F) { Z = (H - (H % 4)) / 4; d = (H % 4) * 8; aa[Z] = (aa[Z] | (G.charCodeAt(H) << d)); H++ } Z = (H - (H % 4)) / 4; d = (H % 4) * 8; aa[Z] = aa[Z] | (128 << d); aa[I - 2] = F << 3; aa[I - 1] = F >>> 29; return aa } function B(x) { var k = "", F = "", G, d; for (d = 0; d <= 3; d++) { G = (x >>> (d * 8)) & 255; F = "0" + G.toString(16); k = k + F.substr(F.length - 2, 2) } return k } function J(k) { k = k.replace(/rn/g, "n"); var d = ""; for (var F = 0; F < k.length; F++) { var x = k.charCodeAt(F); if (x < 128) { d += String.fromCharCode(x) } else { if ((x > 127) && (x < 2048)) { d += String.fromCharCode((x >> 6) | 192); d += String.fromCharCode((x & 63) | 128) } else { d += String.fromCharCode((x >> 12) | 224); d += String.fromCharCode(((x >> 6) & 63) | 128); d += String.fromCharCode((x & 63) | 128) } } } return d } var C = Array(); var P, h, E, v, g, Y, X, W, V; var S = 7, Q = 12, N = 17, M = 22; var A = 5, z = 9, y = 14, w = 20; var o = 4, m = 11, l = 16, j = 23; var U = 6, T = 10, R = 15, O = 21; s = J(s); C = e(s); Y = 1732584193; X = 4023233417; W = 2562383102; V = 271733878; for (P = 0; P < C.length; P += 16) { h = Y; E = X; v = W; g = V; Y = u(Y, X, W, V, C[P + 0], S, 3614090360); V = u(V, Y, X, W, C[P + 1], Q, 3905402710); W = u(W, V, Y, X, C[P + 2], N, 606105819); X = u(X, W, V, Y, C[P + 3], M, 3250441966); Y = u(Y, X, W, V, C[P + 4], S, 4118548399); V = u(V, Y, X, W, C[P + 5], Q, 1200080426); W = u(W, V, Y, X, C[P + 6], N, 2821735955); X = u(X, W, V, Y, C[P + 7], M, 4249261313); Y = u(Y, X, W, V, C[P + 8], S, 1770035416); V = u(V, Y, X, W, C[P + 9], Q, 2336552879); W = u(W, V, Y, X, C[P + 10], N, 4294925233); X = u(X, W, V, Y, C[P + 11], M, 2304563134); Y = u(Y, X, W, V, C[P + 12], S, 1804603682); V = u(V, Y, X, W, C[P + 13], Q, 4254626195); W = u(W, V, Y, X, C[P + 14], N, 2792965006); X = u(X, W, V, Y, C[P + 15], M, 1236535329); Y = f(Y, X, W, V, C[P + 1], A, 4129170786); V = f(V, Y, X, W, C[P + 6], z, 3225465664); W = f(W, V, Y, X, C[P + 11], y, 643717713); X = f(X, W, V, Y, C[P + 0], w, 3921069994); Y = f(Y, X, W, V, C[P + 5], A, 3593408605); V = f(V, Y, X, W, C[P + 10], z, 38016083); W = f(W, V, Y, X, C[P + 15], y, 3634488961); X = f(X, W, V, Y, C[P + 4], w, 3889429448); Y = f(Y, X, W, V, C[P + 9], A, 568446438); V = f(V, Y, X, W, C[P + 14], z, 3275163606); W = f(W, V, Y, X, C[P + 3], y, 4107603335); X = f(X, W, V, Y, C[P + 8], w, 1163531501); Y = f(Y, X, W, V, C[P + 13], A, 2850285829); V = f(V, Y, X, W, C[P + 2], z, 4243563512); W = f(W, V, Y, X, C[P + 7], y, 1735328473); X = f(X, W, V, Y, C[P + 12], w, 2368359562); Y = D(Y, X, W, V, C[P + 5], o, 4294588738); V = D(V, Y, X, W, C[P + 8], m, 2272392833); W = D(W, V, Y, X, C[P + 11], l, 1839030562); X = D(X, W, V, Y, C[P + 14], j, 4259657740); Y = D(Y, X, W, V, C[P + 1], o, 2763975236); V = D(V, Y, X, W, C[P + 4], m, 1272893353); W = D(W, V, Y, X, C[P + 7], l, 4139469664); X = D(X, W, V, Y, C[P + 10], j, 3200236656); Y = D(Y, X, W, V, C[P + 13], o, 681279174); V = D(V, Y, X, W, C[P + 0], m, 3936430074); W = D(W, V, Y, X, C[P + 3], l, 3572445317); X = D(X, W, V, Y, C[P + 6], j, 76029189); Y = D(Y, X, W, V, C[P + 9], o, 3654602809); V = D(V, Y, X, W, C[P + 12], m, 3873151461); W = D(W, V, Y, X, C[P + 15], l, 530742520); X = D(X, W, V, Y, C[P + 2], j, 3299628645); Y = t(Y, X, W, V, C[P + 0], U, 4096336452); V = t(V, Y, X, W, C[P + 7], T, 1126891415); W = t(W, V, Y, X, C[P + 14], R, 2878612391); X = t(X, W, V, Y, C[P + 5], O, 4237533241); Y = t(Y, X, W, V, C[P + 12], U, 1700485571); V = t(V, Y, X, W, C[P + 3], T, 2399980690); W = t(W, V, Y, X, C[P + 10], R, 4293915773); X = t(X, W, V, Y, C[P + 1], O, 2240044497); Y = t(Y, X, W, V, C[P + 8], U, 1873313359); V = t(V, Y, X, W, C[P + 15], T, 4264355552); W = t(W, V, Y, X, C[P + 6], R, 2734768916); X = t(X, W, V, Y, C[P + 13], O, 1309151649); Y = t(Y, X, W, V, C[P + 4], U, 4149444226); V = t(V, Y, X, W, C[P + 11], T, 3174756917); W = t(W, V, Y, X, C[P + 2], R, 718787259); X = t(X, W, V, Y, C[P + 9], O, 3951481745); Y = K(Y, h); X = K(X, E); W = K(W, v); V = K(V, g) } var i = B(Y) + B(X) + B(W) + B(V); return i.toLowerCase() };
;
$(function () {
	LoadBasketControl();
});

function RefreshBasketControl() {
	LoadBasketControl();
};

function LoadBasketControl() {
	if (document.getElementById('basketPlaceholder') != null) {
		$.ajax({
			url: '/App/Info/BasketControl',
			method: 'POST'
		}).done(function (data) {
			document.getElementById('basketPlaceholder').innerHTML = data;
			NewMind.Dms.bindBasket();
		});
	}
};

function ClearBasket(type) {
	$.ajax({
		url: '/App/Info/Clear' + type + 'Basket',
		method: 'POST'
	}).done(LoadBasketControl);
}

function ClearProductBasket() {
	ClearBasket('Product')
}

function ClearOrganisationBasket() {
	ClearBasket('Organisation')
}
;
//Required to prevent the modal dialog from being obscured by the "topbar" and
//to centre the modal vertically within the browser window.
function centerModal() {
	$(this).css('display', 'block');
	var $dialog = $(this).find(".modal-dialog");
	if (window.parent != window) { // Inside an iframe
		positionElementBasedOnScrollPosition($dialog);

		// Ensure the frame is at least as tall as the dialog + the padding on each side
		var minHeight = $dialog.height() + (2 * (parseInt($dialog.css('top').replace('px', ''), 10) + 30)); // 30px for the top/bottom margin on dialogs
		resizeIframeToFitContent(window.frameElement, minHeight);

		// IE fix: bootstrap does a focus, which screws up the iframe by scrolling the parent window
		setTimeout(function () {
			$dialog.off('transitionend');		
		}, 350);
	} else { // Not inside an iframe
		// Position dialog based on browser window height
		var offset = ($(window).height() - $dialog.height()) / 2;
		$dialog.css("top", offset);
	}
}

$(function () {
	$('.modal').on('show.bs.modal', centerModal);
	$('.modal').on('hidden.bs.modal', function () { resizeIframeToFitContent(window.frameElement); })
});

$(window.parent || window).on("resize", function () {
	if (typeof jQuery !== 'undefined') {
		$('.modal').filter(':visible').each(centerModal);
	}
});

// Case 17632 - Fix for ckeditor/bootstrap compatiability bug 
// when ckeditor appears in a bootstrap modal dialog with
// uneditable / inaccessible textboxes and dropdowns in the Link dialog.
// http://stackoverflow.com/questions/14420300/bootstrap-with-ckeditor-equals-problems/
$.fn.modal.Constructor.prototype.enforceFocus = function () {
	modal_this = this
	$(document).on('focusin.modal', function (e) {
		if (modal_this.$element[0] !== e.target && !modal_this.$element.has(e.target).length
		&& !$(e.target.parentNode).hasClass('cke_dialog_ui_input_select')
		&& !$(e.target.parentNode).hasClass('cke_dialog_ui_input_text')) {
			modal_this.$element.focus()
		}
	})
};
;
var NewMind = window.NewMind || {};

NewMind.Buttons = function () {

	// TODO: Extract localisations

	var defaultPaddingForButton = 12;
	var spinnerPadding = 24;
	var widthAnimationDuration = 100;
	var resetButtonTimer = 1000;

	var inProgressTimer = null;
	var resetAfterTimer = null;
	var disableAfterTimer = null;

	function setButtonStatus(btn, buttonText, buttonShouldBeEnabled, showSpinner, resetAfterTime) {
		// Store original label if we haven't already.
		if (!btn.getAttribute('data-original-label'))
			btn.setAttribute('data-original-label', btn.textContent || btn.innerText);

		var $btn = $(btn);

		// Capture widths
		$btn.css('width', 'auto');
		var oldWidth = $btn.width();

		// Set text.
		// HACK: If the button/link has children, then changing the DOM will soetimes abort the action
		// see Case 14442; so if there are children, walk down :-/
		var $elm = $btn.find('*').last();
		if (!$elm.length)
			$elm = $btn;
		$elm.text(buttonText || btn.getAttribute('data-original-label'));

		// If we disable the immediately, Chrome will abort the submit (Case 14265).
		// Wrap in a timeout to avoid :-/
		disableAfterTimer = setTimeout(function () {
			btn.disabled = !buttonShouldBeEnabled;

			if (btn.tagName === 'A' && !buttonShouldBeEnabled) {
				btn.classList.add('disabled');
			}

			disableAfterTimer = null;
		}, 100);

		// Rig animation.
		var expectedPadding = (showSpinner ? spinnerPadding : 0) + defaultPaddingForButton;
		var currentPadding = parseInt($btn.css('padding-left').replace('px', ''), 10);
		var newWidth = $btn.outerWidth() + (expectedPadding - currentPadding);
		$btn.width(oldWidth);
		$btn.animate({ width: newWidth + 'px', paddingLeft: expectedPadding + 'px' }, widthAnimationDuration);

		// Set spinner visibility state.
		// Note: When displaying, wait till after width animation; when hiding, remove immediately.
		if (showSpinner)
			inProgressTimer = setTimeout(function () {
				$btn.addClass('with-spinner');
				inProgressTimer = null;
			}, widthAnimationDuration);
		else {
			// Clear the timer, in case we finished our callback before the spinner was added in.
			if (inProgressTimer)
				clearTimeout(inProgressTimer);
			inProgressTimer = null;
			$btn.removeClass('with-spinner');

			if (btn.tagName === 'A' && btn.classList.contains('disabled')) {
				btn.classList.remove('disabled');
			}
		}

		// If we want to auto-reset, do so in 1 second.
		if (resetAfterTime)
			resetAfterTimer = setTimeout(function () {
				NewMind.Buttons.resetButtonStatus(btn);
				resetAfterTimer = null;
			}, resetButtonTimer);
	}

	// Stops any timed actions & animations, clears any inlined styles and restores the initial state of the button.
	function revertButton(btn) {
		if (disableAfterTimer) { clearTimeout(disableAfterTimer); disableAfterTimer = null; }
		if (inProgressTimer) { clearTimeout(inProgressTimer); inProgressTimer = null; }
		if (resetAfterTimer) { clearTimeout(resetAfterTimer); resetAfterTimer = null; }

		var originalLabel = btn.getAttribute('data-original-label');

		if (originalLabel) {
			$(btn).text(originalLabel);
		}

		if (btn.classList.contains('with-spinner')) {
			btn.classList.remove('with-spinner');
		}

		if (btn.classList.contains('disabled')) {
			btn.classList.remove('disabled');
		}

		if (btn.hasAttribute('style')) {
			btn.removeAttribute('style');
		}

		$(btn).stop();
  }

	return {
		setButtonStatusSaving:		function (btn) { setButtonStatus(btn, NewMind.CommonTranslations.ButtonSaving, false, true, false); },
		setButtonStatusSaved:		function (btn) { setButtonStatus(btn, NewMind.CommonTranslations.ButtonSaved, true, false, true); },
		setButtonStatusSending:		function (btn) { setButtonStatus(btn, NewMind.CommonTranslations.ButtonSending, false, true, false); },
		setButtonStatusSent:		function (btn) { setButtonStatus(btn, NewMind.CommonTranslations.ButtonSent, true, false, true); },
		setButtonStatusCancelling:	function (btn) { setButtonStatus(btn, NewMind.CommonTranslations.ButtonCancelling, false, true, false); },
		setButtonStatusClosing:		function (btn) { setButtonStatus(btn, NewMind.CommonTranslations.ButtonClosing, false, true, false); },
		setButtonStatusLoggingIn:	function (btn) { setButtonStatus(btn, NewMind.CommonTranslations.ButtonLoggingIn, false, true, false); },
		setButtonStatusSearching:	function (btn) { setButtonStatus(btn, NewMind.CommonTranslations.ButtonSearching, false, true, false); },
		setButtonStatusCopying:		function (btn) { setButtonStatus(btn, NewMind.CommonTranslations.ButtonCopying, false, true, false); },
		setButtonStatusCreating:	function (btn) { setButtonStatus(btn, NewMind.CommonTranslations.ButtonCreating, false, true, false); },
		setButtonStatusDone:		function (btn) { setButtonStatus(btn, NewMind.CommonTranslations.ButtonDone, true, false, true); },
		setButtonStatusLoading: function (btn) { setButtonStatus(btn, NewMind.CommonTranslations.ButtonLoading, false, true, false); },
		resetButtonStatus: function (btn) { setButtonStatus(btn, null, true, false, false); },
		revertButton: function (btn) { revertButton(btn); }
	};
}();;
$(function () {
	wireUpDatePickers();
});

function wireUpDatePickers() {
	var autoDatePickerSelector = 'input.auto-date-picker:visible';

	if (typeof (setUserCulture) === "function") {
		setUserCulture();
	}

	// Wire up the datepicker for all on the page that have not been hooked up yet.
	wireUpDatePickersForSelector($(autoDatePickerSelector).not('.k-input'));
};

function wireUpDatePickersForSelector($autoDatePickerSelector)
{

	// Wire up the datepicker.
	$autoDatePickerSelector.kendoDatePicker({
		format: kendo.culture().calendar.patterns.d
	});

	// Wrap the kendo value, so we don't have to hard-code kendo everywhere.
	$autoDatePickerSelector.each(function () {
		var elm = $(this);
		var dp = elm.data('kendoDatePicker');

		// Provide nm-date-picker API
		elm.data('nm-date-picker', {
			value: function (val) {
				return dp.value(val)
			},
			enable: function (enable) {
				return dp.enable(enable)
			}
		});

		// Support show/hide on focus/blur
		elm.on('focus', function () { dp.open(); });
		elm.on('blur', function () { dp.close(); });
	});
};
$(function () {
	resolveDates();
});

function getLocaleTime(date, languageCulture) {
	// Jira EUDMS-198 PR: "toLocaleTimeString" renders UTC / en-GB hours zero indexed, despite no 12-hour time format in the 
	// world using this. This just needs to be (unfortunately) manually corrected, as at least we can determine the bad format.
	if (['en-gb', 'utc'].indexOf((languageCulture || '').toLowerCase()) !== -1) {
		return date.toLocaleTimeString(languageCulture, { hour: '2-digit', minute: '2-digit', second: '2-digit' });
	}
	return date.toLocaleTimeString(undefined, { hour12: true });
}

function getLocaleForDate($element) {
	// PR (HACK): As Chrome's default language is en-US we need to make sure to swap it out for en-GB.
	// UPDATE (06/04/2020) PR: Only override if 'override-us-with-gb' data property dictates so.
	var overrideUsWithGb = ($element.data('override-us-with-gb') || '').toLowerCase() === 'true';
	return overrideUsWithGb && navigator.language === 'en-US'
		? 'en-GB' : navigator.language;
}

function resolveDates() {
	$('.format-date.unresolved').each(function () {
		var $this = $(this);

		var userBrowserLanguage = getLocaleForDate($this);
		// Convert the value (which is milliseconds since 1st Jan 1970 00:00:00 UTC) to
		// a JS date.
		var d = new Date(parseInt($this.text(), 10));

		// If this wasn't valid; bail out. Something has gone wrong!
		if (isNaN(d))
			return;

		var now = new Date();
		var formattedDate = '';

		// For today, output the time (or in browsers without toLocaleTimeString, the word "Today").
		if (d.getFullYear() == now.getFullYear() && d.getMonth() == now.getMonth() && d.getDate() == now.getDate()) {
			if (d.toLocaleTimeString) {
				formattedDate = getLocaleTime(d, userBrowserLanguage);
				if (d.toTimeString)
					$this.prop("title", d.toTimeString());
			}
			else
				formattedDate = NewMind.CommonTranslations.Today;
		}
		// If not today, and we support using the Users formatting, do so (IE9+).
		else if (d.toLocaleDateString) {
			formattedDate = d.toLocaleDateString(userBrowserLanguage);
			if (d.toLocaleDateString && d.toTimeString)
				$this.prop("title", d.toLocaleDateString(userBrowserLanguage) + ' ' + d.toTimeString(userBrowserLanguage));
		}
		// Otherwise, we don't support formatting (IE8!), so just fall back to YYYY-MM-DD to avoid complicated manual formatting.
		else
			formattedDate = d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate();

		// Write the formatted date back into the HTML.
		$this.text(typeof (formattedDate).toLowerCase() == 'string'
			? formattedDate.toUpperCase() : '');

		// Remove unresolved so that the value appears (and we don't format again).
		$this.removeClass('unresolved');
	});

	$('.format-datetime.unresolved').each(function () {
		var $this = $(this);
		var onlyDate = $this.hasClass('date-only');
		var userBrowserLanguage = getLocaleForDate($this);

		// Convert the value (which is milliseconds since 1st Jan 1970 00:00:00 UTC) to
		// a JS date.
		var d = new Date(parseInt($this.text(), 10));

		// If this wasn't valid; bail out. Something has gone wrong!
		if (isNaN(d))
			return;

		var formattedDate = '';

		// If we support using the Users formatting, do so (IE9+).
		if (d.toLocaleDateString && d.toTimeString)
			formattedDate = d.toLocaleDateString(userBrowserLanguage) + (!onlyDate ? ' ' + d.toTimeString(userBrowserLanguage) : '');
		// Otherwise, we don't support formatting (IE8!), so just fall back to YYYY-MM-DD to avoid complicated manual formatting.
		else
			formattedDate = d.getFullYear() + '-' + d.getMonth() + '-' + d.getDate();

		// Write the formatted date back into the HTML.
		$this.text(formattedDate);

		// Remove unresolved so that the value appears (and we don't format again).
		$this.removeClass('unresolved');
	});
};
function formUnloadPrompt(formSelector, unloadMessage, strInputSelector) {

	var inputSelector = "input:visible, textarea:visible";
	if (strInputSelector) {
		inputSelector = strInputSelector;
	}
	var formBefore = $(formSelector).find(inputSelector).serialize();
	var formAfter;
	var formSubmit = false;

	// Detect Form Submit
	$(formSelector).submit(function () {
		formSubmit = true;
	});

	// Handle Form Unload
	window.onbeforeunload = function () {
		if (formSubmit) return;
		formAfter = $(formSelector).find(inputSelector).serialize();
		if (formBefore != formAfter) {
			return unloadMessage;
		}
	};
};
function resizePmsIframeToFitContent(frame) {
	resizeIframeToFitContent(frame, 350);
}

function resizeIframeToFitContent(frame, minSize) {
	minSize = minSize || 20;

	// Make sure jQuery is on the inner page (this isn't the case, for example, for a Yellow Screen of Death!)
	if (frame && frame.contentWindow) {

		// Measuring the document height is unreliable if the frame is already bigger than the content. So, try some
		// different elements (in preference order) and just fallback to resizing the frame small if we don't find one.

		var pageContainer = frame.contentWindow.document.querySelectorAll(".full-page-height")[0];

		if (pageContainer === undefined)
			pageContainer = frame.contentWindow.document.getElementById("page");

		if (pageContainer === null)
			pageContainer = frame.contentWindow.document.getElementById("pagecontainer");

		if (pageContainer === null)
			$(frame).height(20);

		var fixFrame = function () {

			try {
				// If the frame hasn't finished loading; this .height() call my fail (Case 18689).
				// Best we can do is just ignore :(
				var frameHeight = 0;
				try {
					frameHeight = $(frame.contentWindow.document).height();
				}
				catch (e) {
					return;
				}

				$pageContainer = $(pageContainer);

				// See if we can get more accurate height by checking pageContainer's css. 
				if ($pageContainer.length === 1) {
					var pageContainerHeight = parseInt($pageContainer.css('height').replace('px', ''), 10);
					if (pageContainerHeight > 0) {
						frameHeight = pageContainerHeight;
					}
				}
			
				var theHeight = Math.max(frameHeight, minSize);
				if (frame.contentWindow.$)
					frame.contentWindow.$('html').css('overflow-y', 'hidden');
				frame.style.height = theHeight + 'px';
			}
			catch (e) {
				// HACK: This can sometimes fail (eg. if frame is still loading, happens lots in Channel Validation due to the way
				// validation mode works), so just assume (given that we try ita few times) it probably works.
			}
		};

		setTimeout(fixFrame, 50);
		setTimeout(fixFrame, 100);
		setTimeout(fixFrame, 250);
	}
}

function resizeIframeToFitContentWidth(frame) {
	// Make sure jQuery is on the inner page (this isn't the case, for example, for a Yellow Screen of Death!)
	if (frame && frame.contentWindow && frame.contentWindow.$) {
		var frameBody = frame.contentWindow.document.body;

		frame.contentWindow.$('html').css('overflow-x', 'hidden');

		frame.style.width = frameBody.offsetWidth + 'px';
	}
};
$(function () {
	$('.homepage-block')
		.on('mouseover', function () {
			var $this = $(this);
			var $img = $this.children('img');

			$this
				.addClass('hover')
				.css('backgroundColor', this.getAttribute('data-color'));
			$img
				.attr('src', $img.attr('src').replace('.png', '_Reversed.png'));

		})
		.on('mouseout', function () {
			var $this = $(this);
			var $img = $this.children('img');

			$this
				.removeClass('hover')
				.css('backgroundColor', '');
			$img
				.attr('src', $img.attr('src').replace('_Reversed.png', '.png'));
		});
});;
function jqGridBuildPaging(container, grid) {
	$(container).addClass('text-center');
	$(container).html('\
			<ul class="pagination">\
				<li><a href="#" class="pagelink-first" onclick="return false;"></a></li>\
				<li><a href="#" class="pagelink-1" onclick="return false;"></a></li>\
				<li><a href="#" class="pagelink-2" onclick="return false;"></a></li>\
				<li><a href="#" class="pagelink-3" onclick="return false;"></a></li>\
				<li><a href="#" class="pagelink-4" onclick="return false;"></a></li>\
				<li><a href="#" class="pagelink-5" onclick="return false;"></a></li>\
				<li><a href="#" class="pagelink-last" onclick="return false;"></a></li>\
			</ul>\
		');
	$('a', container).click(function () {
		grid.trigger("reloadGrid", [{ page: this.getAttribute('data-page-to-link') }]);
		jqGridUpdatePaging(container, grid);
	});
	jqGridUpdatePaging(container, grid);
}

function jqGridUpdatePaging(container, grid) {
	function Clamp(i, min, max) {
		if (i < min)
			return min;
		if (i > max)
			return max;
		return i;
	}

	var currentPage = parseInt(grid.getGridParam('page'), 10);
	var totalPages = parseInt(grid.getGridParam('lastpage'), 10);
	var clampPage = function (i) { return Clamp(i, 1, totalPages); };
	var endPage = clampPage(currentPage + 2);
	var startPage = clampPage(endPage - 4); // -4 for inclusive
	endPage = clampPage(startPage + 4); // Recalculate, as we might go start+5 now (eg. if we're at page 1).

	var pageLink = 1;
	var pageToLinkTo = startPage;
	while (pageLink <= 5) {
		var showLink = pageToLinkTo <= endPage;
		var isCurrentPage = pageToLinkTo == currentPage;
		var $pageLink = $('.pagelink-' + pageLink, container);

		$pageLink.text(pageToLinkTo); // Set page number on box
		$pageLink[showLink ? 'show' : 'hide'](); // Set link visibility
		$pageLink.parent()[isCurrentPage ? 'addClass' : 'removeClass']('active'); // Set active for current page

		// Set the data-page-to-link for the click handler.
		$pageLink[0].setAttribute('data-page-to-link', pageToLinkTo);

		pageToLinkTo++;
		pageLink++;
	}

	// First link
	$('.pagelink-first', container).prop('disabled', currentPage == 1);
	$('.pagelink-first', container).parent()[currentPage == 1 ? 'addClass' : 'removeClass']('disabled');
	$('.pagelink-first', container)[0].setAttribute('data-page-to-link', 1);
	$('.pagelink-first', container).text(NewMind.CommonTranslations.First);

	// Last link
	$('.pagelink-last', container).prop('disabled', currentPage == totalPages);
	$('.pagelink-last', container).parent()[currentPage == totalPages ? 'addClass' : 'removeClass']('disabled');
	$('.pagelink-last', container)[0].setAttribute('data-page-to-link', totalPages);
	$('.pagelink-last', container).text(NewMind.CommonTranslations.Last);
}
;
var Simpleview = window.Simpleview || {};

// This is being binded to from TypeScript, so pls be careful when changing.
Simpleview.MegaNav = function () {
  var setMegaNavSearchState = function (container) {
    container.classList.add('searching');
  };

  // Ideally all this would be inside a form embedded in the mega nav, but the PMS doesn't like multiple forms in the page and causes saves to be redirec to the "Page Expired" page.
  // To combat this the form is dynamically created upon submission of the search form.
  var performMegaNavSearchIfRequired = function (ev, formUrl, searchType, fieldName) {
    var searchText = (ev.currentTarget.value || '').trim();
    if ((ev.keyCode == 13) // Enter key pressed.
      && (searchText != '')) {
      // Set search state to 'Searching...' on the form.
      var searchContainer = getAncestorWithClassName(ev.currentTarget, 'nav-search');
      if (!searchContainer) {
        return;
      }
      Simpleview.MegaNav.setMegaNavSearchState(searchContainer);

      // Create a form to append the search details to and submit through to the advanced search. 
      var formElement = document.createElement('form');
      formElement.style.display = 'none'; // Hide the form, as we need to append it to the document, for the submission to be against the current document.

      formElement.setAttribute('target', '_top');

      formElement.setAttribute('method', 'get');
      formElement.setAttribute('action', formUrl);

      // Add search type "hidden" field.
      var searchTypeElement = document.createElement('input');
      searchTypeElement.type = 'hidden';
      searchTypeElement.name = 'searchType';
      searchTypeElement.value = searchType;
      formElement.appendChild(searchTypeElement);

      // Add the search type "text" field.
      var searchTextElement = document.createElement('input');
      searchTextElement.type = 'text';
      searchTextElement.name = fieldName;
      searchTextElement.value = searchText;
      formElement.appendChild(searchTextElement);

      document.body.appendChild(formElement);

      // Post the details to the appropriate endpoint.
      formElement.submit();
    }
  };

  function getAncestorWithClassName(element, className) {
    while (element) {
      if (element.classList && element.classList.contains(className)) {
        return element;
      }
      if (!element || element.tagName.toLowerCase() === 'body') {
        return null;
      }
      element = element.parentElement;
    }
  }

  return {
    setMegaNavSearchState: setMegaNavSearchState,
    performMegaNavSearchIfRequired: performMegaNavSearchIfRequired
  };
}();;
// This function is designed to run as the main PMS Actions menu expands.
function mergeActionMenus() {
	// Get references to the main (product-level) Actions menu.
	var mainActionMenu = $('#pms-main-action-menu');
	var menuActionMenuUL = mainActionMenu.find('ul:first');

	// Get references to any tab-level Actions menus.
	// HACK: :visible doesn't work properly in IE8 (looks like a jQuery bug, which they claim to have mixed many times)...
	// So instead, get the active tab and use that!
	// This is what we want to do, but doesn't work in IE
	//     var activeTab = $('.ajax__tab_panel').filter(":visible");
	// This is the IE8 safe method...

	// NOTE: This code is used in both contacts and products screens. For contacts, product will be undefined.
	var $activeTab = $();
	if (typeof product !== "undefined") {
		var $pmsDetails = $(".pmsDetails");
		if($pmsDetails.css("display") == "block")
			$activeTab = $pmsDetails;
		else {
			var allTabs = document.querySelectorAll(".ajax__tab_panel");
			for (var i = 0; i < allTabs.length; i++)
				if (allTabs[i].style.visibility == "visible")
					$activeTab = $(allTabs[i]);
		}
	}

	var samePageActionMenus = $activeTab.find('.mergable-action-menu');
	var iframeActionMenus = $activeTab.find('iframe').contents().find('.mergable-action-menu');
	var iFrameNonTabActionMenus = $('.has-mergable-action-menu').find('iframe').contents().find('.mergable-action-menu');

	// Remove any links from the menu that were inserted previously.
	mainActionMenu.find('.tab-specific').remove();

	// Function that clondes actions from the old tab into the new.
	var insertLink = function (i, elm) {
		// Get references to LIs and As.
		var oldLI = $(elm);
		var oldA = oldLI.find('a');
		var newLI = $(document.createElement('li'));
		var newA = $(document.createElement('a'));

		// Copy over important properties.
		newA.text(oldA.text());
		newA.attr('href', '#');
		newA.attr('style', oldA.attr('style'));
		newA.attr('class', oldA.attr('class'));
		newLI.attr('style', oldLI.attr('style'));
		newLI.attr('class', oldLI.attr('class')).addClass('tab-specific');

		// Handle clicks (don't copy, as we need it to fire in the context if the iframe, if appropriate).
		newA.click(function (event) {
			oldA[0].click();
			event.preventDefault();
		})

		// Add to DOM
		newLI.append(newA);
		menuActionMenuUL.prepend(newLI);
	}

	var divider = $('<li role="presentation" class="divider tab-specific"></li>');
	menuActionMenuUL.prepend(divider);

	$(samePageActionMenus.find('li').get().reverse()).each(insertLink);
	$(iframeActionMenus.find('li').get().reverse()).each(insertLink);
	$(iFrameNonTabActionMenus.find('li').get().reverse()).each(insertLink);

	// If all of the tab-specific items we added are "display: none" then we don't need the divider.
	// NOTE: This filtering is not flawless, since there are other ways they could be hidden.
	// This was added for Case 37879.
	if (menuActionMenuUL.children('.tab-specific').filter(function () { return $(this).css('display') != 'none'; }).length <= 1) // Since the divider has '.tab-specific', if there's only 1 item (the divider) it should still be hidden.
		divider.hide();
};
var NewMind = window.NewMind || {};
NewMind.Products = NewMind.Products || {};

NewMind.Products.FacilityTab = function () {
	var currentCode = '';
	var pageIdSelector = '#facTab';

	function appendFacility(key, name, id, readonly, isAllowedNotes, checked, hasNotes) {
		var htmlEncodedName = $('<div/>').text(name).html();
		var htmlEncodedNotesLabel = $('<div/>').text(product.Notes).html();

		currentCode += '\
			<div class="facility-item" id="facility-item-' + key + '" title="' + id +'">\
				<div class="checkboxcell"><input name="fac-fac" onclick="product.facility.tn(this);" type="checkbox"' + (checked === true ? ' checked="checked"' : '') + (readonly === true ? ' disabled ' : '') + ' value=' + parseInt(key, 10) + '></div>\
				<div class="checkbox-label">\
					<label class="facilityName">' + htmlEncodedName + '</label>\
					<label class="idLabel">[' + id  + ']</label>\
					<a ' + (checked !== true || readonly ? ' disabled="disabled"' : '') + ' class="note' + (hasNotes === true ? ' hasN' : '') + '" onclick="product.facility.notes(this); return false;" href="#" style="' + (isAllowedNotes ? '' : 'visibility: hidden') + '">' + htmlEncodedNotesLabel + '</a>\
				</div>\
			</div>\
		';
	}

	function renderFacilities(currentFacSet) {
		currentFacSet.append(currentCode);
		currentFacSet.append('<div class="clearfix"></div>');
		currentCode = '';
	}

	var filterTimeout = 0;
	function textFilterChanged() {
		clearTimeout(filterTimeout);
		filterTimeout = setTimeout(applyFilters, 200);
	}

	function applyFilters() {
		var filter = document.getElementById('facilityFilter').value;
		var showAll = $('#showAll', pageIdSelector).hasClass('active');
		var facilitySetFilter = document.getElementById('facilitySetFilter').value;

		// Stash the toggle state in hidden input, as it's not in a form that posts back (Case 19102).
		document.getElementById('facilityShowSelectedFilter').value = showAll ? '' : 'true';

		$('.select-all span.unfiltered', pageIdSelector).hide();
		$('.deselect-all span.unfiltered', pageIdSelector).hide();
		$('.select-all span.filtered', pageIdSelector).hide();
		$('.deselect-all span.filtered', pageIdSelector).hide();
		$('.noSelectedResults', pageIdSelector).hide();
		$('.noResults', pageIdSelector).hide();

		// If cleared filters, just show everything and bail quickly.
		if (filter === '' && showAll && !facilitySetFilter) {
			$('.facility-item', pageIdSelector).show().parents('.facCont').show();
			$('.select-all span.unfiltered', pageIdSelector).show();
			$('.deselect-all span.unfiltered', pageIdSelector).show();			
			return;
		}
		
		$('.select-all span.filtered', pageIdSelector).show();
		$('.deselect-all span.filtered', pageIdSelector).show();

		// Otherwise, start with everything hidden.
		$('.facility-item', pageIdSelector).hide().parents('.facCont').hide();

		// Set the root  to check.
		var root = facilitySetFilter
			? $('h4:iequals(\'' + facilitySetFilter.replace(/(:|\.|\[|\]|,)/g, "\\$1") + '\')', pageIdSelector).closest('.facCont')
			: $(pageIdSelector);

		var matchingFacilityItems;
		if (filter !== '') {
			// Find facilities with this text.
			matchingFacilityItems = $('label:icontains("' + filter.replace(/(:|\.|\[|\]|,)/g, "\\$1") + '")', root).closest('.facility-item');
			// Add any facilities nside a group with this text.
			matchingFacilityItems = matchingFacilityItems.add($('h4:icontains("' + filter.replace(/(:|\.|\[|\]|,)/g, "\\$1") + '")', root).closest('.facCont').find('.facility-item'));
		}
		else {
			matchingFacilityItems = $('.facility-item', root);
		}
		
		// Further filter to if we're only showing selected.
		if (!showAll) {
			matchingFacilityItems = matchingFacilityItems.find('input[type=checkbox]:checked').closest('.facility-item');

			if (matchingFacilityItems.length == 0)
				$('.noSelectedResults', pageIdSelector).show();
		}

		// Show all of the facilities and their parent groups.
		matchingFacilityItems.show().parents('.facCont').show();

		if (matchingFacilityItems.length == 0 && showAll)
			$('.noResults', pageIdSelector).show();
		
	}

	function toggleCheckedVisible(btn, all) {
		$(btn).closest('.btn-group').find('.btn.active').removeClass('active');
		$(btn).addClass('active');
		applyFilters();
		
		// foreach facSet, select visible facility-items
		$('div[id^="facSet"]', pageIdSelector).each(function () {			
			var $that = $(this);
			var $facilityItems = $('.facility-item:visible', $that);

			// if the number of visible facility-items is 1 remove column-count
			if ($facilityItems.length == 1) {
				$that.css('column-count', '')
				.css('-webkit-column-count', '')
				.css('-moz-column-count', '');
			} else {
				// else set column count to default of 3
				$that.css('column-count', '3')
				.css('-webkit-column-count', '3')
				.css('-moz-column-count', '3');
			}			
		});
	}

	function jumpTo(facilityKey, facilityName) {
		var elm = $('#facility-item-' + facilityKey, pageIdSelector);
		if (elm.length !== 0) {
			// Ensure it's visible.
			elm.show().parents('.facCont').show();
			// Scroll to it.
			ScrollTo(elm);
			// Flash the background so it's obvious which it is.
			elm.stop().css("background-color", "#f2dede");
			setTimeout(function () { elm.animate({ backgroundColor: "" }, 2000); }, 500);
		}
		else
			NMAlert(product.facilityIsNotAvailableForSelection);
	}

	function populateFacSets() {
		var facSetHeaders = $('h4', pageIdSelector);
		var facSetFilter = $('#facilitySetFilter', pageIdSelector);
		
		$.each(facSetHeaders, function (i, facSetHeader) {
			facSetFilter
				.append(
					$("<option></option>").text($(facSetHeader).text())
				);
		});

		facSetFilter.chosen({
			width: '300px'
		});
	}

	function toggleAllFacilities(lnk, check) {	
		var $facSet = $(lnk).parents(".facSetCont");
		var $checkboxSet = $('.checkboxset', $facSet);
		var $inputs;
		if (check) {
			$inputs = $('.checkboxcell:not(.checked):visible', $checkboxSet);
		} else {
			$inputs = $('.checkboxcell.checked:visible', $checkboxSet);
		}
		$('input[type=checkbox]', $inputs).click();
	}
	
	return {
		toggleCheckedVisible: toggleCheckedVisible,
		textFilterChanged: textFilterChanged,
		appendFacility: appendFacility,
		renderFacilities: renderFacilities,
		jumpTo: jumpTo,
		populateFacSets: populateFacSets,
		facSetFilterChanged: applyFilters,
		applyFilters: applyFilters,
		toggleAllFacilities: toggleAllFacilities
	};
}();
;
function ScrollTo($object) {
	$('html, body').animate({
		scrollTop: Math.max($object.offset().top - 200, 0) // Take 200 off to allow for top bar that obscures things and so it's not right at the top of the window.
	});
}

// Scroll To Top
jQuery(document).ready(function () {
	var offset = 220;
	var duration = 500;
	jQuery(window).scroll(function () {
		if (jQuery(this).scrollTop() > offset) {
			jQuery('.scroll-to-top').fadeIn(duration);
		} else {
			jQuery('.scroll-to-top').fadeOut(duration);
		}
	});

	jQuery('.scroll-to-top').click(function (event) {
		event.preventDefault();
		jQuery('html, body').animate({ scrollTop: 0 }, duration);
		return false;
	});
});

// Scroll To Bottom
jQuery(window).load(function () {

	scrollToBottom = function() {
		var duration = 500;
		var documentHeight = $(document).height();
		var windowHeight = $(window).height();
		if (documentHeight > windowHeight) {
			$("html, body").animate({ scrollTop: documentHeight - windowHeight }, duration);
		}
	};
});
;
jQuery(document).ready(function () {
	var offset = 220;
	var duration = 500;
	jQuery(window).scroll(function () {
		if (jQuery(this).scrollTop() > offset) {
			jQuery('.scroll-to-top').fadeIn(duration);
		} else {
			jQuery('.scroll-to-top').fadeOut(duration);
		}
	});

	jQuery('.scroll-to-top').click(function (event) {
		event.preventDefault();
		jQuery('html, body').animate({ scrollTop: 0 }, duration);
		return false;
	});
});;
// Show the checkbox colum
function showCheckboxes() {
	$('.checkboxcell').removeClass('hidden');
}

// Hide the checkbox column
function hideCheckboxes() {
	$('.checkboxcell').addClass('hidden');
}

$(function () {
	// Wire up checkboxes to set CSS class when toggled
	$(document).on("click", ":not(th).checkboxcell", cellClick);

	// Wire up heading checkbox to toggle all
	$(document).on("click", "th.checkboxcell", headerRowCellClicked);

	// Wire up basket toggle
	$(function () {
		$("td .basket").on("click", function () {
			$(this).toggleClass("inbasket");
		});
	});

	// Ensure all custom checkbox cells have a checked class if their hidden checkbox is checked
	$(".checkboxcell:not(.checked) input[type=checkbox]:checked").closest(".checkboxcell").addClass("checked");
	// And a disabled class if they're disabled
	$(".checkboxcell:not(.disabled) input[type=checkbox]:disabled").closest(".checkboxcell").addClass("disabled");

});

function initDynamicCheckboxes(elements) {
	$('.checkboxcell').each(function () {
		$(this).attr('tabindex', 0);
	});
	$('.checkboxcell').unbind('keydown');
	$('.checkboxcell').keydown(function(e) {
		if (e.which == 13) {
			toggleCustomCheckbox($(this));
			return false;
		}		
	});

	// Add checked class for those with checked checkboxes
	var elms = elements ? $(".checkboxcell:not(.checked) input[type=checkbox]:checked", $(elements)) : $(".checkboxcell:not(.checked) input[type=checkbox]:checked");
	elms.closest(".checkboxcell").addClass("checked");

	// Remove checked class from those with un-checked checkboxes
	var elms = elements ? $(".checkboxcell.checked input[type=checkbox]:not(:checked)", $(elements)) : $(".checkboxcell.checked input[type=checkbox]:not(:checked)");
	elms.closest(".checkboxcell").removeClass("checked");

	// Add disabled state for those with disabled checkboxes
	var elms2 = elements ? $(".checkboxcell:not(.disabled) input[type=checkbox]:disabled", $(elements)) : $(".checkboxcell:not(.disabled) input[type=checkbox]:disabled");
	elms2.closest(".checkboxcell").addClass("disabled");

	// Remove disabled state from those with enabled checkboxes
	var elms3 = elements ? $(".checkboxcell.disabled input[type=checkbox]:not(:disabled)", $(elements)): $(".checkboxcell.disabled input[type=checkbox]:not(:disabled)");
	elms3.closest(".checkboxcell").removeClass("disabled");
}

// Fixes up styles for dynamically injected tablestables and checkboxes (JQGrid)
function initGrid(elements) {
	combineJQGridTables(elements);

	setupTableCheckboxes(elements);

	fixTableCssClasses(elements);
}

function combineJQGridTables(elements) {
	// HACK: JQGrid creates *TWO* tables, one for header, one for data. This is proving too difficult to style, so we're going to
	// mash them together.
	var headerTable = $(elements).find("table.ui-jqgrid-htable")[0];
	var dataTable = $(elements).find("table.ui-jqgrid-btable")[0];
	$(dataTable).append($(headerTable).children("thead"));

	// Remove the old table
	$(headerTable).remove();
}

function setupTableCheckboxes(elements) {
	// Add checkbox cell to parent of all checkboxes so they'll have the correct CSS applied
	$(elements).find("input[type=checkbox]").each(function () { $(this).closest("th, td").addClass("checkboxcell"); })

	initDynamicCheckboxes(elements);
}

function fixTableCssClasses(elements) {
	// Apply the main table classes
	$(elements).find("table:not(.ui-pg-table)").addClass("table table-hover");

	// Remove the JQ widget, as this has some styling (BG colour, border)
	$(elements).children(".ui-widget").removeClass("ui-widget");
}


function cellClick() {
	toggleCustomCheckbox($(this));

	// If we were unticked, might need to untick a thead checkbox
	if (!$(this).hasClass("checked"))
		setCustomCheckboxUnchecked($("th.checkboxcell", $(this).closest('table')));
}

function headerRowCellClicked() {
	toggleCustomCheckbox($(this));

	// Copy our setting to all others
	var table = $(this).closest('table');

	// HACK: In its infinite wisdom, JQGrid creates *TWO* tables; one for header and one for body >:O(
	if (table.hasClass('ui-jqgrid-htable')) {
		table = table.closest('.ui-jqgrid-view').find('.ui-jqgrid-bdiv');
	}

	if ($(this).hasClass("checked"))
		setCustomCheckboxChecked($(".checkboxcell:not(.disabled)"));
	else
		setCustomCheckboxUnchecked($(".checkboxcell:not(.disabled)", table));
}

function setCustomCheckboxChecked(elms) {
	elms.addClass("checked");
	$("input[type=checkbox]:not(:disabled)", elms).prop("checked", true)
}
function setCustomCheckboxUnchecked(elms, tick) {
	elms.removeClass("checked");
	$("input[type=checkbox]:not(:disabled)", elms).prop("checked", false)
}
function toggleCustomCheckbox(elm, tick) {
	if (elm.hasClass("disabled") || elm.find("input[type=checkbox]:disabled").length >= 1)
		return;

	// Toggle custom graphic
	elm.toggleClass("checked");

	var inputs = $("input[type=checkbox]", elm)
		.prop("checked", elm.hasClass("checked")); // Set the checked state of checkbox to match custom graphic

	inputs.triggerHandler('click'); // Fire the original click handler, so all click events fire as expected
	inputs.triggerHandler('change'); // Fire the original change handler, so all click events fire as expected
};
// In IE, if you use onBeforeUnload to prompt the user, and the user clicks Cancel; then
// "Unspecified error" is shown if the thing that triggered the unload is a doPostBack or location.href.
// http://www.telerik.com/forums/cancel-on-onbeforeunload-in-ie-6-7-create-js-unspecified-error
// This is a dirty fix from http://tinisles.blogspot.co.uk/2005/10/onbeforeunload-throwing-errors-in-ie.html

var __oldDoPostBack = null;

$(function () {
	if (typeof (__doPostBack) !== 'undefined') {
		function CatchExplorerError(eventTarget, eventArgument) {
			try {
				return __oldDoPostBack(eventTarget, eventArgument);
			} catch (ex) {
				if (ex.message.indexOf('Unspecified') == -1) {
					throw ex;
				}
				else {
					// Silently do nothing, because this error is "expected" :(
				}
			}
		}

		__oldDoPostBack = __doPostBack;
		__doPostBack = CatchExplorerError;
	}
});;
$(function () {
	// If this page has databinding; attempt to show warning.
	var hasDataBinding = $('xml').length > 0;

	if (hasDataBinding)
		showDatabindingWarningIfUnsupported(0);
});

function supportsDatabinding() {
	// Ceate an XML element, and see if it has an XMLDocument property.
	var container = document.createElement('xml');
	document.body.appendChild(container);
	var browserSupportsDatabinding = !!container.XMLDocument;
	document.body.removeChild(container);

	return browserSupportsDatabinding;
}

function showDatabindingWarningIfUnsupported(attemptNumber) {
	// If browser supports data binding, bail out.
	if (supportsDatabinding())
		return;

	if (attemptNumber < 10) {
		setTimeout(function () { showDatabindingWarningIfUnsupported(attemptNumber + 1) }, 100);
	}
	else
	{
		//show warning
		var screenAlert = document.createElement('div');
		screenAlert.className = 'full-screen-warning';

		var box = document.createElement('div');
		box.className = 'box';

		var heading = document.createElement('h1');
		$(heading).text(NewMind.CommonTranslations.UnsupportedBrowserTitle);

		var message = document.createElement('div');
		$(message).text(NewMind.CommonTranslations.UnsupportedBrowserDatabindingText);

		box.appendChild(heading);
		box.appendChild(message);
		screenAlert.appendChild(box);
		document.body.appendChild(screenAlert);
	}
};
