PageSourceSearch

https://tix.cinefestoz.com/js/browse.js

js cinefestoz.com collected 2026-09-24 21:43:40 UTC 59,365 bytes, 1,648 lines download raw bytes

1
2var calendar = true;
3
4var sortMode = 2;// date/time
5var view = 1;// Visual
6var h3s = true;
7var ft_currentMenu = "";
8var selectedOptions = new Array();
9var sessionDates = new Array();
10var attributeGroups, venues, items;
11var throttled = false, throttleIndex = 1, throttleCount = 10;
12var optionSelected = false;
13var pageTitle = document.title;
14var useLocalStorage = false;
15var searchDescription = true;
16var searchOr = null;// true / false / null (dynamically worked out)
17var searchAND = true;// Setting controls this
18var calloutToggle = true;
19var wishlist = true, ws = true, childEvents = false;
20
21
22// #mark ------ Initialisation  ------
23
24var browseInitComplete = false;
25var browseMode = "Browse";
26var label = "Event";
27var image = "browse";
28
29var x = document.querySelectorAll("[ft-data-label]");
30if (x && x.length > 0) label = x[0].getAttribute("ft-data-label");
31
32if (window.addEventListener) {
33	window.addEventListener("load", ft_initBrowse, false);
34} else if (window.attachEvent) {
35	window.attachEvent("onload", ft_initBrowse);
36}
37if (document.readyState === "complete") setTimeout(function () { ft_initBrowse(); }, 100);
38
39function logBrowse(s) {
40	ferve.log(s, "border-left:2px solid purple;padding-left: 4px;");
41}
42
43var script = document.createElement("script");
44script.type = "text/javascript";
45script.src = (typeof ferve != "undefined" ? ferve.url : "../../") + "js/echo.js";
46document.getElementsByTagName("head")[0].appendChild(script);
47script.onreadystatechange = function () {
48	if (this.readyState == "complete" || this.readyState == "loaded") {
49		ft_initEcho();
50	}
51};
52script.onload = function () {
53	logBrowse("echo.js script.onload");
54	ft_initEcho();
55};
56
57var scrollY = 0;
58function ft_scrollBrowse(event) {
59	scrollY = (document.all ? document.scrollTop : window.pageYOffset);
60	if (browseMode == "Browse") ft_browseOptionsSave();
61	echo.render();
62}
63
64function ft_initBrowse(e) {
65	logBrowse("ft_initBrowse()");
66
67	if (!e.isTrusted) {
68		logBrowse("exiting !e.isTrusted", "info");
69		return;
70	}
71
72	var js = document.getElementById("ft_browseJS");
73	if (js) {
74		var src = js.getAttribute("src").replace("/js/browse.js", "");
75		var urlSearchParams = new URLSearchParams(src);
76		var sa = urlSearchParams.get("sa");
77		if (sa) searchAND = (sa == "1");
78	}
79
80	var x = document.getElementById("ft_events");
81	if (x && x.className.indexOf("ft_grid1") != -1) image = "thumbnail";
82
83	if (window.addEventListener) {
84		window.addEventListener("scroll", ft_scrollBrowse, false);
85	} else if (window.attachEvent) {
86		window.attachEvent("onscroll", ft_scrollBrowse);
87	}
88
89	if (browseMode == "Venues" || browseMode == "Exchange") return;
90
91	if (document.URL.indexOf("/Wishlist") > -1) {
92		browseMode = "Wishlist";
93		useLocalStorage = false;
94		if (ferve.visitor.loggedIn != "1") {
95			var x = document.getElementById("ft_accountMenu");
96			if (x) x.style.display = "none";
97		}
98	}
99
100	if (document.URL.indexOf("/Play") > -1) {
101		browseMode = "Play";
102		useLocalStorage = false;
103	}
104
105	if (document.URL.toLowerCase().indexOf("/merchandise") > -1) browseMode = "Merchandise";
106	if (document.URL.toLowerCase().indexOf("/passes") > -1) browseMode = "Passes";
107	if (document.URL.toLowerCase().indexOf("/vouchers") > -1) browseMode = "Vouchers";
108	if (document.URL.toLowerCase().indexOf("/memberships") > -1) browseMode = "Memberships";
109	if (document.URL.toLowerCase().indexOf("/donate") > -1) browseMode = "Donations";
110	if (document.URL.toLowerCase().indexOf("/passbookings") > -1) browseMode = "BrowsePass";
111
112	if (browseMode != "Browse") {
113		init_data();
114		return;
115	}
116
117
118	if (calendar) {
119		var x = document.getElementById("ft_menuD");
120		if (x) {
121			x.style.width = "263px";
122			x.style.height = "318px";
123		}
124	} else {
125		var x = document.getElementById("ft_calendarContainer");
126		if (x) x.style.display = "none";
127	}
128
129	if (navigator.userAgent.match(/iPod; CPU iPhone OS 6/i) && window.screen.height == 480) {
130		throttled = true;
131		throttleCount = 20;
132		var x = document.getElementById("ft_background");
133		x.style.backgroundImage = "";
134		x.style.background = "#fff";
135	} else if (navigator.userAgent.match(/iPhone/i) && window.screen.height == 480) {
136		throttled = true;
137		throttleCount = 50;
138	} else if (navigator.userAgent.match(/iPhone/i) && window.screen.height == 568) {
139		throttled = true;
140		throttleCount = 200;
141	}
142
143	if (useLocalStorage && browseMode != "Wishlist" && ferve.utility.queryStringValue("refresh") != "1") {
144		var localData = localStorage.getItem("FTBR_" + ferve_h + "_" + browseMode);
145		var localTimeStamp = localStorage.getItem("FTBRTS_" + ferve_h + "_" + browseMode);
146		if (localData && localData.length > 0 && localTimeStamp && localTimeStamp.length > 0) {
147			localTimeStamp = Date.parse(localTimeStamp);
148			if (new Date().getTime() - localTimeStamp > 60000) {// cache data for one minute
149				logBrowse("localStorage [removing _" + browseMode + "]", "info");
150				localStorage.removeItem("FTBR_" + ferve_h + "_" + browseMode);
151				localStorage.removeItem("FTBRTS_" + ferve_h + "_" + browseMode);
152			} else {
153				ft_initBrowse_delegate(localData);
154				return;
155			}
156		}
157	}
158
159	init_data();
160}
161
162
163function ft_initBrowse_delegate(s) {
164	logBrowse("ft_initBrowse_delegate()");
165	var start = new Date().getTime();
166
167	var data = unescape(s);
168
169	if (useLocalStorage) {
170		logBrowse("localStorage [adding _" + browseMode + "]", "info");
171		localStorage.setItem("FTBR_" + ferve_h + "_" + browseMode, data);
172		localStorage.setItem("FTBRTS_" + ferve_h + "_" + browseMode, new Date());
173	}
174
175	if (browseMode == "Wishlist" || browseMode == "Play") {
176		attributeGroups = null;
177		venues = null;
178		items = JSON.parse(data);
179		if (items && typeof (items.Success) !== "undefined" && items.Success == false) {
180			logBrowse("Reloading Items");
181			if (ferve.visitor.hash.length == 0) ferve.visitor.updatePropertiesFromCookie();
182			setTimeout(function () { init_data(); }, 250);
183			return;
184		}
185
186	} else {
187		var jsonData = JSON.parse(data);
188		wishlist = jsonData.Wishlist;
189		attributeGroups = jsonData.AttributeGroups;
190		venues = jsonData.Venues;
191		items = jsonData.Items;
192		childEvents = jsonData.ChildEvents;
193	}
194
195	if (!items || items.length == 0) {
196		var message = "<p>&nbsp;</p><p>&nbsp;</p><p>No " + (browseMode == "Browse" || browseMode == "Play" ? label.toLowerCase() + (label.substring(label.length - 1) != 's' ? "s" : "") : browseMode) + " are available.</p<p>&nbsp;</p><p>&nbsp;</p>";
197
198		if (browseMode == "Wishlist") {
199			message = "<p>&nbsp;</p><p>The wishlist is a list of sessions you are interested in.</p><p>Click a heart icon next to a session to add it to your wishlist.</p>";
200			if (ferve.visitor.loggedIn != "1") message += "<p>You are not currently logged in, <a href=\"javascript:ferve.utility.openLink('account',0)\">log in now</a>.</p>";
201		} else if (browseMode == "BrowsePass") {
202			message = "<p>No events are available to book.&nbsp;</p>";
203		}
204
205		var x = document.getElementById("ft_browseOptions");
206		if (x) x.style.display = "none";
207		x = document.getElementById("ft_events");
208		if (x) x.innerHTML = message;
209		return;
210	}
211	if (browseMode != "BrowsePass") {
212		var parent = document.getElementById("ft_menuAttributes")
213		if (browseMode != "Browse" || (browseMode == "Browse" && !parent)) {
214			browseInitComplete = true;
215			for (var i = 0; i < items.length; i++) {
216				items[i].DateTime = parseDateTime(items[i].DateTime);
217			}
218			sort(sortMode, false);
219			logBrowse("ft_initBrowse(1) calling ft_renderItems");
220			ft_renderItems(true, searchOr);
221
222			try {
223				ft_browseInitted();
224			}
225			catch (e) { }
226
227			return;
228		}
229	}
230
231	if (ferve.utility.queryStringValue("os").length > 0) searchAND = (ferve.utility.queryStringValue("os") == "0");
232
233	var div, html, calloutHTML = "";
234
235	var cssAttributeHash = -1;
236	var cssAttributeLoc = -1;
237
238	// Attributes
239	parent = document.getElementById("ft_menuAttributes");
240	if (attributeGroups && parent) {
241		for (var i = 0; i < attributeGroups.length; i++) {
242			if (attributeGroups[i].Name == "CSS") {
243				cssAttributeHash = attributeGroups[i].Hash;
244				cssAttributeLoc = i;
245				continue;// no menu
246			}
247
248			html = "<a href=\"Javascript:ft_toggleMenu('" + attributeGroups[i].Hash + "')\">" + attributeGroups[i].Name + "</a><div id=\"ft_menu" + attributeGroups[i].Hash + "\" class=\"ft_menu ft_itemHidden\" style=\"display:none;\">";
249			for (var j = 0; j < attributeGroups[i].Attributes.length; j++) {
250				html += "<div><input type=\"checkbox\" onclick=\"Javascript:ft_toggleOption('" + attributeGroups[i].Hash + "','" + attributeGroups[i].Attributes[j].Hash + "')\" id=\"ft_c_" + attributeGroups[i].Hash + "o" + attributeGroups[i].Attributes[j].Hash + "\" /> <a href=\"Javascript:ft_toggleCheckbox('" + attributeGroups[i].Hash + "','" + attributeGroups[i].Attributes[j].Hash + "')\">" + attributeGroups[i].Attributes[j].Value + "</a></div>";
251
252				if (attributeGroups[i].Attributes[j].Callout) {
253					// To allow more than one callout selected set true to false on ft_toggleCheckbox call
254					calloutHTML += "<div class=\"ft_callout\" id=\"co_" + attributeGroups[i].Hash + "o" + attributeGroups[i].Attributes[j].Hash + "\" name=\"ft_callout\"><a href=\"Javascript:ft_toggleCheckbox('" + attributeGroups[i].Hash + "','" + attributeGroups[i].Attributes[j].Hash + "',true);\">" + attributeGroups[i].Attributes[j].Value + "</a></div>";
255				}
256			}
257			if (!attributeGroups[i].Browse) continue;
258
259			html += "</div>";
260
261			div = document.createElement("div");
262			div.className = "ft_menuBox";
263			div.innerHTML = html
264			parent.parentNode.insertBefore(div, parent.nextSibling);
265		}
266		var x = document.getElementById("ft_callouts");
267		if (calloutHTML.length > 0) {
268			if (x) {
269				x.innerHTML = calloutHTML;
270				x.style.display = "";
271			}
272		} else {
273			if (x) x.style.display = "none";
274		}
275
276		try {
277			ft_browseInitted();
278		}
279		catch (e) { }
280	}
281
282
283	// Venues
284	parent = document.getElementById("ft_menuV");
285	if (venues && parent) {
286		for (var i = 0; i < venues.length; i++) {
287			if (venues[i].ParentHash.length > 0) continue;
288			html = "<div><input type=\"checkbox\" onclick=\"Javascript:ft_toggleOption('V','" + venues[i].Hash + "')\" id=\"ft_c_Vo" + venues[i].Hash + "\" /> <a href=\"Javascript:ft_toggleCheckbox('V','" + venues[i].Hash + "')\">" + venues[i].Name + "</a></div>";
289
290			div = document.createElement("div");
291			div.innerHTML = html
292			parent.appendChild(div);
293		}
294	}
295
296	// No venues (online events)
297	if (typeof venues != "undefined" && venues.length == 0) {
298		if (parent) parent.parentNode.style.display = "none";
299	}
300
301	// Dates
302	parent = document.getElementById("ft_menuD");
303	var previousDate = new Date(1974, 16, 5, 0, 0, 0, 0);
304	for (var i = 0; i < items.length; i++) {
305		var sd = items[i].DateTime;
306		items[i].DateTime = parseDateTime(items[i].DateTime);
307		if (noSessionDate(items[i].DateTime)) continue;
308		if (previousDate.toDateString() != items[i].DateTime.toDateString()) {
309			sessionDates[sessionDates.length] = returnDateTime(items[i].DateTime, true, false);
310			if (!calendar) {
311				var option = items[i].DateTime.getFullYear() + "-" + (items[i].DateTime.getMonth() + 1) + "-" + items[i].DateTime.getDate();
312				html = "<div><input type=\"checkbox\" onclick=\"Javascript:ft_toggleOption('D','" + option + "')\" id=\"ft_c_Do" + option + "\" /> <a href=\"Javascript:ft_toggleCheckbox('D','" + option + "')\">" + returnDateTime(items[i].DateTime, true, false) + "</a></div>";
313				div = document.createElement("div");
314				div.innerHTML = html;
315				parent.appendChild(div);
316			}
317			previousDate = items[i].DateTime;
318		}
319	}
320	// No dates (online events)
321	if (typeof sessionDates != "undefined" && sessionDates.length == 0) {
322		if (parent) parent.parentNode.style.display = "none";
323	} else {
324		sessionDates.sort(orderBySessionDate);
325		renderCalendar();
326	}
327
328
329
330
331	// Count, venue, attributes && accessibility
332	items.sort(orderByName);
333	var previousName = (items[0] != undefined ? items[0].Name : ""), previousLoc = 0, count = 0;
334	for (var i = 0; i < items.length; i++) {
335		if (previousName != items[i].Name) {
336			for (var j = previousLoc; j < i; j++) {
337				items[j].Count = count;
338			}
339			count = 1;
340			previousLoc = i;
341			previousName = items[i].Name
342		} else {
343			count++;
344		}
345		items[i].DescriptionBrief = items[i].DescriptionBrief.replace(/&lt;/g, "<")
346		items[i].DescriptionBrief = items[i].DescriptionBrief.replace(/&gt;/g, ">")
347		items[i].VenueName = items[i].VenueName;
348		items[i].AttributeHashes = items[i].AttributeHashes.split(",");
349		items[i].CSS = "";
350		if (cssAttributeHash != -1 && items[i].AttributeHashes.length > 0) {
351			for (var j = 0; j < items[i].AttributeHashes.length; j++) {
352				var attribute = items[i].AttributeHashes[j].split("o");
353				if (attribute[0] == cssAttributeHash) {
354					for (var k = 0; k < attributeGroups[cssAttributeLoc].Attributes.length; k++) {
355						if (attributeGroups[cssAttributeLoc].Attributes[k].AttributeHashes == attribute[1]) {
356							items[i].CSS += attributeGroups[cssAttributeLoc].Attributes[k].Value + " ";
357							break;
358						}
359					}
360				}
361			}
362			if (items[i].CSS.length > 0) items[i].CSS = items[i].CSS.substring(0, items[i].CSS.length - 1);
363		}
364
365		items[i].AccessibilityIcons = "";
366		if (items[i].AccessibilityString && items[i].AccessibilityString.length > 0) {
367			var accessibilityIcons = items[i].AccessibilityString.split(",");
368			for (var j = 0; j < accessibilityIcons.length; j++) {
369				var accessibilityIcon = accessibilityIcons[j].split(";");
370				items[i].AccessibilityIcons +=
371					"<div class='ft_tooltipWrapper'>" +
372					"<div class='" + accessibilityIcon[1] + " ft_tooltip'></div>" +
373					"<div class='ft_tooltipContent ft_centered'>" + accessibilityIcon[0] + "</div>" +
374					"</div>";
375			}
376		}
377	}
378	for (var j = previousLoc; j < i; j++) {
379		items[j].Count = count;
380	}
381
382	var url = document.URL.split("/");
383
384	if (ferve.utility.queryStringValue("s") == "0") {
385		x = document.getElementById("ft_browseOptions");
386		if (x) x.style.display = "none";
387	}
388	var keyword = ferve.utility.queryStringValue("k");
389	if (keyword.length > 0) {
390		var x = document.getElementById("ft_keyword");
391		if (x) x.value = keyword;
392		ft_addSelectedOption("K", keyword);
393	}
394	var venue = ferve.utility.queryStringValue("v", null, true);
395	if (venue != "") {
396		venue = unescape(venue);
397		venue = returnVenueFromName(venue);
398	} else if (url.length == 6 && url[4] == "Venue") {
399		venue = unescape(url[5]);
400		if (venue.indexOf("?") > -1) venue = venue.slice(0, venue.lastIndexOf("?"));
401		venue = returnVenueFromName(venue);
402	}
403	if (venue) ft_toggleCheckbox("V", venue.Hash);
404
405	var group = ferve.utility.queryStringValue("g", null, true);
406	var option = ferve.utility.queryStringValue("o", null, true);
407	if (group == "") {
408		if (url.length == 6) {
409			group = url[4];
410			option = url[5];
411			if (option.indexOf("?") > -1) option = option.slice(0, option.lastIndexOf("?"));
412			childEvents = true;
413		}
414	}
415	group = decodeURIComponent(group);
416	option = decodeURIComponent(option);
417	var groupHash = "";
418	var optionHash = "";
419	if (group.length != 0 && option.length != 0) {
420		for (var i = 0; i < attributeGroups.length; i++) {
421			if (attributeGroups[i].Name == group) {
422				groupHash = attributeGroups[i].Hash;
423				for (var j = 0; j < attributeGroups[i].Attributes.length; j++) {
424					if (attributeGroups[i].Attributes[j].Value == option || (attributeGroups[i].Attributes[j].Value.replace(/[<>*%&:?+]/g, "")).trim() == option) {
425						optionHash = attributeGroups[i].Attributes[j].Hash;
426						break;
427					}
428				}
429			}
430			if (groupHash.length > 0 && option.length > 0) {
431				selectedOptions.length = 0;
432				ft_toggleCheckbox(groupHash, optionHash);
433				break;
434			}
435		}
436	} else {
437		// ft_browseOptionsRead(); - moved to after UI is ready
438	}
439
440	var date = ferve.utility.queryStringValue("d");
441	if (date.length > 0) {
442		calendarDateClicked(date);
443	}
444
445	var sm = ferve.utility.queryStringValue("sm");
446	if (!isNaN(parseFloat(sm)) && isFinite(sm) && sm > 0 && sm < 4) sortMode = sm;
447
448	for (var i = 1; i < 4; i++) {
449		x = document.getElementById("ft_sort" + i);
450		if (x) x.checked = (i == sortMode);
451	}
452
453	sort(sortMode, false);
454
455	var vm = ferve.utility.queryStringValue("vm");
456	if (vm && vm == "ft_grid2" || vm == "ft_list") browseView(vm);
457
458	var h3 = ferve.utility.queryStringValue("h");
459	if (h3 && h3 == "0") h3s = false;
460
461	browseInitComplete = true;
462
463	logBrowse("ft_initBrowse(2) calling ft_renderItems");
464	ft_renderItems(true, searchOr);
465
466
467	logBrowse("init complete in " + (new Date().getTime() - start) + "ms", "info");
468}
469
470
471function init_data() {
472	logBrowse("init_data()");
473	var browseImage = (ft_fta() ? "?" + ferve.returnUniqueID() + "=" + ferve.returnUniqueID() : "");
474	if (browseMode == "Wishlist") {
475		var src = document.getElementById("ft_browse").getAttribute("src");
476		src = src.substring(src.indexOf("?") + 1, src.length);
477		var share = ferve.utility.queryStringValue("h", src); ws = (share.length == 0);
478		ferve.xmlHttpRequest(ferve.url + "api/v1/Items/Schedule?hash=" + ferve.visitor.hash + (share.length > 0 ? "&share=" + share : ""), "GET", "", "", "", "ft_initBrowse_delegate(");
479	} else if (browseMode == "Browse") {
480		var url = document.URL.split("/");
481		var group = "";
482		if (url.length == 6) group = url[4];
483		ferve.xmlHttpRequest(ferve.url + "api/v1/Items/Browse" + browseImage + (group.length > 0 ? (browseImage.length == 0 ? "?" : "&") + "group=" + group : ""), "GET", "", "", "", "ft_initBrowse_delegate(", true);
484	} else if (browseMode == "BrowsePass") {
485		if (passBooking.getPassNumber() != "") {
486			ferve.xmlHttpRequest(ferve.url + "api/v1/Items/BrowsePass?passId=" + passBooking.getPassNumber(), "GET", "", "", "", "ft_initBrowse_delegate(");
487		}
488	} else if (browseMode == "Play") {
489		ferve.xmlHttpRequest(ferve.url + "api/v1/Items/Play?hash=" + ferve.visitor.hash, "GET", "", "", "", "ft_initBrowse_delegate(");
490	} else {
491		ferve.xmlHttpRequest(ferve.url + "api/v1/Items/" + browseMode + browseImage, "GET", "", "", "", "ft_initBrowse_delegate(");
492	}
493}
494
495
496function ft_initEcho() {
497	logBrowse("ft_initEcho()");
498	echo.init({
499		offset: '0',
500		throttle: '250'
501	});
502}
503
504
505// #mark ------ Search UI  ------
506
507
508function ft_resetBrowse(callout) {
509	for (var i = 0; i < selectedOptions.length; i++) {
510		var group = selectedOptions[i].substring(0, selectedOptions[i].indexOf("o"));
511		var option = selectedOptions[i].substring(selectedOptions[i].indexOf("o") + 1);
512		var x = document.getElementById("ft_so_" + group + "o" + option);
513		if (x) {
514			x.className = x.className.replace("ft_itemVisible", "ft_itemHidden");
515			if (callout) ferve.toggleDisplay("ft_so_" + group + "o" + option, "none");
516		}
517		ft_resetBrowseDiv(group, option);
518		x = document.getElementById("ft_c_" + group + "o" + option);
519		if (x) x.checked = false;
520	}
521	var x = document.getElementById("ft_keyword");
522	if (x) x.value = "";
523	x = document.getElementById("ft_resetOptions");
524	if (x) {
525		x.className = x.className.replace("ft_itemVisible", "ft_itemHidden");
526		setTimeout(function () { ferve.toggleDisplay("ft_resetOptions", "none"); }, 550);
527	}
528	renderCalendar();
529	x = document.getElementById("tMorning");
530	if (x) x.className = x.className.replace("tabSelected", "");
531	x = document.getElementById("tAfternoon");
532	if (x) x.className = x.className.replace("tabSelected", "");
533	x = document.getElementById("tEvening");
534	if (x) x.className = x.className.replace("tabSelected", "");
535	if (callout) {
536		selectedOptions.length = 0;
537	} else {
538		setTimeout(function () { selectedOptions.length = 0; ft_renderItems(true, searchOr); ft_updateCallouts(); }, 550);
539	}
540}
541function ft_resetBrowseDiv(ot, ov) {
542	setTimeout(function () { var x = document.getElementById("ft_so_" + ot + "o" + ov); if (x) x.parentNode.removeChild(x); }, 550);
543}
544
545
546function ft_toggleMenu(option, click) {
547	logBrowse("ft_toggleMenu(" + option + ", " + click + ")");
548	var x;
549	if (ft_currentMenu != "") {
550		x = document.getElementById("ft_menu" + ft_currentMenu);
551		if (x) {
552			if (ft_currentMenu == "D") {
553				x.className = x.className.replace("ft_itemVisible", "ft_itemHidden");
554			} else {
555				x.className = x.className.replace("ft_itemVisible95", "ft_itemHidden");
556			}
557		}
558		var currentOption = ft_currentMenu;
559		setTimeout(function () { ferve.toggleDisplay("ft_menu" + currentOption, "none"); }, 550);
560		if (ft_currentMenu == option) {
561			ft_currentMenu = "";
562			return;
563		} else {
564			ft_currentMenu = "";
565		}
566	}
567	x = document.getElementById("ft_menu" + option);
568	if (x) {
569		ferve.toggleDisplay("ft_menu" + option, "");
570		var rect = x.getBoundingClientRect();
571		if ((rect.left + 250) > window.innerWidth) {
572			x.style.left = (window.innerWidth - 255) + "px";
573		}
574		setTimeout(function () { x.className = x.className.replace("ft_itemHidden", (option == "D" ? "ft_itemVisible" : "ft_itemVisible95")); }, 25);
575		ft_currentMenu = option;
576		if (document.addEventListener) {
577			document.addEventListener("click", ft_clickedBrowse, false);
578			if ("ontouchend" in window) document.body.addEventListener("touchend", ft_clickedBrowse, false);
579
580
581		} else {
582			document.attachEvent("onclick", ft_clickedBrowse);
583		}
584	}
585}
586
587function ft_clickedBrowse(e) {
588	logBrowse("ft_clickedBrowse()");
589	if (!e) e = window.event;
590	var t = e.target || e.srcElement;
591	var x = document.getElementById("ft_menu" + ft_currentMenu);
592	if (!x) return;
593	while (t.parentNode) {
594		if (t == x) {
595			logBrowse("exiting ft_clickedBrowse");
596			return;
597		} else if (t.className == "ft_menuBox") {
598			if (document.addEventListener) {
599				document.removeEventListener("click", ft_clickedBrowse, false);
600				if ("ontouchend" in window) document.body.removeEventListener("touchend", ft_clickedBrowse, false);
601			} else {
602				document.detachEvent("onclick", ft_clickedBrowse);
603			}
604			return;
605		}
606		t = t.parentNode;
607	}
608	if (document.addEventListener) {
609		document.removeEventListener("click", ft_clickedBrowse, false);
610		if ("ontouchend" in window) document.body.removeEventListener("touchend", ft_clickedBrowse, false);
611	} else {
612		document.detachEvent("onclick", ft_clickedBrowse);
613	}
614
615	ft_toggleMenu(ft_currentMenu, true);
616
617
618	return false;
619}
620
621
622function ft_updateCallouts() {
623	logBrowse("ft_updateCallouts()");
624	var callouts = document.getElementsByName("ft_callout");
625	for (var i = 0; i < callouts.length; i++) {
626		var option = callouts[i].id.replace("co_", "");
627		var selected = false;
628		for (var j = 0; j < selectedOptions.length; j++) {
629			if (option == selectedOptions[j]) {
630				selected = true;
631				break;
632			}
633		}
634		if (selected) {
635			callouts[i].className = "ft_callout ft_calloutSelected";
636		} else {
637			callouts[i].className = "ft_callout";
638		}
639	}
640}
641
642function ft_toggleCheckbox(group, option, callout) {
643	logBrowse("ft_toggleCheckbox(" + group + ", " + option + ")");
644	if (callout) {
645		if (calloutToggle) {
646			// Reset if not the currently selected callout
647			if (selectedOptions.indexOf(group + "o" + option) == -1) ft_resetBrowse(true);
648		} else {
649			ft_resetBrowse(true);
650		}
651	}
652
653	var x = document.getElementById("ft_c_" + group + "o" + option);
654	if (x) x.checked = !x.checked;
655	ft_toggleOption(group, option);
656	ft_updateCallouts();
657}
658
659function ft_toggleOption(group, option) {
660	logBrowse("ft_toggleOption(" + group + ", " + option + ")");
661	var x = document.getElementById("ft_c_" + group + "o" + option);
662	if (x) {
663		if (x.checked) {
664			ft_addSelectedOption(group, option);
665		} else {
666			ft_removeSelectedOption(group, option);
667		}
668		return;
669	}
670
671	logBrowse("ft_toggleOption() no x");
672	if (selectedOptions.indexOf(group + "o" + option) > -1) {
673		ft_removeSelectedOption(group, option);
674		return;
675	}
676	ft_addSelectedOption(group, option);
677	return;
678}
679
680function ft_addSelectedOption(group, option) {
681	logBrowse("ft_addSelectedOption(" + group + ", " + option + ")");
682	var x = document.getElementById("ft_selectedOptions");
683	if (x) {
684		var name = "";
685		var exit = false;
686
687		if (group == "D") {
688			name = returnDateTime(option, true, false);
689
690		} else if (group == "T") {
691			name = option;
692
693		} else if (group == "V") {
694			for (var i = 0; i < venues.length; i++) {
695				if (venues[i].Hash == option || venues[i].ParentHash == option) {
696					name = venues[i].Name;
697					break;
698				}
699			}
700		} else if (group == "K") {
701			name = option;
702		} else {
703			for (var i = 0; i < attributeGroups.length; i++) {
704				if (attributeGroups[i].Hash == group) {
705					for (var j = 0; j < attributeGroups[i].Attributes.length; j++) {
706						if (attributeGroups[i].Attributes[j].Hash == option) {
707							name = attributeGroups[i].Attributes[j].Value;
708							exit = true;
709							break;
710						}
711					}
712				}
713				if (exit) break;
714			}
715		}
716		var div = document.createElement("div");
717		div.id = "ft_so_" + group + "o" + (group != "K" ? option : "");
718		div.className = "ft_selectedOption " + (browseInitComplete ? "ft_itemHidden" : "ft_itemVisible");
719		div.innerHTML = "<a href=\"Javascript:ft_removeSelectedOption('" + group + "','" + (group != "K" ? option : "") + "')\"><span>X</span>
719 " + name + "</a>";
720		x.appendChild(div);
721		div.style.width = div.offsetWidth + "px";
722		selectedOptions[selectedOptions.length] = group + "o" + (group != "K" ? option : "");
723		if (browseInitComplete) setTimeout(function () { div.className = div.className.replace("ft_itemHidden", "ft_itemVisible"); }, 25);
724		x = document.getElementById("ft_resetOptions");
725		if (x) {
726			ferve.toggleDisplay("ft_resetOptions", "");
727			setTimeout(function () { x.className = x.className.replace("ft_itemHidden", "ft_itemVisible"); }, 25);
728		}
729
730		// Save options when a filter is added
731		if (browseMode == "Browse") {
732			ft_browseOptionsSave();
733		}
734	}
735	if (group != "K") {
736		logBrowse("ft_addSelectedOption calling ft_renderItems");
737		ft_renderItems(true, searchOr);
738	}
739}
740
741function ft_removeSelectedOption(group, option) {
742	var x = document.getElementById("ft_c_" + group + "o" + (group != "K" ? option : ""));
743	if (x) x.checked = false;
744	var index = selectedOptions.indexOf(group + "o" + (group != "K" ? option : ""))
745	if (index > -1) selectedOptions = removeElement(selectedOptions, index);
746
747	x = document.getElementById("ft_so_" + group + "o" + (group != "K" ? option : ""));
748	if (x) {
749		x.className = x.className.replace("ft_itemVisible", "ft_itemHidden");
750		x.className += " ft_selectedOptionWidth";
751		x.style.width = "0";
752		setTimeout(function () {
753			var x = document.getElementById("ft_so_" + group + "o" + (group != "K" ? option : ""));
754			if (x) x.parentNode.removeChild(x);
755			if (selectedOptions.length == 0) {
756				x = document.getElementById("ft_resetOptions");
757				if (x) {
758					x.className = x.className.replace("ft_itemVisible", "ft_itemHidden");
759					setTimeout(function () { ferve.toggleDisplay("ft_resetOptions", "none"); }, 550);
760				}
761			}
762		}, 550);
763	}
764
765	if (group == "D") {
766		var selDate = parseDate(option);
767		x = document.getElementById("d" + selDate.getFullYear() + "-" + (selDate.getMonth() + 1) + "-" + selDate.getDate());
768		if (x) x.className = x.className.replace(" calSelected", "");
769	} else if (group == "T") {
770		x = document.getElementById("t" + option);
771		if (x) x.className = x.className = "";
772	} else if (group == "K") {
773		x = document.getElementById("ft_keyword");
774		if (x) x.value = "";
775	}
776
777	ft_updateCallouts();
778
779	// Save options when a filter is removed
780	if (browseMode == "Browse") {
781		ft_browseOptionsSave();
782	}
783
784	logBrowse("ft_removeSelectedOption calling ft_renderItems");
785	ft_renderItems(true, searchOr);
786
787}
788
789
790// #mark ------ Rendering  ------
791
792// Helper function to group events by NameSort and select the best event from each group
793function groupEventsByNameSortAndSelectBest(items) {
794	var grouped = {};
795	items.forEach(function(item) {
796		if (!grouped[item.NameSort]) grouped[item.NameSort] = [];
797		grouped[item.NameSort].push(item);
798	});
799
800	return Object.keys(grouped).map(function(nameSort) {
801		var group = grouped[nameSort].sort(function(a, b) {
802			return new Date(b.DateTime || 0) - new Date(a.DateTime || 0);
803		});
804		return group.find(function(item) { return item.Status != 6; }) || group[0];
805	});
806}
807
808var membershipAvailable;
809function ft_renderItems(clearSessionList, orSearch, parent = null) {
810
811	if (!browseInitComplete) return;
812
813	logBrowse("\n\nft_renderItems(" + clearSessionList + ", " + orSearch + ")");
814	if (clearSessionList) throttleIndex = 1;
815
816	if (!parent) parent = document.getElementById("ft_events");
817	if (clearSessionList) parent.innerHTML = "";
818	var previousItem = "";
819
820	var keyword = "";
821	var x = document.getElementById("ft_keyword");
822	if (x) keyword = x.value;
823	x = document.getElementById("ft_so_Ko");
824	if (keyword.length > 0) {
825		if (x) {
826			x.innerHTML = "<a href=\"Javascript:ft_removeSelectedOption('K','')\"><span>X</span> " + keyword + "</a>";
827			var ruler = document.getElementById("ruler");
828			ruler.innerHTML = keyword;
829			var width = ruler.offsetWidth;
830			x.style.width = (width + 25) + "px";
831		} else if (keyword.length > 0) {
832			ft_addSelectedOption("K", keyword);
833		}
834	} else {
835		if (x && x.className.indexOf("ft_itemVisible") > -1) ft_removeSelectedOption("K", "");
836	}
837
838	var html = "";
839
840	document.title = pageTitle;
841
842	// ft_browseOptionsSave(); - REMOVED: This was called too early before any filters were selected
843
844	if (selectedOptions.length == 1 && (!throttled || (throttled && throttleIndex == 1))) {
845		var attributeHash = selectedOptions[0].substring(selectedOptions[0].indexOf("o") + 1);
846		if (selectedOptions[0].substring(0, 1) == "V") {
847			var venueName = returnVenueName(attributeHash);
848			parent.innerHTML = "<h2>" + venueName + "</h2>";
849			document.title = venueName + " | " + pageTitle;
850		} else {
851			var attribute = returnAttribute(attributeHash);
852			var attributeGroup = returnAttributeGroup(attributeHash);
853			if (attributeGroup) {
854				var title = (attributeGroup.Name != attribute.Value ? attributeGroup.Name + " >> " + attribute.Value : attributeGroup.Name);
855				parent.innerHTML = "<div class=\"ft_attribute\"><h2>" + title + "</h2>";
856				document.title = title + " | " + pageTitle;
857			}
858			if (attribute && attribute.Description && attribute.Description.length > 0) parent.innerHTML += attribute.Description;
859			parent.innerHTML += "</div><div class=\"ft_clear\" style=\"margin-bottom:15px;\"></div>";
860		}
861	}
862
863	var sessionCount = 0;
864	var throttleStart = ((throttleIndex - 1) * throttleCount) + 1;
865	var throttleEnd = (throttleIndex * throttleCount);
866
867	var foundKeyword, foundDate, foundTime, foundVenue, foundAttribute;
868	var dateCount = 0, timeCount = 0, venueCount = 0, attributeCount = 0;
869
870	// and / or : if eg. date and category use and, if category and category use or
871	if (orSearch == null) {
872		var multiple = false, optionsFound = (keyword.length > 0 ? "K" : "");
873		for (var i = 0; i < selectedOptions.length; i++) {
874			var optionType = selectedOptions[i].substring(0, selectedOptions[i].indexOf("o"));
875			if (optionType == "D") {
876				dateCount++;
877			} else if (optionType == "T") {
878				timeCount++;
879			} else if (optionType == "V") {
880				venueCount++;
881			} else {
882				attributeCount++;
883			}
884		}
885
886		var optionsFound = (dateCount + timeCount + venueCount + attributeCount);
887		logBrowse("optionsFound: " + optionsFound);
888
889		if (optionsFound == 1) {
890			orSearch = true;
891		} else if (optionsFound > 1 && !searchAND) {
892			orSearch = true;
893		} else if (searchAND) {
894			orSearch = false;
895		}
896	}
897	logBrowse("    orSearch: " + orSearch + "\n    dateCount: " + dateCount + "\n    timeCount: " + timeCount + "\n    venueCount: " + venueCount + "\n    attributeCount: " + attributeCount);
898
899	// Group events by NameSort when in alphabetical mode and select the best event from each group
900	if (sortMode == 1) {
901		items = groupEventsByNameSortAndSelectBest(items);
902	}
903
904	for (var i = 0; i < items.length; i++) {
905		// REMOVED: Original skip logic that caused "Sold Out" status to apply to entire alphabetical groups
906		// Replaced with grouping that selects the next available event from each group
907		// if (sortMode == 1 && previousItem == items[i].NameSort) continue;
908		if (browseMode == "Wishlist" && !ft_sessionVisible(items[i])) continue;
909		if (!childEvents && items[i].Child) continue;
910
911		if (selectedOptions.length > 0 || keyword.length > 0) {
912			foundKeyword = false;
913			foundDate = (dateCount <= 1 ? !orSearch : false);
914			foundTime = (timeCount <= 1 ? !orSearch : false);
915			foundVenue = (venueCount <= 1 ? !orSearch : false);
916			foundAttribute = (attributeCount <= 1 ? !orSearch : false);
917			//logBrowse("\n\n\n  Defaults\n\n foundKeyword: " + foundKeyword + "\n  foundDate: " + foundDate + "\n  foundTime: " + foundTime + "\n  foundVenue: " + foundVenue + "\n  foundAttribute: " + foundAttribute);
918
919
920			if (keyword.length > 0) {
921				var sessionString = (items[i].Code != undefined ? items[i].Code : "") + items[i].Name + items[i].Keywords + (searchDescription ? items[i].DescriptionBrief : "") + items[i].AttributeString + items[i].VenueName + items[i].VenueKeywords;
922				if (sessionString.toLowerCase().indexOf(keyword.toLowerCase()) != -1) foundKeyword = true;
923			} else {
924				foundKeyword = !orSearch;
925			}
926
927			if (!foundKeyword || !orSearch) {
928
929				for (var j = 0; j < selectedOptions.length; j++) {
930
931					var optionType = selectedOptions[j].substring(0, selectedOptions[j].indexOf("o"));
932					var optionValue = selectedOptions[j].substring(selectedOptions[j].indexOf("o") + 1);
933
934					if (optionType == "D") {
935						if (dateCount <= 1) foundDate = false;
936						optionValue = parseDate(optionValue);
937						if (items[i].DateTime instanceof Date && optionValue.getDate() == items[i].DateTime.getDate() && optionValue.getMonth() == items[i].DateTime.getMonth() && optionValue.getFullYear() == items[i].DateTime.getFullYear()) {
938							foundDate = true;
939							if (orSearch) break;
940						}
941
942					} else if (optionType == "T") {
943						if (timeCount <= 1) foundTime = false;
944						var hour = items[i].DateTime.getHours();
945						if (optionValue == "Morning" && (hour >= 0 && hour < 12)) {
946							foundTime = true;
947							if (orSearch) break;
948						} else if (optionValue == "Afternoon" && (hour >= 12 && hour < 18)) {
949							foundTime = true;
950							if (orSearch) break;
951						} else if (optionValue == "Evening" && (hour >= 18 && hour <= 23)) {
952							foundTime = true;
953							if (orSearch) break;
954						}
955
956					} else if (optionType == "V") {
957						foundVenue = false;
958						if (venueCount <= 1) foundVenue = false;
959						if (items[i].VenueHash == optionValue || items[i].ParentVenueHash == optionValue) {
960							foundVenue = true;
961							if (orSearch) break;
962						}
963
964					} else if (optionType != "K") {
965						if (attributeCount <= 1) foundAttribute = false;
966						for (var k = 0; k < items[i].AttributeHashes.length; k++) {
967							if (items[i].AttributeHashes[k] == optionType + "o" + optionValue) {
968								foundAttribute = true;
969								if (orSearch) break;
970							}
971						}
972						if (orSearch && foundAttribute) break;
973					}
974				}
975			}
976
977			//logBrowse(items[i].Name + "\n  orSearch: " + orSearch + "\n  foundKeyword: " + foundKeyword + "\n  foundDate: " + foundDate + "\n  foundTime: " + foundTime + "\n  foundVenue: " + foundVenue + "\n  foundAttribute: " + foundAttribute);
978
979			if (!orSearch && (!foundKeyword || !foundDate || !foundTime || !foundVenue || !foundAttribute)) {
980				foundKeyword = false;
981				foundDate = false;
982				foundTime = false;
983				foundVenue = false;
984				foundAttribute = false;
985			}
986
987			if (!foundKeyword && !foundDate && !foundTime && !foundVenue && !foundAttribute) continue;
988		}
989
990		sessionCount++;
991
992		if (throttled) {
993			if (sessionCount < throttleStart) {
994				continue;
995			} else if (sessionCount > throttleEnd) {
996				break;
997			} else {
998				if (previousItem.length == 0 && throttleIndex != 1) {
999					if (sortMode == 1) {
1000						previousItem = items[i].Name;
1001					} else if (sortMode == 2) {
1002						previousItem = returnDateTime(items[i].DateTime, true, false);
1003					} else if (sortMode == 3) {
1004						previousItem = items[i].VenueName;
1005					}
1006				}
1007			}
1008
1009		}
1010
1011		if (browseMode == "Browse" || browseMode == "Exchange" || (browseMode == "Wishlist" && view == 2)) {
1012			if (h3s && sortMode == 1 && previousItem.substring(0, 1) != items[i].NameSort.substring(0, 1)) {
1013				var s = items[i].NameSort.substring(0, 1);
1014				if (!/^[a-zA-Z0-9]+$/.test(s)) s = "*";
1015				if (/^[0-9]+$/.test(s)) s = "#";
1016				var p = previousItem.substring(0, 1);
1017				if (p.length > 0 && !/^[a-zA-Z0-9]+$/.test(p)) p = "*";
1018				if (/^[0-9]+$/.test(p)) p = "#";
1019				if ((s != "#" && s != "*") || (s == "#" && p != "#") || (s == "*" && p != "*")) {
1020					var h3 = document.createElement("h3");
1021					h3.innerHTML = s;
1022					parent.appendChild(h3);
1023				}
1024
1025			} else if (sortMode == 2 && !noSessionDate(items[i].DateTime) && previousItem != returnDateTime(items[i].DateTime, true, false)) {
1026				var h3 = document.createElement("h3");
1027				h3.innerHTML = returnDateTime(items[i].DateTime, true, false);
1028				parent.appendChild(h3);
1029
1030			} else if (sortMode == 2 && !ferve.isNullOrEmpty(previousItem) && items[i].VenueName == "Online") {
1031				var h3 = document.createElement("h3");
1032				h3.innerHTML = "Online";
1033				parent.appendChild(h3);
1034
1035			} else if (sortMode == 3 && previousItem != items[i].VenueName) {
1036				var h3 = document.createElement("h3");
1037				h3.innerHTML = items[i].VenueName;
1038				parent.appendChild(h3);
1039			}
1040		}
1041
1042		html = ft_renderItem(items[i]);
1043
1044		div = document.createElement("div");
1045		div.className = "ft_event";
1046
1047
1048
1049		if (items[i].CSS && items[i].CSS.length > 0) div.className += " " + items[i].CSS;
1050		div.innerHTML = html;
1051		parent.appendChild(div);
1052
1053		if (sortMode == 1) {
1054			previousItem = items[i].NameSort;
1055		} else if (sortMode == 2) {
1056			previousItem = returnDateTime(items[i].DateTime, true, false);
1057		} else if (sortMode == 3) {
1058			previousItem = items[i].VenueName;
1059		}
1060	}
1061
1062	logBrowse("\n\nsessionCount:" + sessionCount + "\n\n");
1063	if (sessionCount == 0 && !orSearch && searchOr) {
1064		logBrowse("ft_renderItems calling ft_renderItems (no items)");
1065		ft_renderItems(clearSessionList, true);
1066		return;
1067	}
1068
1069	if (throttled && sessionCount < items.length) {
1070		div = document.createElement("div");
1071		div.className = "ft_eventMore";
1072		div.id = "lm" + throttleIndex;
1073		div.innerHTML = "<a href=\"Javascript:ft_renderMoreItems(" + throttleIndex + ");\">Load More Sessions...</a>";
1074		parent.appendChild(div);
1075	}
1076
1077
1078	if (parent && html.indexOf("ft_image") == -1) {
1079		parent.innerHTML = "<div style=\"padding:25px;\">No items match your query.</div>";
1080	}
1081
1082	var x = document.getElementById("BrOptions");
1083	if (x) x.className = "ft_itemVisible ft_fade ft_noPrint";
1084
1085	if (browseMode == "Browse" && scrollY != 0) {
1086		var currentScrollY = (document.all ? document.scrollTop : window.pageYOffset);
1087		if (scrollY != currentScrollY) window.scrollTo(0, scrollY);
1088	}
1089
1090
1091	echo.render();
1092
1093	ferve.renderWishlistStars();
1094
1095	if (browseMode == "BrowsePass") {
1096		passBooking.changeViewRender();
1097		var passBookingQty = document.getElementsByClassName("btn-widget");
1098		for (var i = 0; i < passBookingQty.length; i++) {
1099			passBookingQty[i].addEventListener("click", passBooking.selectPassQty);
1100		}
1101	}
1102
1103	//Get the list of membership
1104	membershipAvailable = document.querySelectorAll('#ft_events .ft_event .ft_select').length;
1105
1106}
1107
1108
1109function ft_renderItem(item) {
1110	var html = "", url = "";
1111
1112	if (browseMode == "Exchange") {
1113		url = "Javascript:ft_exchangeItem('" + item.Hash + "');"
1114		html += "<div class=\"ft_select\">";
1115		var ignore = [1, 2, 5, 6];
1116		if (ignore.indexOf(item.Status) == -1) html += "<input type=\"radio\" name=\"itemHash\" value=\"" + item.Hash + "\" data-hash=\"" + item.Hash + "\" onclick=\"ft_exchangeItem('" + item.Hash + "')\" />";
1117		html += "</div>";
1118	} else if (browseMode == "MemberRenew") {
1119		url = "Javascript:ft_renewItem('" + item.Hash + "');"
1120		html += "<div class=\"ft_select\">";
1121		var ignore = [1, 2, 5, 6];
1122		//MP : If there is only one membership type to renew, radio button should be pre-selected
1123		if (ignore.indexOf(item.Status) == -1) html += "<input type=\"radio\" name=\"itemHash\" value=\"" + item.Hash + "\" data-hash=\"" + item.Hash + "\" " + (membershipAvailable == 1 ? "checked" : "") + " onclick=\"ft_renewItem('" + item.Hash + "')\" />";
1124		if (membershipAvailable == 1) ft_renewItem(item.Hash);
1125		html += "</div>";
1126	} else {
1127		url = item.URL;
1128	}
1129
1130	var href = "<a href=\"" + url + "\"" + (url.indexOf("Redirect") != -1 ? " target=\"_blank\"" : "") + ">";
1131
1132	html += "<div class=\"ft_image\">" + href + "<img src=\"" + ft_link("/img/ferve/loading_browse.gif") + "\" data-echo=\"" + ft_link(image == "browse" ? item.ImageBrowse : item.ImageThumbnail) + "\" onerror=\"ft_image404(this);this.style.display='none';\" alt=\"" + item.Name + "\" /></a></div>";
1133
1134	html += "<div class=\"ft_info\">";
1135	html += href;
1136	html += "<div class=\"ft_status\">";
1137	if (item.Status == 1) {
1138		html += "<div class=\"fticon-status-cancelled ft_cancelled\" title=\"Cancelled\"><span class=\"ft_cancelled\">Cancelled</span></div>";
1139	} else if (item.Status == 2) {
1140		html += "<div class=\"fticon-status-postponed ft_postponed\" title=\"Postponed\"><span class=\"ft_postponed\">Postponed</span></div>";
1141	} else if (item.Status == 4) {
1142		html += "<div class=\"fticon-status-sellingfast ft_sellingfast\" title=\"Selling Fast\"><span class=\"ft_sellingfast\">Selling Fast</span></div>";
1143	} else if (item.Status == 5) {
1144		html += "<div class=\"fticon-status-standby ft_standby\" title=\"Standby\"><span class=\"ft_standby\">Standby</span></div>";
1145	} else if (item.Status == 6) {
1146		html += "<div class=\"fticon-status-soldout ft_soldout\" title=\"Sold Out\"><span class=\"ft_soldout\">Sold Out</span></div>";
1147	}
1148	html += "</div>";
1149
1150	if (wishlist && !ws) {
1151		var name = document.getElementById("ft_heading").innerHTML;
1152		name = name.substring(0, name.indexOf("'"));
1153		if (item.Purchased) html += "<div class=\"fticon-status-purchased1\" title=\"" + name + " has purchased tickets\"><img src=\"/img/ferve/status_purchased.svg\" /></div>";
1154		//if (item.Wishlist) html += "<div class=\"fticon-wishlist-on fticon-status-wishlist" + (item.Purchased ? "2" : "1") + "\" title=\"In " + name + "'s wishlist\"></div>";
1155	} else if (item.Purchased) {
1156		html += "<div class=\"fticon-status-purchased\" title=\"Purchased Tickets\"><img src=\"/img/ferve/status_purchased.svg\" /></div>";
1157	}
1158
1159	html += (item.Code != undefined && item.Code.length > 0 ? "<div class=\"ft_code\">" + item.Code + "</div>" : "");
1160	html += "<div class=\"ft_title\">" + item.Name + "</div>";
1161	html += "<div class=\"ft_attributes\">" + item.AttributeString + "</div>";
1162	html += "<div class=\"ft_date\">";
1163	if (sortMode == 1) {
1164		var arr = item.URL.split("/");
1165		html += item.Count + " " + (arr.length > 2 ? arr[1].slice(0, -1) : "Session") + (item.Count == 1 ? "" : "s");
1166	} else {
1167		if (item.TimeSlots > 0) html += "From ";
1168		html += returnDateTime(item.DateTime, (view == 1), true);
1169	}
1170	html += "</div>";
1171	html += "<div class=\"ft_venue\">";
1172	if (sortMode == 1) {
1173		if (item.Count == 1) {
1174			html += (item.VenueName != null ? item.VenueName : "");
1175		} else {
1176			html += "&nbsp;";
1177		}
1178	} else {
1179		html += (item.VenueName != null ? item.VenueName : "");
1180	}
1181	html += "</div>";
1182	html += "<div class=\"ft_runtime\">" + item.Runtime + " mins</div>";
1183	if (item.AccessibilityIcons && item.AccessibilityIcons.length > 0) html += "<div class=\"ft_accessibility\">" + item.AccessibilityIcons + "</div>";
1184	html += "<div class=\"ft_description\">" + item.DescriptionBrief + "</div>";
1185	html += "<div class=\"ft_priceRange\">" + item.PriceRange + "</div>";
1186	html += "<div class=\"ft_btnView\">View</div>";
1187	html += "</a>";
1188
1189	if (browseMode == "BrowsePass") {
1190		var htmlWidget = "";
1191		for (var i = 0;
1191 i <= passBooking.getTotalRedeemPerEvent(); i++) {
1192			if (i == 0) {
1193				htmlWidget += "<label class=\"btn btn-default btn-widget active " + passBooking.lockWidget(item.Status) + "\"><input value=\"" + i + "\" type=\"radio\" name=\"options\" id=\"option" + i + "\" autocomplete=\"off\">" + i + "</label>";
1194			} else {
1195				htmlWidget += "<label class=\"btn btn-default btn-widget " + passBooking.lockWidget(item.Status) + "\"><input value=\"" + i + "\" type=\"radio\" name=\"options\" id=\"option" + i + "\" autocomplete=\"off\">" + i + "</label>";
1196			}
1197		}
1198
1199		html += "<div data-itemName='" + item.Name + "' data-itemhash=" + item.Hash + " data-status='" + item.Status + "' data-thumbnail='" + ft_link(item.ImageThumbnail) + "' data-Allocated=" + item.Allocated + " class=\"btn-group btn-group-toggle browsepass\" role=\"group\" data-toggle=\"buttons\"><label class=\"btn btn-default widgetlabel\">Tickets</label>" + htmlWidget + "</div>";
1200	}
1201
1202	html += "<div class=\"ft_btnTrailer" + (ferve.isNullOrEmpty(item.Trailer) ? " ft_trailerNone" : "") + "\"><a href=\"Javascript:playTrailer('" + item.Trailer + "')\">Trailer</a></div>";
1203	html += "</div>";// ft_info
1204
1205	if (wishlist) {
1206		html += "<div class=\"ft_wishlist\"><a href=\"Javascript:" + (sortMode == 1 && item.Count > 1 ? "ferve.wishlist.toggleSeason('" + item.Id + "')" : "ferve.wishlist.toggle('" + item.Id + "')") + ";\" title=\"Add/remove from your wishlist\"><div class=\"fticon-wishlist-off\" data-wishlist=\"" + item.Id + "\"></div></a></div>"// if (wishlist && ws)
1207	}
1208
1209	if (item.Keywords.substring(item.Keywords.length - 3) == "[H]") html += "<div class=\"corner-ribbon\" title=\"Set as web hidden and is not visible to non admin users.\">Hidden</div>";
1210	if (item.Keywords.substring(item.Keywords.length - 3) == "[E]") html += "<div class=\"corner-ribbon\">Expired</div>";
1211
1212	return html;
1213}
1214
1215
1216function ft_renderMoreItems(ti) {
1217	throttleIndex++;
1218
1219	var x = document.getElementById("lm" + ti);
1220	if (x) x.parentNode.removeChild(x);
1221
1222
1223	setTimeout(function () { ft_renderItems(false, searchOr); }, 100);
1224
1225}
1226
1227
1228// #mark ------ Sorting  ------
1229
1230function sort(mode, click) {
1231
1232	sortMode = mode;
1233
1234	// Save options when sort mode changes
1235	if (browseMode == "Browse") {
1236		ft_browseOptionsSave();
1237	}
1238
1239	var x;
1240
1241	if (click) ft_toggleMenu("S");
1242
1243	for (var i = 1; i < 4; i++) {
1244		x = document.getElementById("ft_sort" + i);
1245		if (x) x.checked = (i == mode);
1246	}
1247
1248	if (mode == 1) {
1249		items.sort(orderByName);
1250	} else if (mode == 2) {
1251		items.sort(orderBySessionDate);
1252	} else if (mode == 3) {
1253		items.sort(orderByVenue);
1254	}
1255
1256	if (click) ft_renderItems(true, searchOr);
1257
1258
1259
1260}
1261
1262/* Array sorting */
1263function orderByName(a, b) {
1264	var x = a.NameSort.toString().toLowerCase(), y = b.NameSort.toString().toLowerCase();
1265	if (!/^[a-zA-Z0-9]+$/.test(x.substring(0, 1))) x = "*";
1266	if (!/^[a-zA-Z0-9]+$/.test(y.substring(0, 1))) y = "*";
1267	return x < y ? -1 : x > y ? 1 : 0;
1268}
1269
1270function orderBySessionDate(a, b) {
1271	if (a.VenueName == "Online") return 1;
1272	if (b.VenueName == "Online") return -1;
1273	var x = a.DateTime, y = b.DateTime;
1274	return x - y;
1275}
1276
1277function orderByVenue(a, b) {
1278	var x = a.VenueName.toLowerCase(), y = b.VenueName.toLowerCase();
1279	return x < y ? -1 : x > y ? 1 : 0;
1280}
1281
1282function returnAttributeGroup(hash) {
1283	if (!attributeGroups || attributeGroups.length == 0) return null;
1284	for (var i = 0; i < attributeGroups.length; i++) {
1285		for (var j = 0; j < attributeGroups[i].Attributes.length; j++) {
1286			if (attributeGroups[i].Attributes[j].Hash == hash) return attributeGroups[i];
1287		}
1288	}
1289	return null;
1290}
1291
1292function returnAttribute(hash) {
1293	if (!attributeGroups || attributeGroups.length == 0) return null;
1294	for (var i = 0; i < attributeGroups.length; i++) {
1295		for (var j = 0; j < attributeGroups[i].Attributes.length; j++) {
1296			if (attributeGroups[i].Attributes[j].Hash == hash) return attributeGroups[i].Attributes[j];
1297		}
1298	}
1299	return null;
1300}
1301
1302function returnVenue(hash) {
1303	if (!venues) return null;
1304	for (var i = 0; i < venues.length; i++) {
1305		if (venues[i].Hash == hash) return venues[i];
1306	}
1307	return "";
1308}
1309
1310function returnVenueName(hash) {
1311	if (!venues) return "NAV";
1312	for (var i = 0; i < venues.length; i++) {
1313		if (venues[i].Hash == hash) return venues[i].Name;
1314	}
1315	return "";
1316}
1317
1318function returnVenueFromName(name) {
1319	if (!venues) return null;
1320	for (var i = 0; i < venues.length; i++) {
1321		if (venues[i].Name == name) return venues[i];
1322	}
1323	return null;
1324}
1325
1326var searchTimeout;
1327function startSearch() {
1328	logBrowse("startSearch (" + (typeof (searchTimeout) != "undefined") + ")");
1329	if (typeof (searchTimeout) != "undefined") clearTimeout(searchTimeout);
1330	searchTimeout = setTimeout(function () {
1331		logBrowse("startSearch calling ft_renderItems");
1332		ft_renderItems(true, searchOr);
1333	}, 400);
1334}
1335
1336function browseView(c) {
1337	logBrowse("browseView(" + c + ")");
1338	var x = document.getElementById("ft_events");
1339	if (!x) x = document.getElementById("ft_venuess");
1340	if (!x) return;
1341	x.className = c;
1342	view = (x.className.indexOf("ft_list") > -1 ? 2 : 1);
1343
1344	ft_renderItems(true, searchOr);// Always render
1345
1346	// Save options when view changes
1347	if (browseMode == "Browse") {
1348		ft_browseOptionsSave();
1349	}
1350
1351	// Wishlist
1352	//var x = document.getElementById("ft_events");
1353	//if (x && x.innerHTML.indexOf("ft_scheduler") > -1) ft_renderItems(true, false);
1354}
1355
1356
1357// #mark ------ Calendar  ------
1358
1359var clickedDates = new Array();
1360var clickedTimes = new Array();
1361function renderCalendar(m, y) {
1362	logBrowse("\n\nrenderCalendar(" + m + ", " + y + ")");
1363	// Calendar rendering borrowed from tix iPhone app
1364	var html = "";
1365	var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
1366
1367	var selectedDate;
1368	if (isNaN(m) || isNaN(y)) {
1369		selectedDate = new Date();
1370		for (var i = 0; i < items.length; i++) {
1371			if (items[i].DateTime > selectedDate) {
1372				m = items[i].DateTime.getMonth()
1373				y = items[i].DateTime.getFullYear()
1374				break;
1375			}
1376		}
1377		if (isNaN(m) || isNaN(y)) {
1378			m = selectedDate.getMonth()
1379			y = selectedDate.getFullYear()
1380		}
1381	}
1382	selectedDate = new Date(y, m, 1);
1383
1384	// Work out 1st day of month
1385	var dayMonthStarts = selectedDate.getDay();
1386	if (dayMonthStarts == 0) dayMonthStarts = 7;
1387
1388	// Days in current month
1389	var daysInCurrentMonth = new Date(selectedDate.getFullYear(), selectedDate.getMonth() + 1, 0).getDate();
1390
1391	// Days in previous month
1392	var previousMonth = new Date(selectedDate.getTime());
1393	previousMonth.setMonth(previousMonth.getMonth() - 1);
1394	daysInPreviousMonth = new Date(previousMonth.getFullYear(), previousMonth.getMonth() + 1, 0).getDate();
1395
1396	var nextMonth = new Date(selectedDate.getTime());
1397	nextMonth.setMonth(nextMonth.getMonth() + 1);
1398
1399	// Header
1400	html += "<div class='calHeader'><div class='calPrev'><a href='Javascript:renderCalendar(" + previousMonth.getMonth() + "," + previousMonth.getFullYear() + ")'>&lt;</a></div><div class='calDate'>" + monthName(selectedDate.getMonth(), false) + " " + selectedDate.getFullYear() + "</div><div class='calNext'><a href='Javascript:renderCalendar(" + nextMonth.getMonth() + "," + nextMonth.getFullYear() + ")'>&gt;</a></div></div><div class='calCells calWeek'><div>M</div><div>T</div><div>W</div><div>T</div><div>F</div><div class='calDisabled'>S</div><div class='calDisabled'>S</div></div>";
1401
1402
1403	// Tiles
1404	var noRows = 0;
1405	var dayCounter = 1;
1406
1407	for (var rowCounter = 1; rowCounter < 7; rowCounter++) {
1408
1409		html += "<div class='calCells'>";
1410
1411		for (var columnCounter = 1; columnCounter < 8; columnCounter++) {
1412
1413			var cellOutsideCurrentMonth = false;
1414			var cellSelected = false;
1415			var cellFestivalDate = false;// Sessions on that day
1416			var cellToday = false;
1417			var tileDate = new Date(selectedDate.getTime());
1418
1419			var day = 0;
1420			if (dayMonthStarts != 1 && dayCounter == 1) dayCounter = ((dayMonthStarts - 2) * -1);
1421
1422			if (rowCounter == 1 && columnCounter < dayMonthStarts) {
1423				// First row and listing previous month tile
1424				day = daysInPreviousMonth + dayCounter;
1425				cellOutsideCurrentMonth = true;
1426				tileDate.setMonth(tileDate.getMonth() - 1);
1427				dayCounter++;
1428
1429			} else {
1430				if (rowCounter == 1 && columnCounter == dayMonthStarts) dayCounter = 1;
1431
1432				if (dayCounter > daysInCurrentMonth) {
1433					// Next month
1434					day = dayCounter - daysInCurrentMonth;
1435					cellOutsideCurrentMonth = true;
1436					tileDate.setMonth(tileDate.getMonth() + 1);
1437				} else {
1438					// Current month
1439					day = dayCounter;
1440				}
1441
1442				dayCounter++;
1443			}
1444
1445			tileDate.setDate(day);
1446
1447			// Has the date been selected
1448			if (clickedDates.length > 0) {
1449
1450				for (var i = 0; i < clickedDates.length; i++) {
1451
1452					if (clickedDates.getDate() == tileDate.getDate() && clickedDates.getMonth() == tileDate.getMonth() && clickedDates.getFullYear() == tileDate.getFullYear()) {
1453						cellSelected = true;
1454						break;
1455					}
1456				}
1457
1458			}
1459
1460			var today = new Date();
1461			if (today.getDate() == tileDate.getDate() && today.getMonth() == tileDate.getMonth() && today.getFullYear() == tileDate.getFullYear()) {
1462				// today
1463				cellToday = true;
1464			}
1465
1466			for (var i = 0; i < items.length; i++) {
1467				if (items[i].DateTime instanceof Date && items[i].DateTime.getDate() == tileDate.getDate() && items[i].DateTime.getMonth() == tileDate.getMonth() && items[i].DateTime.getFullYear() == tileDate.getFullYear()) {
1468					// Festival date
1469					cellFestivalDate = true;
1470					break;
1471				}
1472			}
1473
1474			var className = "";
1475
1476			if (cellToday && cellFestivalDate && cellSelected) {
1477				className = "calOrganiser calToday calSelected";
1478
1479			} else if (cellToday && !cellFestivalDate && cellSelected) {
1480				className = "calToday calSelected";
1481
1482			} else if (cellToday && !cellFestivalDate && !cellSelected && cellOutsideCurrentMonth) {
1483				className = "calDisabled calToday";
1484
1485			} else if (cellToday && !cellFestivalDate && !cellSelected) {
1486				className = "calToday";
1487
1488			} else if (cellToday && cellFestivalDate && !cellSelected) {
1489				className = "calOrganiser calToday";
1490
1491			} else if (!cellToday && cellFestivalDate && !cellSelected) {
1492				className = "calOrganiser";
1493
1494			} else if (!cellToday && cellFestivalDate && cellSelected) {
1495				className = "calOrganiser calSelected";
1496
1497			} else if (!cellToday && !cellFestivalDate && cellSelected) {
1498				className = "calSelected";
1499
1500			} else if (cellOutsideCurrentMonth) {
1501				className = "calDisabled";
1502
1503			} else {
1504				className = "";
1505			}
1506
1507
1508			html += "<div class='" + className + "' id='d" + tileDate.getFullYear() + "-" + (tileDate.getMonth() + 1) + "-" + tileDate.getDate() + "'><a href=\"Javascript:calendarDateClicked('" + tileDate.getFullYear() + "-" + (tileDate.getMonth() + 1) + "-" + tileDate.getDate() + "');\">" + tileDate.getDate() + "</a></div>";
1509
1510
1511		}
1512
1513		html += "</div>";// calCells
1514
1515		noRows++;
1516
1517		if (dayCounter > daysInCurrentMonth) break;
1518	}
1519
1520	var x = document.getElementById("ft_calendar")
1521	if (x) x.innerHTML = html;
1522}
1523
1524function calendarDateClicked(selectedDate) {
1525	var selDate = parseDate(selectedDate);
1526	var x = document.getElementById("d" + selDate.getFullYear() + "-" + (selDate.getMonth() + 1) + "-" + selDate.getDate());
1527	var y = document.getElementById("ft_so_Do" + selectedDate);
1528	if (y) {
1529		x.className = x.className.replace("calSelected", "");
1530		ft_removeSelectedOption("D", selectedDate);
1531	} else {
1532		if (x) x.className += " calSelected";
1533		ft_addSelectedOption("D", selectedDate);
1534	}
1535}
1536
1537function calendarTimeClicked(selectedTime) {
1538	var x = document.getElementById("t" + selectedTime);
1539	var y = document.getElementById("ft_so_To" + selectedTime);
1540	var y = document.getElementById("ft_so_To" + selectedTime);
1541	if (y) {
1542		x.className = "";
1543		ft_removeSelectedOption("T", selectedTime);
1544	} else {
1545		x.className = "tabSelected";
1546		ft_addSelectedOption("T", selectedTime);
1547	}
1548}
1549
1550
1551
1552
1553
1554// #mark ------ Utility - Prefs  ------
1555
1556
1557function ft_browseOptionsRead() {
1558	console.log("=== ft_browseOptionsRead() called ===");
1559	console.log("ferve_h:", ferve_h);
1560
1561	var savedOptions = window.sessionStorage.getItem("FTBR_" + ferve_h + "-options");
1562
1563	if (savedOptions == null) {
1564		console.log("No saved options found, returning early");
1565		return;
1566	}
1567
1568	var keyword = window.sessionStorage.getItem("FTBR_" + ferve_h + "-keyword");
1569	if (keyword.length > 0 && document.getElementById("ft_keyword")) document.getElementById("ft_keyword").value = keyword;
1570
1571	sortMode = window.sessionStorage.getItem("FTBR_" + ferve_h + "-sortMode");
1572	scrollY = window.sessionStorage.getItem("FTBR_" + ferve_h + "-scrollY");
1573	console.log("Saved sortMode:", sortMode, "scrollY:", scrollY);
1574
1575	var tmpSelectedOptions = savedOptions;
1576	if (tmpSelectedOptions.length > 0) {
1577		tmpSelectedOptions = JSON.parse(tmpSelectedOptions);
1578	}
1579
1580
1581	for (var i = 0; i < tmpSelectedOptions.length; i++) {
1582		var group = tmpSelectedOptions[i].substring(0, tmpSelectedOptions[i].indexOf("o")).toUpperCase();
1583		var option = tmpSelectedOptions[i].substring(tmpSelectedOptions[i].indexOf("o") + 1);
1584
1585		if (group.length == 0 || option.length == 0) {
1586			continue;
1587		}
1588		if (group == "D") {
1589			calendarDateClicked(option);
1590		} else if (group == "T") {
1591			calendarTimeClicked(option);
1592		} else if (group != "K") {
1593			ft_toggleCheckbox(group, option);
1594		}
1595	}
1596}
1597
1598function ft_browseOptionsSave() {
1599	window.sessionStorage.setItem("FTBR_" + ferve_h + "-options", JSON.stringify(selectedOptions));
1600	window.sessionStorage.setItem("FTBR_" + ferve_h + "-keyword", (document.getElementById("ft_keyword") ? document.getElementById("ft_keyword").value : ""));
1601	window.sessionStorage.setItem("FTBR_" + ferve_h + "-sortMode", sortMode);
1602	window.sessionStorage.setItem("FTBR_" + ferve_h + "-scrollY", scrollY);
1603}
1604
1605
1606// #mark ------ Utility  ------
1607
1608function ft_link(l) {
1609	if (l.indexOf("http") > -1) return l;
1610
1611	if (window.location.protocol == "https:") {
1612		return ferve.url + l.substring(1);
1613	} else {
1614		return l;
1615	}
1616}
1617
1618
1619function removeElement(array, itemNo) {
1620	size = array.length;
1621	validNo = (itemNo != "NaN");
1622	inRange = ((itemNo >= 0) && (itemNo <= array.length));
1623	if (validNo && inRange) {
1624		for (var i = 0; i <= size; i++)
1625			array[i] = ((i == itemNo) ? "delete" : array[i]);
1626		for (var j = itemNo; j < size - 1; j++)
1627			if (j != size) array[j] = array[j + 1];
1628		array.length = size - 1;
1629		return array
1630	}
1631	return array
1632}
1633
1634
1635function switchCSS(cn) {
1636	var x = document.getElementById("ft_events");
1637	if (x) x.className = cn;
1638
1639	// reset echo
1640	var elem, nodes = document.querySelectorAll("img[data-echoFT]"), length = nodes.length;
1641	for (var i = 0; i < length; i++) {
1642		elem = nodes[i];
1643		elem.setAttribute("data-echo", elem.getAttribute("data-echoFT"));
1644		elem.removeAttribute("data-echoFT");
1645	}
1646	echo.render();
1647}
1648

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.