PageSourceSearch

https://events.oriskanyfallslibrary.org/js/am-events-listings.js?v=1483

js oriskanyfallslibrary.org collected 2026-10-08 03:27:50 UTC 84,494 bytes, 1,972 lines download raw bytes

1(function ($, window, document, undefined) {
2	$.support.cors = true;
3
4	// bootstrap-datepicker puts aria-selected on its day cells, but those cells have
5	// role="button" -- and aria-selected is only valid on gridcell/option/row/tab/treeitem.
6	// The selected state is already carried by aria-pressed on the same cell, so dropping
7	// aria-selected is lossless for AT users and clears axe's aria-allowed-attr rule.
8	function installCalendarAriaFix(root) {
9		if (window.__amEventsCalendarAriaFixInstalled) return;
10		window.__amEventsCalendarAriaFixInstalled = true;
11		var target = root && root.querySelectorAll ? root : (document.body || document.documentElement);
12		var strip = function () {
13			var tds = target.querySelectorAll('td.day[aria-selected]');
14			for (var i = 0; i < tds.length; i++) tds[i].removeAttribute('aria-selected');
15		};
16		strip();
17		if (window.MutationObserver) {
18			var scheduled = false;
19			var flush = function () {
20				scheduled = false;
21				strip();
22			};
23			var schedule = function () {
24				if (scheduled) return;
25				scheduled = true;
26				if (window.requestAnimationFrame) {
27					window.requestAnimationFrame(flush);
28					return;
29				}
30				window.setTimeout(flush, 0);
31			};
32			var obs = new MutationObserver(schedule);
33			obs.observe(target, { childList: true, subtree: true });
34		}
35	}
36
37	function escapeHtml(value) {
38		return String(value == null ? '' : value)
39			.replace(/&/g, '&amp;')
40			.replace(/</g, '&lt;')
41			.replace(/>/g, '&gt;')
42			.replace(/"/g, '&quot;')
43			.replace(/'/g, '&#39;');
44	}
45
46	var pluginName = "amEvents",
47		defaults = {
48			title: '',
49			view: 'list',
50			range: 'today',
51			ages: [],
52			excludedAges: [],
53			types: [],
54			excludedTypes: [],
55			locale: 'en-US',
56			timezone: 'America/New_York',
57			locations: [],
58			excludedLocations: [],
59			linkTarget: '_blank',
60			inCellEvents: 3,
61			private: false,
62			eventsGroupCalenderBAge: false,
63			term: '',
64			end: '',
65			start: '',
66			gridDate: '',
67			filters: {}, // added for brochure
68			filterOptions: {},
69			filterMaxItems: 15,
70			filterCurrentColumn: null,
71			filterCurrentColumnCount: 0,
72			showBrochure: false,
73			showRSS: false,
74			showICAL: false,
75			singleBranch: false,
76			limitWaitlist: false,
77			allowWaitlistSubscription: false,
78			customInterestedText: '',
79			brochuredata: null,
80			allowSubs: false,
81			brochureNoPopup: false,
82			showImagesInListGrid: false,
83			testMode: 0,
84			duration: '',
85			emitEvents: false,
86			embeddedMode: false,
87			embedSource: '',
88			parentOrigin: '',
89		};
90
91	function Plugin(element, options) {
92		var self = this;
93		this.element = element;
94		this.$element = $(element);
95
96		if (!options.view) options.view = self._readCookie('eventsView') ? self._readCookie('eventsView') : 'list';
97		this.settings = $.extend({}, defaults, options);
98		this._defaults = defaults;
99		this._name = pluginName;
100		this.paused = false;
101		this.rangeUpdating = false;
102		this.tagsLoaded = false;
103		this.locationsLoaded = false;
104		this.agesLoaded = false;
105		this._init();
106	}
107
108	$.extend(Plugin.prototype, {
109		_init: function () {
110			var self = this;
111			var customEventLabel = escapeHtml(self.settings.customEventLabel);
112			var customEventsLabel = escapeHtml(self.settings.customEventsLabel);
113			var params = self._getURLSearchParameters();
114			if (params.l) {
115				self.settings.locations = params.l
116					.split(',')
117					.map(s => s.trim().toLowerCase().replace(/,/g, ''))
118					.filter(Boolean);
119			}
120			if (params.et) {
121				self.settings.excludedTypes = params.et.split(',').map(s => s.trim().toLowerCase());
122			}
123			if (params.t) {
124				try {
125					const decodedTypes = JSON.parse(decodeURIComponent(params.t));
126					self.settings.types = decodedTypes.map(s => s.trim().toLowerCase());
127				} catch (e) {
128					const splitTypes = decodeURIComponent(params.t).split(',').map(s => s.trim().toLowerCase());
129					self.settings.types = [...new Set(splitTypes)];
130				}
131			}
132			self.initComplete = false;
133			self._embeddedHeightFrame = null;
134			self._lastEmbeddedHeight = 0;
135			self._embeddedHeightObserver = null;
136			self._embeddedResizeObserver = null;
137			self.$element.addClass('events-holder');
138			if (self.settings.embeddedMode) {
139				// When custom CSS is used to set the html/body height to 100% it breaks the height calculation used by this embedded widget.
140				// By setting the height to auto we mitigate this.
141				document.documentElement.style.cssText = 'height: auto !important;';
142				document.body.style.cssText = 'height: auto !important;';
143				self.$element.addClass('events-holder--embedded');
144				self.$element.on('am-events-listings.url-update', function (event, data) {
145					if (data && data.data) {
146						self._postToParent('url-update', { pathAndQuery: data.data });
147					}
148				});
149				self._embeddedHostMessageHandler = function (event) {
150					var origin = self._getParentOrigin();
151					if (!origin || event.origin !== origin) {
152						return;
153					}
154					if (event.source !== window.parent) {
155						return;
156					}
157					var data = (event.data && typeof event.data === 'object') ? event.data : {};
158					if (data.source !== 'create-classic-attend-host' || !data.type) {
159						return;
160					}
161					if (data.type === 'height-request') {
162						self._scheduleHeightPost(true);
163						return;
164					}
165					if (data.type !== 'navigate' || !data.payload || !data.payload.pathAndQuery) {
166						return;
167					}
168					try {
169						var next = new URL(data.payload.pathAndQuery, window.location.origin);
170						if (next.pathname === window.location.pathname && next.search === window.location.search) {
171							return;
172						}
173						window.location.href = next.pathname + next.search + next.hash;
174					} catch (e) {
175						return;
176					}
177				};
178				window.addEventListener('message', self._embeddedHostMessageHandler);
179				$(document).off('click.amEventsEmbeddedNavigate').on('click.amEventsEmbeddedNavigate', 'a[data-embedded-event-path]', function (event) {
180					if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.which === 2) {
181						return;
182					}
183					var pathAndQuery = $(this).attr('data-embedded-event-path');
184					if (!pathAndQuery || !self._canPostToParent()) {
185						return;
186					}
187					event.preventDefault();
188					self._postToParent('navigate', { pathAndQuery: pathAndQuery });
189				});
190				if (typeof MutationObserver !== 'undefined') {
191					self._embeddedHeightObserver = new MutationObserver(function () {
192						self._scheduleHeightPost();
193					});
194					self._embeddedHeightObserver.observe(self.element, {
195						attributes: true,
196						childList: true,
197						subtree: true
198					});
199				}
200				if (typeof ResizeObserver !== 'undefined') {
201					self._embeddedResizeObserver = new ResizeObserver(function () {
202						self._scheduleHeightPost();
203					});
204					self._embeddedResizeObserver.observe(self.element);
205				}
206				window.addEventListener('load', function () {
207					self._scheduleHeightPost();
208				});
209			}
210			if (self.settings.singleBranch) {
211				self.$element.addClass('events-single-branch');
212			}
213			var navigation = $('<div class="row events-filter-row"></div>').appendTo(self.element);
214			self.views = $('<div class="row events-view-row"></div>').appendTo(self.element);
215
216			$('<div class="col-md-4 col-sm-12 col-xs-12"><label for="eventsearch" class="sr-only">Search events</label><input id="eventsearch" class="form-control events-search-field"  placeholder="Search for ' + customEventsLabel + '" type="text"/></div>').appendTo(navigation);
217			var filters = $('<div class="col-md-6 col-sm-12 col-xs-12"><span class="hidden-xs events-single-hidden">Filter by: </span></div>').appendTo(navigation);
218			self.viewPicker = $('<div class="col-md-2 col-sm-12 col-xs-12 events-views"></div>').appendTo(navigation);
219
220			$('<button class="events-option-dropdown noselect events-single-hidden" data-show-filter="events-location-section"  aria-label="Location filter button" tabindex="0">Location</button>').appendTo(filters);
221			$('<button class="events-option-dropdown noselect" data-show-filter="events-age-section" aria-label="Age group filter button" tabindex="0">Age group</button>').appendTo(filters);
222			$('<button class="events-option-dropdown noselect" data-show-filter="events-type-section" aria-label="' + customEventLabel + ' type filter button" tabindex="0">' + customEventLabel + ' type</button>').appendTo(filters);
223
224			var checked = (self.settings.locations.length + self.settings.excludedLocations.length) === 0 ? 'checked="checked"' : '';
225
226			self.locationList = $('<div id="event_locations"></div>');
227			$('<div class="events-filter-section events-location-section">' +
228				'<div class="events-filter-close">close</div>' +
229				'<div class="heading4">Location</div>' +
230				'<div class="checkbox">' +
231				'   <label for="loc_all" class="check-all-filter-text">' +
232				'       <input id="loc_all" type="checkbox" ' + checked + ' aria-label="View all locations">View events at all branches' +
233				'   </label>' +
234				'</div><hr>' +
235				'</div>').append(self.locationList).appendTo(self.element);
236
237			var checked = self.settings.ages.length === 0 ? 'checked="checked"' : '';
238			self.ageList = $('<div id="event_ages"></div>');
239			$('<div class="events-filter-section events-age-section">' +
240				'<div class="events-filter-close">close</div>' +
241				'<div class="heading4">Age group</div>' +
242				'<div class="checkbox">' +
243				'   <label for="group_all" class="check-all-filter-text">' +
244				'       <input id="group_all" type="checkbox" ' + checked + ' aria-label="View all Ages">View all age groups' +
245				'   </label>' +
246				'</div><hr>' +
247				'</div>').append(self.ageList).appendTo(self.element);
248
249			var checked = self.settings.types.length === 0 ? 'checked="checked"' : '';
250			self.typeList = $('<div id="event_types"></div>');
251			$('<div class="events-filter-section events-type-section">' +
252				'<div class="events-filter-close">close</div>' +
253				'<div class="heading4">' + customEventLabel + ' type</div>' +
254				'<div class="checkbox">' +
255				'   <label for="type_all" class="check-all-filter-text">' +
256				'       <input id="type_all" type="checkbox" ' + checked + ' aria-label="View all Types">View all ' + customEventLabel + ' types' +
257				'   </label>' +
258				'</div><hr>' +
259				'</div>').append(self.typeList).appendTo(self.element);
260
261
262			self.todayButton = $('<button aria-label="Show events for today" class="events-option-dropdown">Today</button>').appendTo(self.viewPicker);
263
264			$('<i data-view="list" role="button" tabindex="0" aria-pressed="true" aria-label="Show list view" class="fa fa-list"></i>').appendTo(self.viewPicker);
265			$('<i data-view="grid" role="button" tabindex="0" aria-pressed="mixed" aria-label="Show calendar view" class="am-events"></i>').appendTo(self.viewPicker);
266
267
268			self.gridView = $('<div data-view="grid" class="events-list-view"></div>').appendTo(self.views);
269			var listView = $('<div data-view="list" class="events-list-view"></div>').appendTo(self.views);
270			var brochureButton = self.settings.showBrochure ? '<div class="events-brochure"><button class="events-option-dropdown events-brochure-button"><i class="fa fa-file-pdf-o" style="color: #ED0202;"></i>&nbsp;Create Brochure</button></div>' : '';
271
272			if (self.settings.start !== '') {
273				self.settings.start = moment(self.settings.start);
274				self.settings.end = self.settings.end !== '' ? moment(self.settings.end) : self.settings.start;
275			} else {
276				self.settings.start = moment();
277				self.settings.end = self.settings.start
278			}
279
280			self._quickRangeBase = (self.settings.embeddedMode && self._getParentOrigin())
281				? self._getParentOrigin() + window.location.pathname
282				: '';
283
284			self.listViewLeft = $('<div class="events-left">' +
285				'<div class="events-details">' +
286				'<ul class="nav nav-tabs" role="tablist">' +
287				'<li role="presentation" class="active"><a href="#calendar" aria-controls="calendar" role="tab" data-toggle="tab">Calendar</a></li>' +
288				'<li role="presentation"><a href="#date_range" aria-controls="date_range" role="tab" data-toggle="tab">Date range</a></li>' +
289				'</ul>' +
290				'<div class="tab-content">' +
291				'<div role="tabpanel" class="tab-pane active" id="calendar">' +
292				'<div class="events-calendar"></div>' +
293				'</div>' +
294				'<div role="tabpanel" class="tab-pane" id="date_range">' +
295				'<div class="events-range input-daterange input-group" id="datepicker">' +
296				'<label for="start">From</label>' +
297				'<div class="input-group events-date-input">' +
298				'<input type="text" class="form-control" value="' + self.settings.start.format('MMMM D YYYY') + '" name="start" id="start" aria-label="Start Date"><span class="input-group-addon"><i class="am-events"></i></span>' +
299				'</div>' +
300				'<label for="end">To</label>' +
301				'<div class="input-group events-date-input">' +
302				'<input type="text" class="form-control" value="' + (self.settings.end !== '' ? self.settings.end.format('MMMM D YYYY') : self.settings.start.format('MMMM D YYYY')) + '" name="end" id="end" aria-label="End Date"><span class="input-group-addon"><i class="am-events"></i></span>' +
303				'</div>' +
304				'</div>' +
305				'</div>' +
306				'</div>' +
307				'</div>' +
308				'<div class="events-link hidden-xs">' +
309				'<a aria-label="Show events for today" href="' + self._quickRangeBase + '?r=today" data-type="today" data-embedded-nav-processed="1">Today</a>' +
310				'<a aria-label="Show events for tomorrow" href="' + self._quickRangeBase + '?r=tomorrow" data-type="tomorrow" data-embedded-nav-processed="1">Tomorrow</a>' +
311				'<a aria-label="Show events for this week" href="' + self._quickRangeBase + '?r=thisweek" data-type="thisweek" data-embedded-nav-processed="1">This week</a>' +
312				'<a aria-label="Show events for next week" href="' + self._quickRangeBase + '?r=nextweek" data-type="nextweek" data-embedded-nav-processed="1">Next week</a>' +
313				'<a aria-label="Show events for this month" href="' + self._quickRangeBase + '?r=thismonth" data-type="thismonth" data-embedded-nav-processed="1">This month</a>' +
314				'<a aria-label="Show events for next month" href="' + self._quickRangeBase + '?r=nextmonth" data-type="nextmonth" data-embedded-nav-processed="1">Next month</a>' +
315				'</div>' +
316				'</div>
316').appendTo(listView);
317			self.listViewRight = $('<div class="events-right">' +
318				'<h1 class="headingtext">' + customEventsLabel + '</h1>' +
319				'<div class="events-date-bar noselect">' +
320				'<div class="events-day-title">' +
321				'<span data-by="-1" tabindex="0" role="button" aria-label="Previous day" class="events-change-day events-change-prev fa fa-chevron-left"></span>' +
322				'<span aria-live="polite" class="events-date-string headingtext"></span>' +
323				'<span data-by="1" tabindex="0" role="button" aria-label="Next day" class="events-change-day events-change-next fa fa-chevron-right"></span>' +
324				'</div>' +
325				'<div class="events-range-title">' +
326				'<div class="events-date-range-string headingtext"></div>' +
327				'</div>' +
328				'</div>' +
329				'<div class="events-filter-details">' +
330				'<div aria-label="Clear the current filters" class="events-filter-clear">Clear filter</div>' +
331				'<div class="events-filter-message"></div>' +
332				'</div>' +
333				'<div class="events-body"></div>' +
334				'</div>').appendTo(listView);
335
336			var a = moment().startOf('week');
337			var b = a.clone().add(7, 'days');
338			var days = '';
339			for (var m = a; m.isBefore(b); m.add(1, 'days')) {
340				days += '<div>' + m.format('dddd') + '</div>';
341			}
342			$('<div class="events-grid">' +
343				'<h2 class="headingtext">' + customEventsLabel + '</h2>' +
344				'<div class="events-date-bar noselect">' +
345				'<div class="events-day-title">' +
346				'<span data-by="-1" tabindex="0" role="button" aria-label="Previous month" class="events-change-day events-change-prev fa fa-chevron-left"></span>' +
347				'<span aria-live="polite" class="events-date-string headingtext"></span>' +
348				'<span data-by="1" tabindex="0" role="button" aria-label="Next month" class="events-change-day events-change-next fa fa-chevron-right"></span>' +
349				'</div>' +
350				'</div>' +
351				'<div class="events-filter-details">' +
352				'<div class="events-filter-clear">Clear filter</div>' +
353				'<div class="events-filter-message"></div>' +
354				'</div>' +
355				'<div class="events-grid-header">' + days + '</div>' +
356				'<div class="events-grid-body"></div>' +
357				'</div>').appendTo(self.gridView);
358
359
360			self._loadFilters();
361			self._bindEvents();
362			if (self.settings.range === 'today' && !self.settings.filters.date && (self.settings.start && !self.settings.end)) {
363				var now = moment();
364				$('.events-range.input-daterange input[name=start]', this.listViewLeft).datepicker('update', now.format('MMMM D YYYY'));
365				$('.events-range.input-daterange input[name=end]', this.listViewLeft).datepicker('update', now.endOf('month').format('MMMM D YYYY'));
366				$('.events-details a[href="#date_range"]', this.listViewLeft).tab('show');
367			}
368
369			var acceptableRanges = ['days', 'weeks', 'months'];
370			if (acceptableRanges.includes(self.settings.range) && '' != self.settings.duration) {
371				self._updateDateRange();
372			} else if (self.settings.range !== 'today' && $('a[data-type=' + self.settings.range + ']').length > 0) {
373				self._updateDateRange();
374			} else {
375
376				if ((self.settings.start && self.settings.end) && (self.settings.start.format('MMMM D YYYY') !== self.settings.end.format('MMMM D YYYY'))) {
377					$('.events-details a[href="#date_range"]', this.listViewLeft).tab('show');
378					$('.events-range.input-daterange input[name=start]', this.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
379					$('.events-range.input-daterange input[name=end]', this.listViewLeft).datepicker('update', (self.settings.end ? self.settings.end.format('MMMM D YYYY') : self.settings.start.format('MMMM D YYYY')));
380				}
381
382				if (self.settings.start && !self.settings.end) {
383					$('.events-details a[href="#calendar"]', this.listViewLeft).tab('show');
384				}
385			}
386			if (self.settings.term && self.settings.term.length > 0) {
387				$('.events-search-field', navigation).val(self.settings.term);
388			}
389
390			self.setView(self.settings.view);
391			// self.setView('grid');
392			self.initComplete = true;
393			self._scheduleHeightPost();
394		},
395		_getURLSearchParameters: function () {
396			var self = this;
397			var prmstr = window.location.search.substr(1);
398			return prmstr != null && prmstr != "" ? this._transformToAssocArray(prmstr) : {};
399		},
400		_transformToAssocArray: function (prmstr) {
401			var params = {};
402			var prmarr = prmstr.split("&");
403			for (var i = 0; i < prmarr.length; i++) {
404				var tmparr = prmarr[i].split("=");
405				var key = decodeURIComponent(tmparr[0]);
406				var val = decodeURIComponent(tmparr[1] || "").replace(/\+/g, " ");
407				params[key] = val;
408			}
409			return params;
410		},
411		_setURLPieces: function () {
412			var self = this;
413			var url = window.location.pathname + '?';
414			var params = '';
415			var vars = self._getURLSearchParameters();
416			if (self.settings.range !== 'today') {
417				vars.r = self.settings.range;
418			}
419
420			if (self.settings.filters.locations.length > 0) {
421				vars.l = self.settings.filters.locations
422					.map(function (loc) {
423						return loc.replace(/,/g, '');
424					})
425					.join(',');
426			} else {
427				delete vars['l'];
428			}
429			if (self.settings.filters.ages.length > 0) {
430				vars.a = self.settings.filters.ages.join(',');
431			} else {
432				delete vars['a'];
433			}
434			if (self.settings.filters.types.length > 0) {
435				vars.t = encodeURIComponent(JSON.stringify(self.settings.filters.types));
436			} else {
437				delete vars['t'];
438			}
439			if (self.settings.filters.term && self.settings.filters.term.length > 0) {
440				vars.term = self.settings.filters.term;
441			} else {
442				delete vars['term'];
443			}
444			delete vars['start'];
445			delete vars['end'];
446			delete vars['r'];
447			if (self.settings.range !== 'today') {
448				vars['r'] = self.settings.range;
449			}
450			//if the range is not specified with a duration then remove the duration
451			if (false == (['days', 'weeks', 'months', 'range'].includes(self.settings.range))) {
452				delete vars['n'];
453			}
454			if (self.settings.range === 'range' && this.settings.view !== 'grid') {
455				vars['start'] = self.settings.start.format('YYYY-MM-DD');
456				if (self.settings.end === '' || self.settings.end.format('YYYY-MM-DD') === self.settings.start.format('YYYY-MM-DD')) {
457					delete vars['end'];
458				} else {
459					vars['end'] = self.settings.end.format('YYYY-MM-DD');
460				}
461			}
462			if (this.settings.view === 'grid' && self.settings.gridDate) {
463				vars['d'] = self.settings.gridDate;
464			}
465			if (self.settings.view === 'list') {
466				delete vars['v'];
467			} else {
468				vars['v'] = self.settings.view;
469			}
470
471			if (Object.keys(vars).length > 0) {
472				params = $.param(vars, true);
473			}
474
475			if (self.settings.emitEvents) {
476				var data = { 'data': (url + params) };
477				var event = new $.Event('am-events-listings.url-update', data);
478				self.$element.trigger(event, data);
479			} else {
480				window.history.pushState({ 'r': 'type' }, '', url + params);
481			}
482		},
483		_getParentOrigin: function () {
484			if (typeof this.settings.parentOrigin === 'string' && this.settings.parentOrigin.length > 0) {
485				return this.settings.parentOrigin;
486			}
487			if (document.referrer && document.referrer.length > 0) {
488				try {
489					return (new URL(document.referrer)).origin;
490				} catch (e) {
491					return '';
492				}
493			}
494			return '';
495		},
496		_updateQuickRangeLinks: function () {
497			var self = this;
498			var filters = self.settings.filters;
499			var filterParams = {
500				l: filters.locations.length > 0 ? filters.locations.map(function (loc) { return loc.replace(/,/g, ''); }).join(',') : '',
501				a: filters.ages.length > 0 ? filters.ages.join(',') : '',
502				t: filters.types.length > 0 ? encodeURIComponent(JSON.stringify(filters.types)) : '',
503				term: filters.term || ''
504			};
505			var vars = {};
506			$.each(filterParams, function (key, value) {
507				if (value) {
508					vars[key] = value;
509				}
510			});
511			self.listViewLeft.find('.events-link>a[data-type]').each(function () {
512				var $link = $(this);
513				vars.r = $link.attr('data-type');
514				$link.attr('href', self._quickRangeBase + '?' + $.param(vars, true));
515			});
516		},
517		_postToParent: function (type, payload) {
518			if (!this.settings.embeddedMode || !window.parent || window.parent === window) {
519				return;
520			}
521			var origin = this._getParentOrigin();
522			if (!origin) {
523				return;
524			}
525			window.parent.postMessage({
526				source: 'classic-attend',
527				type: type,
528				payload: payload || {}
529			}, origin);
530		},
531		_canPostToParent: function () {
532			if (!this.settings.embeddedMode || !window.parent || window.parent === window) {
533				return false;
534			}
535			if (this.settings.embedSource !== 'create-classic-attend') {
536				return false;
537			}
538			return !!this._getParentOrigin();
539		},
540		_getEmbeddedEventPath: function (eventId) {
541			var id = String(eventId || '').trim();
542			if (!id) {
543				return '';
544			}
545			return '/event/' + encodeURIComponent(id);
546		},
547		_getEmbeddedEventHref: function (pathAndQuery) {
548			if (!pathAndQuery || !this._canPostToParent()) {
549				return '';
550			}
551			var origin = this.settings.parentOrigin;
552			if (origin) {
553				return origin + pathAndQuery;
554			}
555			return pathAndQuery;
556		},
557		_bindEmbeddedEventLink: function ($link, pathAndQuery) {
558			if (!this.settings.embeddedMode || !$link || !pathAndQuery) {
559				return;
560			}
561			var embeddedHref = this._getEmbeddedEventHref(pathAndQuery);
562			if (embeddedHref) {
563				$link.attr('href', embeddedHref);
564			}
565			$link.attr('data-embedded-event-path', pathAndQuery);
566		},
567		_scheduleHeightPost: function (forcePost) {
568			var self = this;
569			if (!self.settings.embeddedMode) {
570				return;
571			}
572			if (self._embeddedHeightFrame) {
573				window.cancelAnimationFrame(self._embeddedHeightFrame);
574			}
575			self._embeddedHeightFrame = window.requestAnimationFrame(function () {
576				self._embeddedHeightFrame = null;
577				var scrollY = window.scrollY || window.pageYOffset || 0;
578				var rootElement = (self.element && self.element.nodeType === 1 ? self.element : self.$element && self.$element[0]) || null;
579				var holderHeight = rootElement ? Math.ceil($(rootElement).outerHeight() || 0) : 0;
580				var holderScrollHeight = rootElement ? Math.ceil(rootElement.scrollHeight || 0) : 0;
581				var contentHeight = Math.max(
582					holderHeight,
583					holderScrollHeight,
584					rootElement ? Math.ceil(rootElement.getBoundingClientRect().bottom + scrollY) : 0
585				);
586				if (document.body && document.body.children) {
587					for (var idx = 0; idx < document.body.children.length; idx++) {
588						var child = document.body.children[idx];
589						if (!child || /^(SCRIPT|STYLE|LINK|META|NOSCRIPT)$/i.test(child.tagName) || child.getClientRects().length === 0) {
590							continue;
591						}
592						// Fixed elements (e.g. the amPopupCover dimmer) size to the viewport, and in an
593						// auto-height iframe the viewport is whatever we last reported — counting them
594						// makes the reported height chase itself and the host page jumps.
595						if (window.getComputedStyle(child).position === 'fixed') {
596							continue;
597						}
598						var childBottom = Math.ceil(child.getBoundingClientRect().bottom + scrollY);
599						contentHeight = Math.max(contentHeight, childBottom);
600					}
601				}
602				contentHeight += 8;
603				if (!contentHeight || (!forcePost && contentHeight === self._lastEmbeddedHeight)) {
604					return;
605				}
606				self._lastEmbeddedHeight = contentHeight;
607				self._postToParent('height-update', { height: contentHeight });
608			});
609		},
610		_updateDateRange: function () {
611			var self = this;
612			self.paused = true;
613			var now = moment();
614
615			switch (self.settings.range) {
616				case 'today':
617					self.settings.start = now.startOf('day');
618					self.settings.end = now.clone();
619					$('div.events-calendar').datepicker('setDate', now.startOf('day').toDate());
620					$('.events-details a[href="#calendar"]').tab('show');
621					break;
622				case 'tomorrow':
623					self.settings.start = now.add(1, 'day').startOf('day');
624					self.settings.end = now.clone();
625					$('div.events-calendar').datepicker('setDate', self.settings.start.toDate());
626					$('.events-details a[href="#calendar"]').tab('show');
627					break;
628				case 'thisweek':
629					self.settings.start = now;
630					self.settings.end = self.settings.start.clone().endOf('week');
631					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
632					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
633					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
634					break;
635				case 'thismonth':
636					self.settings.start = now;
637					self.settings.end = self.settings.start.clone().endOf('month');
638					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
639					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
640					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
641					break;
642				case 'nextweek':
643					self.settings.start = now.add(1, 'week').startOf('week');
644					self.settings.end = self.settings.start.clone().endOf('week');
645					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
646					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
647					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
648					break;
649				case 'nextmonth':
650					self.settings.start = now.add(1, 'month').startOf('month');
651					self.settings.end = self.settings.start.clone().endOf('month');
652					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
653					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
654					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
655					break;
656				case 'days':
657					self.settings.start = now;
658					self.settings.end = now.clone().add(self.settings.duration, 'days');
659					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
660					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
661					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
662					break;
663				case 'weeks':
664					self.settings.start = now;
665					self.settings.end = now.clone().add(self.settings.duration, 'weeks');
666					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
667					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
668					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
669					break;
670				case 'months':
671					self.settings.start = now;
672					self.settings.end = now.clone().add(self.settings.duration, 'months');
673					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
674					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
675					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
676					break;
677			}
678			self.paused = false;
679			self._update();
680		},
681		_bindEvents: function () {
682			var self = this;
683			self.gridView.data('date', moment(self.settings.gridDate).startOf('month'));
684			self.viewPicker.on('click keyup', '[data-view]', function (e) {
685				e.preventDefault();
686
687				var isSpace = e.type === 'keyup' && (e.which == 32 || e.keyCode == 32 || e.key == " ");
688				var isEnter = e.type === 'keyup' && (e.which == 13 || e.keyCode == 13 || e.key == "Enter");
689
690				if (e.type === 'click' || isSpace || isEnter) {
691					$('[data-view]').each(function (_, el) {
692						$(el).attr('aria-pressed', 'mixed');
693					})
694
695					self.setView($(this).attr('data-view'));
696					$(this).attr('aria-pressed', true);
697				}
698			});
699			installCalendarAriaFix(self.element);
700			$('.events-range.input-daterange', self.listViewLeft).datepicker({
701				todayBtn: "linked",
702				title: "Events calendar",
703				orientation: "bottom left",
704				format: 'MM d yyyy',
705				todayHighlight: true
706			}).on('changeDate', function (event) {
707				self.settings.range = 'range';
708				self.settings.start = moment($('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('getDate'));
709				self.settings.end = moment($('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('getDate'));
710				self._update();
711			});
712			$('.events-date-input').on('click', '.input-group-addon', function (event) {
713				event.preventDefault();
714				event.stopPropagation();
715				$(this).closest('.events-date-input').find('input').datepicker('show');
716			});
717
718			$('div.events-calendar', self.listViewLeft).datepicker({
719				title: "Events calendar",
720				startDate: new Date()
721			}).on('changeDate', function (event) {
722				event.preventDefault();
723				self.paused = true;
724				$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', moment(event.date).format('MMMM D YYYY'));
725				$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', moment(event.date).format('MMMM D YYYY'));
726				self.settings.start = moment(event.date).startOf('day');
727				self.settings.end = moment(event.date).endOf('day');
728				if (!self.rangeUpdating) {
729					self.settings.range = 'range';
730				}
731				self.paused = false;
732				self._update();
733			});
734
735			if (self.settings.start === self.settings.end) {
736				self.rangeUpdating = true;
737				$('div.events-calendar', self.listViewLeft).datepicker('setDate', new Date(self.settings.start.format('MMMM D YYYY')));
738				self.rangeUpdating = false;
739			}
740
741			self.$element.on('click', '.events-option-dropdown', function (event) {
742				if (!$(event.target).hasClass('events-option-dropdown')) return;
743				event.preventDefault();
744				var hide = $('.' + $(this).attr('data-show-filter')).is(':visible');
745				$('.events-filter-section', self.$element).fadeOut();
746				if (!hide) {
747					$('div.' + $(this).attr('data-show-filter'), self.$element).css({
748						top: 30,
749						left: 0
750					}).position({
751						my: "left top",
752						at: "left bottom",
753						of: this, // or $("#otherdiv)
754						collision: "fit",
755						within: self.$element,
756						using: function (pos, data) {
757							var menu = data.element.element;
758							pos.top = data.target.top + data.target.height + 30;
759							$(menu).css(pos)
760						}
761					}).fadeIn();
762				}
763			}).on('click', '.events-filter-close', function (event) {
764				event.preventDefault();
765				$(this).closest('.events-filter-section').fadeOut();
766			}).on('change', '#loc_all', function (event) {
767				if ($(this).is(':checked')) {
768					self.$element.find('#event_locations [id^=loc_]').prop('checked', false);
769				} else {
770					$(this).prop('checked', true);
771				}
772				self._update();
773			}).on('change', '#event_locations [id^=loc_]', function (event) {
774				if ($(this).is(':checked')) {
775					self.$element.find('#loc_all').prop('checked', false);
776				} else if (self.$element.find('#event_locations [id^=loc_]:checked').length === 0) {
777					self.$element.find('#loc_all').prop('checked', true);
778				}
779				self._update();
780			}).on('change', '#group_all', function (event) {
781				if ($(this).is(':checked')) {
782					self.$element.find('#event_ages [id^=group_]').prop('checked', false);
783				} else {
784					$(this).prop('checked', true);
785				}
786				self._update();
787			}).on('change', '#event_ages [id^=group_]', function (event) {
788				if ($(this).is(':checked')) {
789					self.$element.find('#group_all').prop('checked', false);
790				} else if (self.$element.find('#event_locations [id^=group_]:checked').length === 0) {
791					self.$element.find('#group_all').prop('checked', true);
792				}
793				self._update();
794			}).on('change', '#type_all', function (event) {
795				if ($(this).is(':checked')) {
796					self.$element.find('#event_types [id^=type_]').prop('checked', false);
797				} else {
798					$(this).prop('checked', true);
799				}
800				self._update();
801			}).on('change', '#event_types [id^=type_]', function (event) {
802				if ($(this).is(':checked')) {
803					self.$element.find('#type_all').prop('checked', false);
804				} else if (self.$element.find('#event_types [id^=type_]:checked').length === 0) {
805					self.$element.find('#type_all').prop('checked', true);
806				}
807				self._update();
808			}).on('click', '.events-filter-clear', function (event) {
809				event.preventDefault();
810				self.$element.find('.events-search-field').val('');
811				self.$element.find('#loc_all').prop('checked', true);
812				self.$element.find('#group_all').prop('checked', true);
813				self.$element.find('#type_all').prop('checked', true);
814				self.$element.find('#event_ages [id^=group_]').prop('checked', false);
815				self.$element.find('#event_types [id^=type_]').prop('checked', false);
816				self.$element.find('#event_locations [id^=loc_]').prop('checked', false);
817				self._update();
818			}).on('keyup', '.events-search-field', function (event) {
819				if (self.SearchTimer) {
820					clearTimeout(self.SearchTimer);
821					self.SearchTimer = false;
822				}
823				var term = $(this).val();
824				self.SearchTimer = setTimeout(function () {
825					self._doSearch(term);
826				}, 750);
827			});
828			$('.events-change-day', self.listViewRight).on('click keyup', function (event) {
829				event.preventDefault();
830
831				var isSpace = event.type === 'keyup' && (event.which === 32 || event.keyCode === 32 || event.key === " ");
832				var isEnter = event.type === 'keyup' && (event.which === 13 || event.keyCode === 13 || event.key === "Enter");
833
834				if (event.type === 'click' || isSpace || isEnter) {
835					self.settings.range = 'range';
836					var newdate = moment($('div.events-calendar', self.listViewLeft).datepicker('getDate')).add($(this).attr('data-by'), 'day').startOf('day');
837					if (newdate.isBefore(moment().startOf('day'))) return;
838					$('div.events-calendar', self.listViewLeft).datepicker('setDate', newdate.toDate());
839				}
840			});
841			self.listViewLeft.on('click', '.events-link>a', function (event) {
842				if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.which === 2) {
843					return;
844				}
845				event.preventDefault();
846				self.paused = true;
847				self.settings.range = $(this).attr('data-type');
848				self.rangeUpdating = true;
849				self._updateDateRange();
850				self.rangeUpdating = false;
851			});
852			self.gridView.on('click', '.events-grid-cell', function (event) {
853				event.preventDefault();
854				var selected = $(this).hasClass('events-grid-cell-selected');
855				self.gridView.find('.events-grid-day-list').remove();
856				self.gridView.find('.events-grid-cell-selected').removeClass('events-grid-cell-selected');
857				if (!selected) {
858					$(this).addClass('events-grid-cell-selected');
859					var dayView = $('<div class="events-grid-day-list"></div>').insertAfter($(this).closest('.events-grid-row'));
860					$.each($(this).data('events'), function (index, val) {
861						var hasPre = false;
862						var isBilled = false;
863						var preText = '';
864						var tagString = '';
865						var ticketIcon = '<span class="fa-stack ticket-symbol" style="font-size:12px"><i class="fa fa-circle fa-stack-2x" style="color: #2bafbe"></i><i class="fa fa-usd fa-stack-1x fa-inverse"></i></span>&nbsp;';
866						var title = val.title;
867
868						if (typeof (val.enable_billing) !== 'undefined') {
869							isBilled = parseInt(val.enable_billing) === 1;
870						}
871
872						if (self.settings.eventsGroupCalenderBAge) {
873							if (val.ages) {
874								$.each(val.agesArray, function (index, val) {
875									var dotColour = typeof self.ages[val] !== 'undefined' ? self.ages[val] : self._genColour(val);
876									tagString += ' <i class="fa fa-circle" style="color:' + dotColour + '"></i> ' + val;
877								});
878							}
879						} else {
880							if (val.tags) {
881								$.each(val.tagsArray, function (index, val) {
882									var dotColour = typeof self.tags[val] !== 'undefined' ? self.tags[val] : self._genColour(val);
883									tagString += ' <i class="fa fa-circle" style="color:' + dotColour + '"></i> ' + val;
884								});
885							}
886						}
887
888						if (parseInt(val.changed) === 1) {
889							title = '<span class="eelist-changed-message">Cancelled</span>&nbsp;' + title;
890						}
891
892						if (parseInt(val.changed) === 2 || parseInt(val.changed) === 3) {
893							title = '<span class="eelist-changed-message">Rescheduled</span>&nbsp;' + title;
894						}
895
896						if (isBilled) {
897							title = ticketIcon + title;
898						}
899
900						if (hasPre) {
901							title = preText + title;
902						}
903
904
905						if (val.link) {
906							var link = $('<a target="' + self.settings.linkTarget + '" href="' + val.link + '"> ' + $('<div></div>').html(title).text() + '</a>');
907						} else {
908							var id = val.new_event_id || val.id;
909							var embeddedEventPath = self._getEmbeddedEventPath(id);
910							var link = $('<a target="' + self.settings.linkTarget + '">' + title + '</a>');
911							if (window.websiteUrl) {
912								link.attr('href', window.websiteUrl + '/event/' + id);
913							} else {
914								link.attr('href', '/event/' + id);
915							}
916							self._bindEmbeddedEventLink(link, embeddedEventPath);
917						}
918						if (val.sub_title) {
919							link.append('<span> - ' + val.sub_title + '</span>');
920						}
921
922
923						link.qtip({
924							content: {
925								text: function (event, api) {
926									return self._getEventBlock(val, true);
927								}
928							},
929							style: {
930								classes: 'qtip-bootstrap events-popup',
931								def: false,
932								width: 500
933							},
934							hide: {
935								fixed: true,
936								delay: 300
937							},
938							position: {
939								viewport: $(window)
940							}
941						});
942						if (parseInt(val.changed) === 1) {
943							$('<div>&nbsp;, ' + val.location + '&nbsp;&nbsp;<span class="events-grid-day-list-tags">' + tagString + '</span></div>').prepend(link).appendTo(dayView);
944						} else if (parseInt(val.changed) === 2 || parseInt(val.changed) === 3) {
945							$('<div>&nbsp;<b>' + val.time_string + '</b>, ' + val.location + '&nbsp;&nbsp;<span class="events-grid-day-list-tags">' + tagString + '</span></div>').prepend(link).appendTo(dayView);
946						} else {
947							$('<div>&nbsp;<b>' + val.time_string + '</b>, ' + val.location + '&nbsp;&nbsp;<span class="events-grid-day-list-tags">' + tagString + '</span></div>').prepend(link).appendTo(dayView);
948						}
949
950					});
951				}
952			}).on('click keyup', '.events-change-day', function (e) {
953				e.preventDefault();
954
955				var isSpace = event.type === 'keyup' && (e.which == 32 || e.keyCode == 32 || e.key == " ");
956				var isEnter = event.type === 'keyup' && (e.which == 13 || e.keyCode == 13 || e.key == "Enter");
957
958				if (e.type === 'click' || isSpace || isEnter) {
959					self.settings.range = 'range';
960					self.gridView.data('date').add($(this).attr('data-by'), 'month');
961					self.settings.gridDate = self.gridView.data('date').format('YYYY-MM-DD');
962					self._update();
963				}
964			});
965
966			self.todayButton.on('click', function (event) {
967				event.preventDefault();
968				self.settings.range = 'today';
969				if (self.settings.view == 'list') {
970					self.rangeUpdating = true;
971					$('div.events-calendar', self.listViewLeft).datepicker('setDate', moment().startOf('day').toDate());
972					self.rangeUpdating = false;
973				} else {
974					self.gridView.data('date', moment().startOf('month'));
975
976				}
977				self._update();
978			});
979			$(window).on('resize', function () {
980				var w = $(document).width();
981				if (w < 768 && self.settings.view == 'grid') {
982					self.setView('list');
983				} else {
984
985				}
986				self._scheduleHeightPost();
987			});
988
989			// Brochure button logic:
990
991		},
992		_doSearch: function (term) {
993			this.paused = true;
994			var now = moment();
995			if (this.settings.filters.days === 0) {
996				var startDate = now.format('MMMM D YYYY');
997				var endDate = now.endOf('month').format('MMMM D YYYY');
998				$('.events-range.input-daterange input[name=start]', this.listViewLeft).datepicker('update', startDate);
999				$('.events-range.input-daterange input[name=end]', this.listViewLeft).datepicker('update', endDate);
1000			}
1001			$('.events-details a[href="#date_range"]', this.listViewLeft).tab('show');
1002			this.paused = false;
1003			this._update();
1004		},
1005
1006		// #4381 - CK
1007		// Hide external venue description on qtip
1008		_getEventBlock: function (event, inQtip) {
1009			var self = this;
1010			var $item = $('<div class="eelistevent-data"></div>');
1011			if (parseInt(event.changed) === 3) {
1012				$item.addClass('eelistevent-postponed');
1013			}
1014			if (parseInt(event.changed) === 2 || parseInt(event.changed) === 3) {
1015				$('<div class="eelist-changed-message"></div>').text('Rescheduled').prependTo($item);
1016			} else if (parseInt(event.changed) === 1) {
1017				$item.addClass('eelistevent-canceled');
1018				$('<div class="eelist-changed-message"></div>').text('Cancelled').prependTo($item);
1019			}
1020			var title = event.title;
1021			var evtLink = '';
1022			var imageLink = ''
1023
1024			if (event.sub_title) title += '<span>' + ' - ' + event.sub_title + '</span>';
1025			if (event.link) {
1026				evtLink = $('<a target="' + self.settings.linkTarget + '" href="' + event.link + '"></a>');
1027			} else {
1028				var id = event.new_event_id || event.id;
1029				evtLink = $('<a target="' + self.settings.linkTarget + '" href="' + (window.websiteUrl || "") + '/event/' + id + '"></a>');
1030				self._bindEmbeddedEventLink(evtLink, self._getEmbeddedEventPath(id));
1031			}
1032
1033			imageLink = evtLink.clone();
1034			evtLink.html(title);
1035			$('<div class="eelisttitle"></div>').append(evtLink).appendTo($item);
1036
1037			// CH 14854 - Add in event images.
1038			//
1039			if (self.settings.showImagesInListGrid) {
1040				if (typeof event.image === 'string' && event.image.length > 0) {
1041					var url = 'https://' + self.settings.client + '.libnet.info/images/events/' + self.settings.client + '/' + event.image;
1042					//var image = '<img src="' + url + '" class="img-thumbnail event-list-image" alt="' + event.title + '"/>';
1043					var imageFrame = $('<div/>');
1044					var image = $('<span/>')
1045						.attr('role', 'img')
1046						.attr('aria-label', event.title)
1047						.css({
1048							'display': 'inline-block',
1049							'width': '118px',
1050							'height': '118px',
1051							'background-image': 'url(\'' + url + '\')',
1052							'background-position': 'center center',
1053							'background-size': 'cover'
1054						});
1055
1056					if (typeof event.image_alt_text === 'string' && event.image_alt_text.length > 0) {
1057						image.attr('aria-label', event.image_alt_text);
1058					}
1059
1060					imageFrame.append(image);
1061					imageLink.append(imageFrame);
1062
1063					$('<div class="eelistimage"></div>').append(imageLink).appendTo($item);
1064				}
1065			}
1066
1067			if (parseInt(event.changed) === 2) {
1068				$item.append('<div class="eelisttime eelisttime-postponed headingtext">(' +
1069					'<span>Rescheduled from </span>' +
1070					event.datestring + ': ' + event.time_string +
1071					')</div>');
1072
1073				var tt = $('<div class="eelisttime headingtext"></div>').append("<span class='eelist-changed-message'> New date </span>").appendTo($item);
1074				tt.append(document.createTextNode(event.changed_datestring + ': ' + event.changed_time_string));
1075			} else if (parseInt(event.changed) === 3) {
1076				$item.append('<div class="eelisttime headingtext">' + event.datestring + ': ' + event.time_string + '</div>');
1077				var tt = $('<div class="eelisttime headingtext"></div>').append("<span class='eelist-changed-message'> New date </span>").appendTo($item);
1078				tt.append(document.createTextNode(event.changed_datestring + ': ' + event.changed_time_string));
1079			} else {
1080				$item.append('<div class="eelisttime headingtext">' + event.datestring + ': ' + event.time_string + '</div>');
1081			}
1082
1083			var lstring = event.library;
1084			if (event.venues) {
1085				lstring += ' - <i>' + event.venues + '</i>';
1086			}
1087			if (parseInt(event.changed_venue) == 1) {
1088				$item.append('<div class="eelocation"><i class="am-locations"/><span class="eelist-changed-message"> New venue </span> ' + lstring + '</div>');
1089			} else {
1090				$item.append('<div class="eelocation"><i class="am-locations"/> ' + lstring + '</div>');
1091			}
1092
1093			if (event.agesArray) {
1094				if (self.settings.eventsGroupCalenderBAge) {
1095					var tagsList = $('<div class="eelisttags"></div>').append('<span>Age group:&nbsp;&nbsp;</span>').appendTo($item);
1096					$.each(event.agesArray, function (index, val) {
1097						var dotColour = typeof self.ages[val] !== 'undefined' ? self.ages[val] : self._genColour(val);
1098						tagsList.append('<span><i class="fa fa-circle" style="color:' + dotColour + '"></i> ' + val + ' </span>');
1099					});
1100				} else {
1101					$('<div class="eelistgroup"></div>').append('<span>Age group:&nbsp;&nbsp;</span>').append(event.agesArray.join(", ")).appendTo($item);
1102				}
1103			}
1104
1105			if (event.tagsArray) {
1106				if (self.settings.eventsGroupCalenderBAge) {
1107					$('<div class="eelistgroup"></div>').append('<span>' + escapeHtml(self.settings.customEventLabel) + ' type:&nbsp;&nbsp;</span>').append(event.tagsArray.join(", ")).appendTo($item);
1108				} else {
1109					var tagsList = $('<div class="eelisttags"></div>').append('<span>' + escapeHtml(self.settings.customEventLabel) + ' type:&nbsp;&nbsp;</span>').appendTo($item);
1110					$.each(event.tagsArray, function (index, val) {
1111						var dotColour = typeof self.tags !== 'undefined' && typeof self.tags[val] !== 'undefined' ? self.tags[val] : self._genColour(val);
1112						tagsList.append('<span><i class="fa fa-circle" style="color:' + dotColour + '"></i> ' + val + ' </span>');
1113					});
1114				}
1115			}
1116
1117			$item.append('<div class="eelistdesc">' + event.description + '</div>');
1118			// #4381 - CK
1119			// Hide external venue description on qtip
1120			if (event.venue_type && event.venue_type == 'external' && event.venue_description.length > 0 && !inQtip) {
1121				$item.append('<div class="eelistvenuedesc"><b>Venue details</b><br>' + event.venue_description + '</div>');
1122			}
1123
1124			if ((parseInt(event.changed, 10) !== 1 && parseInt(event.changed, 10) !== 3) && parseInt(event.allow_reg, 10) === 1) {
1125				if (moment.tz(event.close_registration, self.settings.timezone).diff() < 0) {
1126					if (parseInt(event.third_party_reg, 10) == 0) {
1127						$item.append('<div class="regclosed">Registration is now closed</div>');
1128					}
1129				} else {
1130					// If third-party-reg is off then run the numbers, otherwise jump to next if set.
1131					// CH #11417 - Added maximums for waitlists.
1132					if (event.ticketInfo) {
1133						var available = 0;
1134						if (event.ticketInfo.ticketData) {
1135							for (var t = 0; t < event.ticketInfo.ticketData.length; t++) {
1136								var tkt = event.ticketInfo.ticketData[t];
1137								available += tkt.available;
1138							}
1139						}
1140
1141						if (event.ticketInfo.typeId === -1) {
1142							available = event.ticketInfo.available;
1143						}
1144
1145						if (available <= 0) {
1146							if (self.settings.allowWaitlistSubscription) {
1147								$item.append('<div class="regclosed">This ' + escapeHtml(self.settings.customEventLabel) + ' is full. Click the button below to be notified when a registration becomes available.</div>');
1148								$('<button data-eventid="' + event.id + '" data-recurringid="' + event.recurring_id + '" data-regtype="-1" class="button subscribeToEvent"></button>')
1149									.text(self.settings.customInterestedText)
1150									.on('click', function () {
1151										self._showInterestedPopup($(this).attr('data-eventid'), $(this).attr('data-recurringid'), event);
1152									})
1153									.appendTo($item)
1154							} else {
1155								$item.append('<div class="regclosed">This ' + escapeHtml(self.settings.customEventLabel) + ' is full</div>');
1156							}
1157
1158							//$btn.text('Event Full').prop('disabled', true);
1159							return $item;
1160						}
1161					}
1162
1163					var allowWaitList = parseInt(event.allow_waitlist, 10) !== 0 && ((event.max_waitlist == 0 || (parseInt(event.max_waitlist) > 0) && (parseInt(event.total_waitlist) < parseInt(event.max_waitlist))));
1164					var regAllowed = parseInt(event.total_registrants, 10) < parseInt(event.max_attendee, 10) || allowWaitList;
1165
1166					if (parseInt(event.third_party_reg, 10) === 0) {
1167						if (!regAllowed) {
1168							$item.append('<div class="regclosed">This ' + escapeHtml(self.settings.customEventLabel) + ' is full</div>');
1169							return $item;
1170						}
1171					}
1172
1173					if (event.version == 2 && parseInt(event.third_party_reg, 10) === 1) {
1174						if (event.reg_url.length > 0) {
1175							$('<a target="' + self.settings.linkTarget + '" href="' + event.reg_url + '" class="button eventRegButton">Register</a>').appendTo($item);
1176						}
1177					} else if (parseInt(event.staff_only_registration, 10) === 1) {
1178						$('<div class="regclosed eventStaffRegMessage">Please contact the library to register for this event</div>').appendTo($item);
1179					} else {
1180						var event_id = event.id;
1181						var location_id = event.location_id;
1182						var $btn, $onlineBtn;
1183
1184						if (parseInt(event.total_registrants, 10) >= parseInt(event.max_attendee, 10)) {
1185							var m = 'Join the wait list';
1186							var regstatus = '0';
1187							var t = '2';
1188						} else {
1189							var m = 'Register';
1190							var regstatus = '1';
1191							var t = '1';
1192						}
1193
1194						if (parseInt(event.whole_serires_only, 10) === 1 && moment(event.series_starts).subtract(event.registration_close, 'minutes').tz(self.settings.timezone).diff() < 0) {
1195							if (parseInt(event.third_party_reg, 10) == 0) {
1196								$item.append('<div class="regclosed">Registration is now closed</div>');
1197							}
1198						} else if (parseInt(event.whole_serires_only, 10) === 0 || (parseInt(event.whole_serires_only, 10) === 1)) {
1199							$btn = null, $onlineBtn = null;
1200							if (event.event_type !== undefined && event.event_type !== '') {
1201								if (event.event_type == 'INPERSON' && event.webinar_seats && event.venue_type == 'external') {
1202									event.event_type = 'HYBRID';
1203									event.max_attendee_virtual = event.webinar_seats;
1204								}
1205
1206								switch (event.event_type) {
1207									case 'INPERSON':
1208										var ariaLabel = m + " for " + event.title + " in person" + " on " + event.datestring + " at " + event.start_time;
1209										$btn = $('<button aria-label="' + ariaLabel + '" data-eventid="' + event.id + '" data-recurringid="' + event.recurring_id + '" data-regtype="' + t + '" data-attendancetype="INPERSON" data-testmode="' + self.settings.testMode + '" class="button eventRegButton"></button>').text(m).appendTo($item).regButton();
1210										break;
1211									case 'ONLINE':
1212										var ariaLabel = m + " for " + event.title + " on line" + " on " + event.datestring + " at " + event.start_time;
1213										$btn = $('<button aria-label="' + ariaLabel + '" data-eventid="' + event.id + '" data-recurringid="' + event.recurring_id + '" data-regtype="' + t + '" data-attendancetype="ONLINE" data-testmode="' + self.settings.testMode + '" class="button eventRegButton"></button>').text(m + ' to attend online').appendTo($item).regButton();
1214										break;
1215									case 'HYBRID':
1216										if (parseInt(event.allow_waitlist, 10) == 1 && parseInt(event.total_inperson_registrants, 10) >= parseInt(event.max_attendee_in_branch, 10)) {
1217											var m_in_branch = 'Join the wait list';
1218											var t_in_branch = '2';
1219										} else if (parseInt(event.max_attendee_in_branch, 10) > 0) {
1220											var m_in_branch = 'Register';
1221											var t_in_branch = '1';
1222										}
1223
1224										if (parseInt(event.allow_waitlist, 10) == 1 && parseInt(event.total_online_registrants, 10) >= parseInt(event.max_attendee_virtual, 10)) {
1225											var m_online = 'Join the wait list';
1226											var t_online = '2';
1227										} else if (parseInt(event.max_attendee_virtual, 10) > 0) {
1228											var m_online = 'Register';
1229											var t_online = '1';
1230										}
1231
1232										if (parseInt(event.allow_waitlist, 10) == 1 || parseInt(event.total_inperson_registrants, 10) < parseInt(event.max_attendee_in_branch, 10)) {
1233											var ariaLabel = m_in_branch + " for " + event.title + " to attend in person" + " on " + event.datestring + " at " + event.start_time;
1234											if (parseInt(event.max_attendee_in_branch, 10) > 0) {
1235												$btn = $('<button aria-label="' + ariaLabel + '" data-eventid="' + event.id + '" data-recurringid="' + event.recurring_id + '" data-regtype="' + t_in_branch + '" data-attendancetype="INPERSON" data-testmode="' + self.settings.testMode + '" class="button eventRegButton"></button>').text(m_in_branch + ' to attend in person').appendTo($item).regButton();
1236											}
1237										}
1238
1239										if (parseInt(event.allow_waitlist, 10) == 1 || parseInt(event.total_online_registrants, 10) < parseInt(event.max_attendee_virtual, 10)) {
1240											var ariaLabelOnline = m_online + " for " + event.title + " to attend online" + " on " + event.datestring + " at " + event.start_time;
1241											if (parseInt(event.max_attendee_virtual, 10) > 0) {
1242												$onlineBtn = $('<button aria-label="' + ariaLabelOnline + '" data-eventid="' + event.i
1242d + '" data-recurringid="' + event.recurring_id + '" data-regtype="' + t_online + '" data-attendancetype="ONLINE" data-testmode="' + self.settings.testMode + '" class="button eventRegButton"></button>').text(m_online + ' to attend online').appendTo($item).regButton();
1243											}
1244										}
1245
1246										break;
1247								}
1248							} else {
1249								var ariaLabelOther = m + " for " + event.title + " on " + event.datestring + " at " + event.start_time;
1250								$btn = $('<button aria-label="' + ariaLabelOther + '" data-eventid="' + event.id + '" data-recurringid="' + event.recurring_id + '" data-regtype="' + t + '" data-attendancetype="UNKNOWN" data-testmode="' + self.settings.testMode + '" class="button eventRegButton"></button>').text(m).appendTo($item).regButton();
1251							}
1252
1253							// Time juggling for the reg button CH10228.
1254							// Due to some oddities in the way moment works and calculates differences
1255							// No TZ conversion here since time is saved as a TZ time to the database. It also screws with comparisons.
1256							// E.g. IF we have 2019-02-01 12:00:00 stored in the database as a CST time, converting to a moment with tz included will produce something like this:
1257							// E.g(2). 2019-02-01T11:00:00-6:00 as tz calculation takes into account a tz-less time. Confusing. I know.
1258							var regOpens = moment(event.reg_opens).format('YYYY-MM-DD HH:mm:ss');
1259							// Add the timezone here to account for local computer time
1260							// This is because if someone has EST set on their local computer, we want it to compare on that time so that someone can't abuse the system and just change the clock to get around it.
1261							var nowTime = moment().tz(self.settings.timezone).format('YYYY-MM-DD HH:mm:ss');
1262							// Formatted times can now be compared, because the TZ string has been stripped. So basically we are comparing two different times.
1263							// E.g. 2019-02-01 1:30:00 (now) >= 2019-02-01 12:00:00 (opens) would be true.
1264							var regOpened = nowTime >= regOpens;
1265
1266							if (parseInt(event.open_registration_imidiatly, 10) === 0 && !regOpened) {
1267								if ($btn) {
1268									$btn.prop('disabled', true);
1269								}
1270
1271								if ($onlineBtn) {
1272									$onlineBtn.prop('disabled', true);
1273								}
1274
1275								$('<div class="events-reg-open-message"></div>').text("Registration opens on " + moment(event.reg_opens).format('dddd') + ", " + moment(event.reg_opens).format('MMMM D YYYY') + ' at ' + moment(event.reg_opens).format('h:mma')).appendTo($item);
1276							}
1277						}
1278
1279
1280					}
1281				}
1282
1283				if (typeof event.enable_billing !== 'undefined' && parseInt(event.enable_billing) === 1) {
1284					// Then there is class/ticketdata info
1285					$('.ticket-group', $item).remove();
1286					var coEventBillingInfo = new EventBillingInfo(event.recurring_id, 1, event.ticketInfo);
1287					var infoBox = $('<div class="eelistgroup ticket-group"></div>');
1288					infoBox.append(coEventBillingInfo.info).appendTo($item);
1289				}
1290			}
1291			return $item;
1292		},
1293		_getFeed: function (type) {
1294			var self = this;
1295			var feedData = {
1296				feedType: type,
1297				filters: {
1298					location: [],
1299					ages: [],
1300					types: [],
1301					tags: [],
1302					term: '',
1303					days: 1
1304				}
1305			};
1306
1307			if (self.settings.filters) {
1308				var filters = self.settings.filters;
1309				feedData.filters.ages = filters.ages.length > 0 ? filters.ages : ['all'];
1310				feedData.filters.types = filters.types.length > 0 ? filters.types : ['all'];
1311				feedData.filters.location = self.settings.locations.length > 0 ? self.settings.locations : ['all'];
1312			}
1313			if($('.events-search-field', self.element).val() && $('.events-search-field', self.element).val().length > 0) {
1314				feedData.filters.term = $('.events-search-field', self.element).val();
1315			} else if (self.settings.term && self.settings.term.length > 0) {
1316				feedData.filters.term = self.settings.term;
1317			}
1318
1319			var urlFilters = JSON.stringify(feedData);
1320			var url = window.location.protocol + "//" + window.location.hostname + '/feeds?data=' + btoa(urlFilters);
1321			this._showFeedPopup(url, feedData);
1322			//window.open('/feeds?data='+ urlFilters , '_blank');
1323		},
1324		_showFeedPopup: function (url, filterData) {
1325			var self = this;
1326			var term = filterData.filters.term;
1327			var filterString = 'Showing ';
1328
1329			if (term && term.length > 2) {
1330				filterString += 'search results for <b>' + term + '</b> for ';
1331			}
1332
1333			if (filterData.filters.types.length > 0) {
1334				filterString += filterData.filters.types.join(', ') + ' events';
1335			} else {
1336				filterString += 'all ' + self.settings.customEventLabel + 'types';
1337			}
1338			filterString += ' at ';
1339			if (self.settings.filters.locations.length > 0) {
1340				if (self.settings.filters.locations.length > 1) {
1341					filterString += 'these locations ' + self.settings.filters.locations.join(', ');
1342				} else {
1343					if (self.settings.filters.locations[0] !== 'all') {
1344						filterString += self.settings.filters.locations[0];
1345					} else {
1346						filterString += 'all locations';
1347					}
1348				}
1349			} else {
1350				filterString += 'all locations';
1351			}
1352
1353			filterString += ' for ';
1354			if (filterData.filters.ages.length > 0) {
1355				if (filterData.filters.ages.length > 1) {
1356					filterString += 'the age groups ' + filterData.filters.ages.join(', ');
1357				} else {
1358					filterString += 'the age group ' + filterData.filters.ages[0];
1359				}
1360			} else {
1361				filterString += 'all ages';
1362			}
1363
1364
1365			if (filterData) {
1366				var icon = "fa fa-rss";
1367				var style = "color:#f26522";
1368				var title = "RSS";
1369
1370				if (filterData.feedType === 'ical') {
1371					icon = "fa fa-calendar";
1372					style = "color:#3981c6";
1373					title = "iCal";
1374				}
1375
1376				var html = '<div id="brochure-popup">' +
1377					'	<div class="row">' +
1378					'		<div class="col-md-12">' +
1379					'			<div style="float:right;"><a class="close-brochure-popup" style="cursor: pointer;">close</a></div>' +
1380					'			<h3 class="patron-popup heading-text"><i class="' + icon + '" style="' + style + '"></i>&nbsp;&nbsp;' + title + '&nbsp;Events Feed</h3>' +
1381					'		</div>' +
1382					'	</div>' +
1383					'	<div class="patron-popup top-row row">' +
1384					'		<div class="col-md-12">' +
1385					'			<strong>Feed will contain up to 500 events with filters selected, excluding dates.</strong>' +
1386					'		</div>' +
1387					'		<div class="col-md-12">' +
1388					'			Filtered By: ' + filterString +
1389					'		</div>' +
1390					'	</div>' +
1391					'	<div class="patron-popup row" style="margin-left:15px;margin-right:15px;padding-top:35px;padding-bottom:20px;">' +
1392					'		<div class="col-md-12">' +
1393					'			<input type="text" id="feed-link" class="form-control" aria-label="Feed URL" value="' + url + '"/>' +
1394					'		</div>' +
1395					'	</div>' +
1396					'	<div class="patron-popup button-row" style="margin-left:15px;">' +
1397					'		<div class="col-md-3 col-md-offset-6" style="padding-top: 10px;">' +
1398					'			<button class="copy-button brochure-button btn-block">Copy Link to Clipboard</button>' +
1399					'		</div>' +
1400					'		<div class="col-md-3" style="padding-top: 10px;">' +
1401					'			<button class="view-button brochure-button btn-block">Download File</button>' +
1402					'		</div>' +
1403					'	</div>' +
1404
1405					'</div>';
1406
1407				var popup = $('<div class="events2-brochure"></div>
1407').amPopup({
1408					title: '',
1409					popUpClass: '',
1410					showTitle: false
1411				});
1412				popup.append(html);
1413				popup.on('click', '.close-brochure-popup', function () {
1414					popup.data('plugin_amPopup').close();
1415				})
1416					.on('click', '.copy-button', function () {
1417						var el = document.getElementById("feed-link");
1418						el.select();
1419						document.execCommand('copy');
1420					}).on('click', '.view-button', function () {
1421						window.open(url, '_blank');
1422					});
1423				popup.data('plugin_amPopup').open();
1424			}
1425		},
1426		_showBrochurePopup: function () {
1427			var filters = {};
1428			var options = {};
1429			var self = this;
1430
1431			if (self.settings.filters) {
1432				filters = self.settings.filters;
1433			}
1434
1435			if (self.settings.filterOptions) {
1436				options = self.settings.filterOptions;
1437			}
1438
1439			var subData = {
1440				filters: filters,
1441				options: options,
1442				allowSubs: self.settings.allowSubs,
1443				customEventsLabel: self.settings.customEventsLabel,
1444				customEventLabel: self.settings.customEventLabel
1445			};
1446
1447			var ps = new patronSubscriptions(subData);
1448			ps.showPopup();
1449		},
1450		// CH14299
1451		_showInterestedPopup: function (dateId, recurringId, eventInfo) {
1452			var self = this;
1453			var eventInfo = {
1454				title: eventInfo.title + ' - ' + eventInfo.sub_title,
1455				datestring: eventInfo.datestring + ' - ' + eventInfo.time_string,
1456				location: eventInfo.location
1457			};
1458
1459			var interested = new EventBillingSubscriptions(dateId, recurringId, eventInfo, true);
1460			var interestedForm = interested.subscriptionForm;
1461			interestedForm.appendTo('body');
1462
1463			$('.Event_Full-dialog').modal({
1464				backdrop: 'static',
1465				keyboard: false
1466			}).modal('show');
1467
1468		},
1469		_update: function () {
1470			var self = this;
1471			if (self.paused) return;
1472			if (!self.initComplete) return;
1473			var options = {};
1474			var filter = {};
1475			options.private = self.settings.private;
1476
1477			var term = $('.events-search-field', self.element).val();
1478			if (term && term.length > 2) {
1479				options.search = term;
1480			}
1481
1482			if (self.settings.view == 'list') {
1483				var end = moment($('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('getDate'));
1484				var start = moment($('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('getDate'));
1485				var days = end.diff(start, 'days');
1486
1487				if (self.settings.filters.date === start.format('YYYY-MM-DD')) {
1488					options.date = self.settings.filters.date;
1489				} else {
1490					options.date = start.format('YYYY-MM-DD');
1491				}
1492
1493				if (self.settings.filters.days === days) {
1494					options.days = self.settings.filters.days;
1495				} else {
1496					options.days = end.diff(start, 'days');
1497				}
1498
1499			} else {
1500				var date = self.gridView.data('date').clone();
1501				// options.date=date.isoWeekday(1).format('YYYY-MM-DD');
1502				options.date = date.startOf('week').format('YYYY-MM-DD');
1503				options.days = 42;
1504			}
1505
1506			//if (options.days === 0) options.days = 1;
1507
1508			$(".events-brochure, .brochure-grid, .events-rss, .events-ical").remove();
1509			var navigation = $('.events-filter-row').first();
1510			var brochureButton = self.settings.showBrochure ? '<div class="events-brochure"><button class="events-option-dropdown events-brochure-button"><i class="fa fa-file-pdf-o" style="color: #ED0202;"></i>&nbsp;Create Brochure</button></div>' : '';
1511			var rssButton = self.settings.showRSS ? '<div class="events-rss"><button class="events-option-dropdown events-rss-button"><i class="fa fa-rss" style="color: #f26522;"></i>&nbsp;RSS</button></div>' : '';
1512			var icalButton = self.settings.showICAL ? '<div class="events-ical"><button class="events-option-dropdown events-ical-button"><i class="fa fa-calendar" style="color: #3981c6;"></i>&nbsp;Add to Calendar</button></div>' : '';
1513			var left = $('.events-left');
1514
1515			if (self.settings.view === "grid") {
1516				navigation.append($('<div class="col-md-12 brochure-grid" style="margin-top:10px;">' + brochureButton + '</div>'));
1517			} else {
1518				left.append(brochureButton);
1519				left.append(rssButton);
1520				left.append(icalButton);
1521			}
1522
1523			$('.events-brochure-button').on('click', function () {
1524				//self._getBrochure();
1525				if (self.settings.brochureNoPopup) {
1526					window.location.href = "/brochure";
1527				} else {
1528					self._showBrochurePopup();
1529				}
1530			});
1531
1532			$('.events-rss-button').on('click', function () {
1533				//self._getBrochure();
1534				self._getFeed('rss');
1535			});
1536
1537			$('.events-ical-button').on('click', function () {
1538				//self._getBrochure();
1539				self._getFeed('ical');
1540			});
1541
1542			$('.events-day-title', self.listViewRight).toggle(options.days == 0);
1543			$('.events-range-title', self.listViewRight).toggle(options.days >= 1);
1544
1545			if (!$('#loc_all', self.listViewLeft).is(':checked')) {
1546				options.locations = [];
1547				filter.locations = [];
1548				$('[id^=loc_]', self.locationList).each(function () {
1549					if ($(this).is(':checked')) {
1550						options.locations.push($(this).attr('id').substr(4));
1551						filter.locations.push($.trim($(this).parent().text()));
1552					}
1553				});
1554			}
1555			if (!$('#group_all', self.listViewLeft).is(':checked')) {
1556				options.ages = [];
1557				filter.ages = [];
1558				$('[id^=group_]', self.ageList).each(function () {
1559					if ($(this).is(':checked')) {
1560						options.ages.push(encodeURIComponent($(this).attr('value')));
1561						filter.ages.push($.trim($(this).parent().text()));
1562					}
1563				});
1564			}
1565			if (!$('#type_all', self.listViewLeft).is(':checked')) {
1566				options.types = [];
1567				filter.types = [];
1568				$('[id^=type_]', self.typeList).each(function () {
1569					if ($(this).is(':checked')) {
1570						options.types.push(encodeURIComponent($(this).attr('value')));
1571						filter.types.push($.trim($(this).parent().text()));
1572					}
1573				});
1574			}
1575			if ((term && term.length > 2) || filter.locations.length > 0 || filter.ages.length > 0 || filter.types.length > 0) {
1576				var filterString = 'Showing ';
1577				if (term && term.length > 2) {
1578					filterString += 'search results for <b>' + term + '</b> for ';
1579				}
1580				if (filter.types.length > 0) {
1581					filterString += filter.types.join(', ') + ' events';
1582				} else {
1583					filterString += 'all ' + self.settings.customEventLabel + ' types';
1584				}
1585				filterString += ' at ';
1586				if (filter.locations.length > 0) {
1587					filterString += filter.locations.join(', ');
1588				} else {
1589					filterString += 'all locations';
1590				}
1591				filterString += ' for ';
1592				if (filter.ages.length > 0) {
1593					if (filter.ages.length > 1) {
1594						filterString += 'the age groups ' + filter.ages.join(', ');
1595					} else {
1596						filterString += 'the age group ' + filter.ages[0];
1597					}
1598				} else {
1599					filterString += 'all ages';
1600				}
1601				$('.events-filter-message', self.views).html(filterString);
1602				$('.events-filter-details', self.views).fadeIn();
1603			} else {
1604				$('.events-filter-details', self.views).hide();
1605			}
1606			$('.events-date-range-string', self.listViewRight).text('');
1607
1608			var $list = $('.events-body', self.listViewRight).empty().text('Loading events...');
1609
1610			var $grid = $('.events-grid-body', self.gridView).empty().text('Loading events...');
1611
1612			// Fix for bad dates.
1613			options.days++;
1614
1615			// Request for brochure on front-end, need to save off the filters - which are all the options used to get event data
1616			var filterOptions = {
1617				ages: filter.ages,
1618				locations: filter.locations,
1619				types: filter.types,
1620				days: options.days,
1621				date: options.date,
1622				private: options.private,
1623				term: options.search
1624			};
1625
1626			self.settings.locations = options.locations;
1627			self.settings.filters = filterOptions;
1628			if (typeof (filterString) !== "undefined") {
1629				self.settings.filters.display = filterString.substr(7);
1630			} else {
1631				self.settings.filters.display = "All events for date range, no filters.";
1632			}
1633
1634			if (typeof (options.display) !== "undefined") {
1635				delete options.display;
1636			}
1637
1638			self.settings.filterOptions = options;
1639			self._setURLPieces();
1640			self._updateQuickRangeLinks();
1641
1642			var website = (window.websiteUrl || "");
1643			$.getJSON(website + '/eeventcaldata?event_type=' + self.settings.type + '&req=' + encodeURIComponent(JSON.stringify(options)), function (data) {
1644				if (self.settings.view == 'list') {
1645					$('.events-date-string', self.listViewRight).text(start.format('MMMM D YYYY'));
1646					$('.events-date-range-string', self.listViewRight).text(start.format('MMMM D YYYY') + ' - ' + end.format('MMMM D YYYY'));
1647					$list.empty();
1648					$.each(data, function (index, val) {
1649
1650						var cancelled = parseInt(val.changed);
1651						if ((cancelled !== 1 || isNaN(cancelled)) || self.settings.eventsCalendarShowCancelledEvents) {
1652							$('<div class="eelistevent"></div>').append(self._getEventBlock(val, false)).appendTo($list);
1653						}
1654					});
1655					if ($list.children().length === 0) $list.html('<div class="eelisttitle" style="margin-left:20px">Sorry there are no events that match your selection.</div>');
1656				} else if (self.settings.view == 'grid') {
1657					var sortedData = {};
1658					$.each(data, function (index, val) {
1659						var d = moment(val.raw_start_time).format('YYYY-M-D');
1660						if (!sortedData[d]) sortedData[d] = [];
1661
1662						var cancelled = parseInt(val.changed);
1663						if ((cancelled !== 1 || isNaN(cancelled)) || self.settings.eventsCalendarShowCancelledEvents) {
1664							sortedData[d].push(val);
1665						}
1666					});
1667					$grid.empty();
1668					var month = self.gridView.data('date').format('M');
1669					$('.events-date-string', self.gridView).text(self.gridView.data('date').format('MMMM YYYY'));
1670
1671					var a = moment(options.date);
1672					var b = a.clone().add(42, 'days');
1673					var row = false;
1674					for (var m = a; m.isBefore(b); m.add(1, 'days')) {
1675						if (m.format('e') == 0) {
1676							row = $('<div class="events-grid-row"></div>').appendTo($grid);
1677						}
1678						var cell = $('<div class="events-grid-cell noselect"></div>').appendTo(row);
1679
1680						$('<span class="events-grid-cell-date fa-stack fa-lg"><i class="fa fa-circle fa-stack-2x"></i><i class="events-grid-cell-date-number fa fa-stack-1x">' + m.format('D') + '</i></span>').appendTo(cell);
1681						var cellContent = $('<div class="events-grid-cell-content"></div>').appendTo(cell);
1682						var cellTypes = $('<div class="events-grid-cell-types"></div>').appendTo(cell);
1683
1684						if (m.format('M') !== month) cell.addClass('events-grid-cell-fade');
1685						if (m.isSame(moment().startOf('day'))) cell.addClass('events-grid-cell-today');
1686						cell.data('events', sortedData[m.format('YYYY-M-D')] || []);
1687
1688						if (cell.data('events').length > 0) {
1689							var dayData = cell.data('events');
1690							var evts = $('<div class="events-grid-cell-event-holder"></div>').appendTo(cell);
1691							if (dayData.length <= self.settings.inCellEvents) {
1692								$.each(dayData, function (index, val) {
1693									if (self.settings.eventsGroupCalenderBAge) {
1694										var age = val.agesArray[0];
1695										if (parseInt(val.changed, 10) === 2) {
1696											var start = (val.changed_start_time == '12:00am' && val.changed_end_time == '11:59pm') ? 'All day' : val.changed_start_time;
1697										} else {
1698											var start = (val.start_time == '12:00am' && val.end_time == '11:59pm') ? 'All day' : val.start_time;
1699										}
1700										var dotColour = typeof self.ages[age] !== 'undefined' ? self.ages[age] : self._genColour(age);
1701									} else {
1702										var tag = val.tagsArray[0];
1703										if (parseInt(val.changed, 10) === 2) {
1704											var start = (val.changed_start_time == '12:00am' && val.changed_end_time == '11:59pm') ? 'All day' : val.changed_start_time;
1705										} else {
1706											var start = (val.start_time == '12:00am' && val.end_time == '11:59pm') ? 'All day' : val.start_time;
1707										}
1708										var dotColour = typeof self.tags[tag] !== 'undefined' ? self.tags[tag] : self._genColour(tag);
1709									}
1710									var evcell = $('<div class="events-grid-cell-event"><i class="fa fa-circle" style="color:' + dotColour + '"></i> <b>' + start + '</b> ' + val.title + '</div>').data('event', val).appendTo(evts);
1711									evcell.qtip({
1712										content: {
1713											text: function (event, api) {
1714												return self._getEventBlock(val, true);
1715											}
1716										},
1717										style: {
1718											classes: 'qtip-bootstrap events-popup',
1719											def: false,
1720											width: 500
1721										},
1722										hide: {
1723											fixed: true,
1724											delay: 300
1725										},
1726										position: {
1727											viewport: $(window)
1728										}
1729									});
1730
1731								});
1732							} else {
1733								var locs = [];
1734								var tags = {};
1735								$.each(dayData, function (index, val) {
1736									if (parseInt(val.changed, 10) == 1) return;
1736 // Do not show cancelled events in total
1737									locs.push(val.location);
1738									if (self.settings.eventsGroupCalenderBAge) {
1739										if (val.ages) {
1740											$.each(val.agesArray, function (index, val) {
1741												if (!tags[val]) {
1742													tags[val] = 1;
1743												} else {
1744													tags[val]++;
1745												}
1746											});
1747										}
1748									} else {
1749										if (val.tags) {
1750											$.each(val.tagsArray, function (index, val) {
1751												if (!tags[val]) {
1752													tags[val] = 1;
1753												} else {
1754													tags[val]++;
1755												}
1756											});
1757										}
1758									}
1759								});
1760								locs = locs.filter(function (item, i, ar) {
1761									return ar.indexOf(item) === i;
1762								});
1763
1764								var i = 0;
1765								$.each(tags, function (type, total) {
1766									i++;
1767									if (i <= 14) {
1768										if (self.settings.eventsGroupCalenderBAge) {
1769											var textColour = self._getContrastYIQ(self.ages[type] || self._genColour(type));
1770											$('<span title="' + type + '" class="events-grid-cell-type fa-stack fa-lg"><i class="fa fa-circle fa-stack-2x"></i><i style="color:' + textColour + '" class="events-grid-cell-type-number fa fa-stack-1x">' + total + '</i></span>').css('color', self.ages[type] || self._genColour(type)).appendTo(cellTypes);
1771										} else {
1772											var textColour = self._getContrastYIQ(self.tags[type] || self._genColour(type));
1773											$('<span title="' + type + '" class="events-grid-cell-type fa-stack fa-lg"><i class="fa fa-circle fa-stack-2x"></i><i style="color:' + textColour + '" class="events-grid-cell-type-number fa fa-stack-1x">' + total + '</i></span>').css('color', self.tags[type] || self._genColour(type)).appendTo(cellTypes);
1774										}
1775									}
1776								});
1777								if (i > 14) {
1778									$('<div class="events-grid-cell-more">+ ' + (i - 14) + ' more ' + escapeHtml(self.settings.customEventLabel) + ' types</div>').appendTo(cellTypes);
1779								}
1780								var summary = dayData.length + ' ' + escapeHtml(self.settings.customEventsLabel) + ' at<br>';
1781								summary += locs.length > 1 ? locs.length + ' locations' : ' 1 location';
1782								cellContent.prepend(summary);
1783							}
1784						}
1785
1786					}
1787
1788				}
1789				self._scheduleHeightPost();
1790			});
1791		},
1792		_eraseCookie: function (name) {
1793			createCookie(name, "", -1);
1794		},
1795		_createCookie: function (name, value, days) {
1796			var expires = "";
1797			if (days) {
1798				var date = new Date();
1799				date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
1800				expires = "; expires=" + date.toGMTString();
1801			}
1802			document.cookie = name + "=" + value + expires + "; path=/";
1803		},
1804		_readCookie: function (name) {
1805			var nameEQ = name + "=";
1806			var ca = document.cookie.split(';');
1807			for (var i = 0; i < ca.length; i++) {
1808				var c = ca[i];
1809				while (c.charAt(0) == ' ') c = c.substring(1, c.length);
1810				if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
1811			}
1812			return null;
1813		},
1814		setView: function (view) {
1815			var self = this;
1816			view = (view === 'list' || view === 'grid') ? view : 'list';
1817			self._createCookie('eventsView', view, 365);
1818			$('.linkcolour', self.viewPicker).removeClass('linkcolour');
1819			$('[data-view="' + view + '"]', self.viewPicker).addClass('linkcolour');
1820			$('[data-view]', self.views).hide();
1821			$('[data-view="' + view + '"]', self.views).show();
1822
1823			if (self.settings.view == view) return;
1824			self.settings.view = view;
1825			self._update();
1826		},
1827		reload: function () {
1828			this._update();
1829		},
1830		_getContrastYIQ: function (hexcolor) {
1831			var r = parseInt(hexcolor.substr(1, 2), 16);
1832			var g = parseInt(hexcolor.substr(3, 2), 16);
1833			var b = parseInt(hexcolor.substr(5, 2), 16);
1834			var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
1835			return (yiq >= 128) ? 'black' : 'white';
1836		},
1837		_genColour: function (str) {
1838			for (var i = 0, hash = 0; i < str.length; hash = str.charCodeAt(i++) + ((hash << 5) - hash));
1839			for (var i = 0, colour = "#"; i < 3; colour += ("00" + ((hash >> i++ * 8) & 0xFF).toString(16)).slice(-2));
1840			return colour;
1841		},
1842		_genColumn: function (item, idx, targetEle, last) {
1843			var self = this;
1844			if (self.settings.filterCurrentColumn === null) {
1845				self.settings.filterCurrentColumn = $('<div style="float:left;width:250px;" />');
1846			}
1847
1848			var limit = (typeof this.settings.filterMaxItems !== "undefined") ? this.settings.filterMaxItems : 15;
1849			if (idx % limit === 0) {
1850				self.settings.filterCurrentColumn.appendTo(targetEle);
1851				if (idx > 0) {
1852					self.settings.filterCurrentColumn = $('<div style="float:left;width:250px;" />');
1853					self.settings.filterCurrentColumnCount++;
1854				}
1855			}
1856
1857			item.appendTo(self.settings.filterCurrentColumn);
1858
1859			if (last) {
1860				var colsMax = 250;
1861				self.settings.filterCurrentColumn.appendTo(targetEle);
1862				colsMax = (self.settings.filterCurrentColumnCount + 1) * 250;
1863				targetEle.css('max-width', colsMax.toString() + "px !important");
1864				self.settings.filterCurrentColumn = null;
1865				self.settings.filterCurrentColumnCount = 0;
1866			}
1867		},
1868		_loadFilters: function () {
1869			var self = this;
1870			$.getJSON(this.settings.apiServer + '/v1/' + self.settings.client + '/locations', function (json, textStatus) {
1871				self.locations = json;
1872
1873				$.each(json, function (index, val) {
1874					var lcName = val.name.toLowerCase().replace(/,/g, '');
1875					var checked = self.settings.locations.indexOf(lcName) >= 0 ? 'checked="checked"' : '';
1876					if (self.settings.excludedLocations.length > 0) {
1877						checked = self.settings.excludedLocations.indexOf(lcName) >= 0 ? '' : 'checked="checked"';
1878					}
1879					var item = $('<label for="loc_' + val.id + '"><input value="' + val.id + '" id="loc_' + val.id + '" type="checkbox" ' + checked + ' aria-label="Location Filter: ' + val.name + '"> ' + val.name + '</label>');
1880					self._genColumn(item, index, self.locationList, index === (json.length - 1));
1881				});
1882
1883				self.locationsLoaded = true;
1884				if (self.agesLoaded && self.locationsLoaded && self.tagsLoaded) self._update();
1885			}).error(function (jqXHR, textStatus, errorThrown) {
1886				console.log(errorThrown);
1887			});
1888
1889			$.getJSON(this.settings.apiServer + '/v1/' + self.settings.client + '/eventstags', function (json, textStatus) {
1890				if (self.settings.eventsGroupCalenderBAge) {
1891					self.tags = json;
1892
1893					$.each(json, function (index, val) {
1894						var lcName = val.name.toLowerCase();
1895						var checked = self.settings.types.indexOf(lcName) >= 0 ? 'checked="checked"' : '';
1896						if (self.settings.excludedTypes.length > 0) {
1897							checked = self.settings.excludedTypes.indexOf(lcName) >= 0 ? '' : 'checked="checked"';
1898						}
1899						var item = $('<label for="type_' + index + '"><input value="' + val.name + '" id="type_' + index + '" type="checkbox" ' + checked + ' aria-label="Age Filter: ' + val.name + '"> ' + val.name + '</label>');
1900						self._genColumn(item, index, self.typeList, index === (json.length - 1));
1901					});
1902				} else {
1903					self.tags = {};
1904
1905					$.each(json, function (index, val) {
1906						if (!val.colour) {
1907							val.colour = self._genColour(val.name);
1908						}
1909
1910						self.tags[val.name] = val.colour;
1911						var lcName = val.name.toLowerCase();
1912						var checked = '';
1913						if (self.settings.types.length > 0) {
1914							checked = self.settings.types.indexOf(lcName) >= 0 ? 'checked="checked"' : '';
1915						} else if (self.settings.excludedTypes.length > 0) {
1916							checked = self.settings.excludedTypes.indexOf(lcName) >= 0 ? '' : 'checked="checked"';
1917						} else {
1918							//checked = 'checked="checked"';
1919						}
1920						var item = $('<label for="type_' + index + '"><input value="' + val.name + '" id="type_' + index + '" type="checkbox" ' + checked + ' aria-label="Type Filter: ' + val.name + '"> <i class="fa fa-circle" style="color:' + val.colour + '"></i> ' + val.name + '</label>');
1921						self._genColumn(item, index, self.typeList, index === (json.length - 1));
1922					});
1923				}
1924
1925				self.tagsLoaded = true;
1926				if (self.agesLoaded && self.locationsLoaded && self.tagsLoaded) self._update();
1927			});
1928			$.getJSON(this.settings.apiServer + '/v1/' + self.settings.client + '/eventsages', function (json, textStatus) {
1929				if (self.settings.eventsGroupCalenderBAge) {
1930					self.ages = {};
1931
1932					$.each(json, function (index, val) {
1933						if (!val.colour) {
1934							val.colour = self._genColour(val.name);
1935						}
1936
1937						self.ages[val.name] = val.colour;
1938						var lcName = val.name.toLowerCase();
1939						var checked = self.settings.ages.indexOf(lcName) >= 0 ? 'checked="checked"' : '';
1940						if (self.settings.excludedAges.length > 0) {
1941							checked = self.settings.excludedAges.indexOf(lcName) >= 0 ? '' : 'checked="checked"';
1942						}
1943						var item = $('<label for="group_' + index + '"><input value="' + val.name + '" id="group_' + index + '" type="checkbox" ' + checked + '  aria-label="Ages Filter: ' + val.name + '"> <i class="fa fa-circle" style="color:' + val.colour + '"></i> ' + val.name + '</label>');
1944						self._genColumn(item, index, self.ageList, index === (json.length - 1));
1945					});
1946				} else {
1947					self.ages = json;
1948
1949					$.each(json, function (index, val) {
1950						var lcName = val.name.toLowerCase();
1951						var checked = self.settings.ages.indexOf(lcName) >= 0 ? 'checked="checked"' : '';
1952						if (self.settings.excludedAges.length > 0) {
1953							checked = self.settings.excludedAges.indexOf(lcName) >= 0 ? '' : 'checked="checked"';
1954						}
1955						var item = $('<label for="group_' + index + '"><input value="' + val.name + '" id="group_' + index + '" type="checkbox" ' + checked + ' aria-label="Ages Filter: ' + val.name + '"> ' + val.name + '</label>');
1956						self._genColumn(item, index, self.ageList, index === (json.length - 1));
1957					});
1958				}
1959				self.agesLoaded = true;
1960				if (self.agesLoaded && self.locationsLoaded && self.tagsLoaded) self._update();
1961			});
1962		}
1963	});
1964
1965	$.fn[pluginName] = function (options) {
1966		return this.each(function () {
1967			if (!$.data(this, "plugin_" + pluginName)) {
1968				$.data(this, "plugin_" + pluginName, new Plugin(this, options));
1969			}
1970		});
1971	};
1972})(jQuery, window, document);

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.