PageSourceSearch

https://hplibrary.org/js/am-events-listings.js?v=1480

js hplibrary.org collected 2026-09-25 20:13:03 UTC 84,156 bytes, 1,966 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						var childBottom = Math.ceil(child.getBoundingClientRect().bottom + scrollY);
593						contentHeight = Math.max(contentHeight, childBottom);
594					}
595				}
596				contentHeight += 8;
597				if (!contentHeight || (!forcePost && contentHeight === self._lastEmbeddedHeight)) {
598					return;
599				}
600				self._lastEmbeddedHeight = contentHeight;
601				self._postToParent('height-update', { height: contentHeight });
602			});
603		},
604		_updateDateRange: function () {
605			var self = this;
606			self.paused = true;
607			var now = moment();
608
609			switch (self.settings.range) {
610				case 'today':
611					self.settings.start = now.startOf('day');
612					self.settings.end = now.clone();
613					$('div.events-calendar').datepicker('setDate', now.startOf('day').toDate());
614					$('.events-details a[href="#calendar"]').tab('show');
615					break;
616				case 'tomorrow':
617					self.settings.start = now.add(1, 'day').startOf('day');
618					self.settings.end = now.clone();
619					$('div.events-calendar').datepicker('setDate', self.settings.start.toDate());
620					$('.events-details a[href="#calendar"]').tab('show');
621					break;
622				case 'thisweek':
623					self.settings.start = now;
624					self.settings.end = self.settings.start.clone().endOf('week');
625					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
626					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
627					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
628					break;
629				case 'thismonth':
630					self.settings.start = now;
631					self.settings.end = self.settings.start.clone().endOf('month');
632					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
633					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
634					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
635					break;
636				case 'nextweek':
637					self.settings.start = now.add(1, 'week').startOf('week');
638					self.settings.end = self.settings.start.clone().endOf('week');
639					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
640					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
641					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
642					break;
643				case 'nextmonth':
644					self.settings.start = now.add(1, 'month').startOf('month');
645					self.settings.end = self.settings.start.clone().endOf('month');
646					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
647					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
648					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
649					break;
650				case 'days':
651					self.settings.start = now;
652					self.settings.end = now.clone().add(self.settings.duration, 'days');
653					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
654					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
655					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
656					break;
657				case 'weeks':
658					self.settings.start = now;
659					self.settings.end = now.clone().add(self.settings.duration, 'weeks');
660					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
661					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
662					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
663					break;
664				case 'months':
665					self.settings.start = now;
666					self.settings.end = now.clone().add(self.settings.duration, 'months');
667					$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', self.settings.start.format('MMMM D YYYY'));
668					$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', self.settings.end.format('MMMM D YYYY'));
669					$('.events-details a[href="#date_range"]', self.listViewLeft).tab('show');
670					break;
671			}
672			self.paused = false;
673			self._update();
674		},
675		_bindEvents: function () {
676			var self = this;
677			self.gridView.data('date', moment(self.settings.gridDate).startOf('month'));
678			self.viewPicker.on('click keyup', '[data-view]', function (e) {
679				e.preventDefault();
680
681				var isSpace = e.type === 'keyup' && (e.which == 32 || e.keyCode == 32 || e.key == " ");
682				var isEnter = e.type === 'keyup' && (e.which == 13 || e.keyCode == 13 || e.key == "Enter");
683
684				if (e.type === 'click' || isSpace || isEnter) {
685					$('[data-view]').each(function (_, el) {
686						$(el).attr('aria-pressed', 'mixed');
687					})
688
689					self.setView($(this).attr('data-view'));
690					$(this).attr('aria-pressed', true);
691				}
692			});
693			installCalendarAriaFix(self.element);
694			$('.events-range.input-daterange', self.listViewLeft).datepicker({
695				todayBtn: "linked",
696				title: "Events calendar",
697				orientation: "bottom left",
698				format: 'MM d yyyy',
699				todayHighlight: true
700			}).on('changeDate', function (event) {
701				self.settings.range = 'range';
702				self.settings.start = moment($('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('getDate'));
703				self.settings.end = moment($('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('getDate'));
704				self._update();
705			});
706			$('.events-date-input').on('click', '.input-group-addon', function (event) {
707				event.preventDefault();
708				event.stopPropagation();
709				$(this).closest('.events-date-input').find('input').datepicker('show');
710			});
711
712			$('div.events-calendar', self.listViewLeft).datepicker({
713				title: "Events calendar",
714				startDate: new Date()
715			}).on('changeDate', function (event) {
716				event.preventDefault();
717				self.paused = true;
718				$('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('update', moment(event.date).format('MMMM D YYYY'));
719				$('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('update', moment(event.date).format('MMMM D YYYY'));
720				self.settings.start = moment(event.date).startOf('day');
721				self.settings.end = moment(event.date).endOf('day');
722				if (!self.rangeUpdating) {
723					self.settings.range = 'range';
724				}
725				self.paused = false;
726				self._update();
727			});
728
729			if (self.settings.start === self.settings.end) {
730				self.rangeUpdating = true;
731				$('div.events-calendar', self.listViewLeft).datepicker('setDate', new Date(self.settings.start.format('MMMM D YYYY')));
732				self.rangeUpdating = false;
733			}
734
735			self.$element.on('click', '.events-option-dropdown', function (event) {
736				if (!$(event.target).hasClass('events-option-dropdown')) return;
737				event.preventDefault();
738				var hide = $('.' + $(this).attr('data-show-filter')).is(':visible');
739				$('.events-filter-section', self.$element).fadeOut();
740				if (!hide) {
741					$('div.' + $(this).attr('data-show-filter'), self.$element).css({
742						top: 30,
743						left: 0
744					}).position({
745						my: "left top",
746						at: "left bottom",
747						of: this, // or $("#otherdiv)
748						collision: "fit",
749						within: self.$element,
750						using: function (pos, data) {
751							var menu = data.element.element;
752							pos.top = data.target.top + data.target.height + 30;
753							$(menu).css(pos)
754						}
755					}).fadeIn();
756				}
757			}).on('click', '.events-filter-close', function (event) {
758				event.preventDefault();
759				$(this).closest('.events-filter-section').fadeOut();
760			}).on('change', '#loc_all', function (event) {
761				if ($(this).is(':checked')) {
762					self.$element.find('#event_locations [id^=loc_]').prop('checked', false);
763				} else {
764					$(this).prop('checked', true);
765				}
766				self._update();
767			}).on('change', '#event_locations [id^=loc_]', function (event) {
768				if ($(this).is(':checked')) {
769					self.$element.find('#loc_all').prop('checked', false);
770				} else if (self.$element.find('#event_locations [id^=loc_]:checked').length === 0) {
771					self.$element.find('#loc_all').prop('checked', true);
772				}
773				self._update();
774			}).on('change', '#group_all', function (event) {
775				if ($(this).is(':checked')) {
776					self.$element.find('#event_ages [id^=group_]').prop('checked', false);
777				} else {
778					$(this).prop('checked', true);
779				}
780				self._update();
781			}).on('change', '#event_ages [id^=group_]', function (event) {
782				if ($(this).is(':checked')) {
783					self.$element.find('#group_all').prop('checked', false);
784				} else if (self.$element.find('#event_locations [id^=group_]:checked').length === 0) {
785					self.$element.find('#group_all').prop('checked', true);
786				}
787				self._update();
788			}).on('change', '#type_all', function (event) {
789				if ($(this).is(':checked')) {
790					self.$element.find('#event_types [id^=type_]').prop('checked', false);
791				} else {
792					$(this).prop('checked', true);
793				}
794				self._update();
795			}).on('change', '#event_types [id^=type_]', function (event) {
796				if ($(this).is(':checked')) {
797					self.$element.find('#type_all').prop('checked', false);
798				} else if (self.$element.find('#event_types [id^=type_]:checked').length === 0) {
799					self.$element.find('#type_all').prop('checked', true);
800				}
801				self._update();
802			}).on('click', '.events-filter-clear', function (event) {
803				event.preventDefault();
804				self.$element.find('.events-search-field').val('');
805				self.$element.find('#loc_all').prop('checked', true);
806				self.$element.find('#group_all').prop('checked', true);
807				self.$element.find('#type_all').prop('checked', true);
808				self.$element.find('#event_ages [id^=group_]').prop('checked', false);
809				self.$element.find('#event_types [id^=type_]').prop('checked', false);
810				self.$element.find('#event_locations [id^=loc_]').prop('checked', false);
811				self._update();
812			}).on('keyup', '.events-search-field', function (event) {
813				if (self.SearchTimer) {
814					clearTimeout(self.SearchTimer);
815					self.SearchTimer = false;
816				}
817				var term = $(this).val();
818				self.SearchTimer = setTimeout(function () {
819					self._doSearch(term);
820				}, 750);
821			});
822			$('.events-change-day', self.listViewRight).on('click keyup', function (event) {
823				event.preventDefault();
824
825				var isSpace = event.type === 'keyup' && (event.which === 32 || event.keyCode === 32 || event.key === " ");
826				var isEnter = event.type === 'keyup' && (event.which === 13 || event.keyCode === 13 || event.key === "Enter");
827
828				if (event.type === 'click' || isSpace || isEnter) {
829					self.settings.range = 'range';
830					var newdate = moment($('div.events-calendar', self.listViewLeft).datepicker('getDate')).add($(this).attr('data-by'), 'day').startOf('day');
831					if (newdate.isBefore(moment().startOf('day'))) return;
832					$('div.events-calendar', self.listViewLeft).datepicker('setDate', newdate.toDate());
833				}
834			});
835			self.listViewLeft.on('click', '.events-link>a', function (event) {
836				if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.which === 2) {
837					return;
838				}
839				event.preventDefault();
840				self.paused = true;
841				self.settings.range = $(this).attr('data-type');
842				self.rangeUpdating = true;
843				self._updateDateRange();
844				self.rangeUpdating = false;
845			});
846			self.gridView.on('click', '.events-grid-cell', function (event) {
847				event.preventDefault();
848				var selected = $(this).hasClass('events-grid-cell-selected');
849				self.gridView.find('.events-grid-day-list').remove();
850				self.gridView.find('.events-grid-cell-selected').removeClass('events-grid-cell-selected');
851				if (!selected) {
852					$(this).addClass('events-grid-cell-selected');
853					var dayView = $('<div class="events-grid-day-list"></div>').insertAfter($(this).closest('.events-grid-row'));
854					$.each($(this).data('events'), function (index, val) {
855						var hasPre = false;
856						var isBilled = false;
857						var preText = '';
858						var tagString = '';
859						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;';
860						var title = val.title;
861
862						if (typeof (val.enable_billing) !== 'undefined') {
863							isBilled = parseInt(val.enable_billing) === 1;
864						}
865
866						if (self.settings.eventsGroupCalenderBAge) {
867							if (val.ages) {
868								$.each(val.agesArray, function (index, val) {
869									var dotColour = typeof self.ages[val] !== 'undefined' ? self.ages[val] : self._genColour(val);
870									tagString += ' <i class="fa fa-circle" style="color:' + dotColour + '"></i> ' + val;
871								});
872							}
873						} else {
874							if (val.tags) {
875								$.each(val.tagsArray, function (index, val) {
876									var dotColour = typeof self.tags[val] !== 'undefined' ? self.tags[val] : self._genColour(val);
877									tagString += ' <i class="fa fa-circle" style="color:' + dotColour + '"></i> ' + val;
878								});
879							}
880						}
881
882						if (parseInt(val.changed) === 1) {
883							title = '<span class="eelist-changed-message">Cancelled</span>&nbsp;' + title;
884						}
885
886						if (parseInt(val.changed) === 2 || parseInt(val.changed) === 3) {
887							title = '<span class="eelist-changed-message">Rescheduled</span>&nbsp;' + title;
888						}
889
890						if (isBilled) {
891							title = ticketIcon + title;
892						}
893
894						if (hasPre) {
895							title = preText + title;
896						}
897
898
899						if (val.link) {
900							var link = $('<a target="' + self.settings.linkTarget + '" href="' + val.link + '"> ' + $('<div></div>').html(title).text() + '</a>');
901						} else {
902							var id = val.new_event_id || val.id;
903							var embeddedEventPath = self._getEmbeddedEventPath(id);
904							var link = $('<a target="' + self.settings.linkTarget + '">' + title + '</a>');
905							if (window.websiteUrl) {
906								link.attr('href', window.websiteUrl + '/event/' + id);
907							} else {
908								link.attr('href', '/event/' + id);
909							}
910							self._bindEmbeddedEventLink(link, embeddedEventPath);
911						}
912						if (val.sub_title) {
913							link.append('<span> - ' + val.sub_title + '</span>');
914						}
915
916
917						link.qtip({
918							content: {
919								text: function (event, api) {
920									return self._getEventBlock(val, true);
921								}
922							},
923							style: {
924								classes: 'qtip-bootstrap events-popup',
925								def: false,
926								width: 500
927							},
928							hide: {
929								fixed: true,
930								delay: 300
931							},
932							position: {
933								viewport: $(window)
934							}
935						});
936						if (parseInt(val.changed) === 1) {
937							$('<div>&nbsp;, ' + val.location + '&nbsp;&nbsp;<span class="events-grid-day-list-tags">' + tagString + '</span></div>').prepend(link).appendTo(dayView);
938						} else if (parseInt(val.changed) === 2 || parseInt(val.changed) === 3) {
939							$('<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);
940						} else {
941							$('<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);
942						}
943
944					});
945				}
946			}).on('click keyup', '.events-change-day', function (e) {
947				e.preventDefault();
948
949				var isSpace = event.type === 'keyup' && (e.which == 32 || e.keyCode == 32 || e.key == " ");
950				var isEnter = event.type === 'keyup' && (e.which == 13 || e.keyCode == 13 || e.key == "Enter");
951
952				if (e.type === 'click' || isSpace || isEnter) {
953					self.settings.range = 'range';
954					self.gridView.data('date').add($(this).attr('data-by'), 'month');
955					self.settings.gridDate = self.gridView.data('date').format('YYYY-MM-DD');
956					self._update();
957				}
958			});
959
960			self.todayButton.on('click', function (event) {
961				event.preventDefault();
962				self.settings.range = 'today';
963				if (self.settings.view == 'list') {
964					self.rangeUpdating = true;
965					$('div.events-calendar', self.listViewLeft).datepicker('setDate', moment().startOf('day').toDate());
966					self.rangeUpdating = false;
967				} else {
968					self.gridView.data('date', moment().startOf('month'));
969
970				}
971				self._update();
972			});
973			$(window).on('resize', function () {
974				var w = $(document).width();
975				if (w < 768 && self.settings.view == 'grid') {
976					self.setView('list');
977				} else {
978
979				}
980				self._scheduleHeightPost();
981			});
982
983			// Brochure button logic:
984
985		},
986		_doSearch: function (term) {
987			this.paused = true;
988			var now = moment();
989			if (this.settings.filters.days === 0) {
990				var startDate = now.format('MMMM D YYYY');
991				var endDate = now.endOf('month').format('MMMM D YYYY');
992				$('.events-range.input-daterange input[name=start]', this.listViewLeft).datepicker('update', startDate);
993				$('.events-range.input-daterange input[name=end]', this.listViewLeft).datepicker('update', endDate);
994			}
995			$('.events-details a[href="#date_range"]', this.listViewLeft).tab('show');
996			this.paused = false;
997			this._update();
998		},
999
1000		// #4381 - CK
1001		// Hide external venue description on qtip
1002		_getEventBlock: function (event, inQtip) {
1003			var self = this;
1004			var $item = $('<div class="eelistevent-data"></div>');
1005			if (parseInt(event.changed) === 3) {
1006				$item.addClass('eelistevent-postponed');
1007			}
1008			if (parseInt(event.changed) === 2 || parseInt(event.changed) === 3) {
1009				$('<div class="eelist-changed-message"></div>').text('Rescheduled').prependTo($item);
1010			} else if (parseInt(event.changed) === 1) {
1011				$item.addClass('eelistevent-canceled');
1012				$('<div class="eelist-changed-message"></div>').text('Cancelled').prependTo($item);
1013			}
1014			var title = event.title;
1015			var evtLink = '';
1016			var imageLink = ''
1017
1018			if (event.sub_title) title += '<span>' + ' - ' + event.sub_title + '</span>';
1019			if (event.link) {
1020				evtLink = $('<a target="' + self.settings.linkTarget + '" href="' + event.link + '"></a>');
1021			} else {
1022				var id = event.new_event_id || event.id;
1023				evtLink = $('<a target="' + self.settings.linkTarget + '" href="' + (window.websiteUrl || "") + '/event/' + id + '"></a>');
1024				self._bindEmbeddedEventLink(evtLink, self._getEmbeddedEventPath(id));
1025			}
1026
1027			imageLink = evtLink.clone();
1028			evtLink.html(title);
1029			$('<div class="eelisttitle"></div>').append(evtLink).appendTo($item);
1030
1031			// CH 14854 - Add in event images.
1032			//
1033			if (self.settings.showImagesInListGrid) {
1034				if (typeof event.image === 'string' && event.image.length > 0) {
1035					var url = 'https://' + self.settings.client + '.libnet.info/images/events/' + self.settings.client + '/' + event.image;
1036					//var image = '<img src="' + url + '" class="img-thumbnail event-list-image" alt="' + event.title + '"/>';
1037					var imageFrame = $('<div/>');
1038					var image = $('<span/>')
1039						.attr('role', 'img')
1040						.attr('aria-label', event.title)
1041						.css({
1042							'display': 'inline-block',
1043							'width': '118px',
1044							'height': '118px',
1045							'background-image': 'url(\'' + url + '\')',
1046							'background-position': 'center center',
1047							'background-size': 'cover'
1048						});
1049
1050					if (typeof event.image_alt_text === 'string' && event.image_alt_text.length > 0) {
1051						image.attr('aria-label', event.image_alt_text);
1052					}
1053
1054					imageFrame.append(image);
1055					imageLink.append(imageFrame);
1056
1057					$('<div class="eelistimage"></div>').append(imageLink).appendTo($item);
1058				}
1059			}
1060
1061			if (parseInt(event.changed) === 2) {
1062				$item.append('<div class="eelisttime eelisttime-postponed headingtext">(' +
1063					'<span>Rescheduled from </span>' +
1064					event.datestring + ': ' + event.time_string +
1065					')</div>');
1066
1067				var tt = $('<div class="eelisttime headingtext"></div>').append("<span class='eelist-changed-message'> New date </span>").appendTo($item);
1068				tt.append(document.createTextNode(event.changed_datestring + ': ' + event.changed_time_string));
1069			} else if (parseInt(event.changed) === 3) {
1070				$item.append('<div class="eelisttime headingtext">' + event.datestring + ': ' + event.time_string + '</div>');
1071				var tt = $('<div class="eelisttime headingtext"></div>').append("<span class='eelist-changed-message'> New date </span>").appendTo($item);
1072				tt.append(document.createTextNode(event.changed_datestring + ': ' + event.changed_time_string));
1073			} else {
1074				$item.append('<div class="eelisttime headingtext">' + event.datestring + ': ' + event.time_string + '</div>');
1075			}
1076
1077			var lstring = event.library;
1078			if (event.venues) {
1079				lstring += ' - <i>' + event.venues + '</i>';
1080			}
1081			if (parseInt(event.changed_venue) == 1) {
1082				$item.append('<div class="eelocation"><i class="am-locations"/><span class="eelist-changed-message"> New venue </span> ' + lstring + '</div>');
1083			} else {
1084				$item.append('<div class="eelocation"><i class="am-locations"/> ' + lstring + '</div>');
1085			}
1086
1087			if (event.agesArray) {
1088				if (self.settings.eventsGroupCalenderBAge) {
1089					var tagsList = $('<div class="eelisttags"></div>').append('<span>Age group:&nbsp;&nbsp;</span>').appendTo($item);
1090					$.each(event.agesArray, function (index, val) {
1091						var dotColour = typeof self.ages[val] !== 'undefined' ? self.ages[val] : self._genColour(val);
1092						tagsList.append('<span><i class="fa fa-circle" style="color:' + dotColour + '"></i> ' + val + ' </span>');
1093					});
1094				} else {
1095					$('<div class="eelistgroup"></div>').append('<span>Age group:&nbsp;&nbsp;</span>').append(event.agesArray.join(", ")).appendTo($item);
1096				}
1097			}
1098
1099			if (event.tagsArray) {
1100				if (self.settings.eventsGroupCalenderBAge) {
1101					$('<div class="eelistgroup"></div>').append('<span>' + escapeHtml(self.settings.customEventLabel) + ' type:&nbsp;&nbsp;</span>').append(event.tagsArray.join(", ")).appendTo($item);
1102				} else {
1103					var tagsList = $('<div class="eelisttags"></div>').append('<span>' + escapeHtml(self.settings.customEventLabel) + ' type:&nbsp;&nbsp;</span>').appendTo($item);
1104					$.each(event.tagsArray, function (index, val) {
1105						var dotColour = typeof self.tags !== 'undefined' && typeof self.tags[val] !== 'undefined' ? self.tags[val] : self._genColour(val);
1106						tagsList.append('<span><i class="fa fa-circle" style="color:' + dotColour + '"></i> ' + val + ' </span>');
1107					});
1108				}
1109			}
1110
1111			$item.append('<div class="eelistdesc">' + event.description + '</div>');
1112			// #4381 - CK
1113			// Hide external venue description on qtip
1114			if (event.venue_type && event.venue_type == 'external' && event.venue_description.length > 0 && !inQtip) {
1115				$item.append('<div class="eelistvenuedesc"><b>Venue details</b><br>' + event.venue_description + '</div>');
1116			}
1117
1118			if ((parseInt(event.changed, 10) !== 1 && parseInt(event.changed, 10) !== 3) && parseInt(event.allow_reg, 10) === 1) {
1119				if (moment.tz(event.close_registration, self.settings.timezone).diff() < 0) {
1120					if (parseInt(event.third_party_reg, 10) == 0) {
1121						$item.append('<div class="regclosed">Registration is now closed</div>');
1122					}
1123				} else {
1124					// If third-party-reg is off then run the numbers, otherwise jump to next if set.
1125					// CH #11417 - Added maximums for waitlists.
1126					if (event.ticketInfo) {
1127						var available = 0;
1128						if (event.ticketInfo.ticketData) {
1129							for (var t = 0; t < event.ticketInfo.ticketData.length; t++) {
1130								var tkt = event.ticketInfo.ticketData[t];
1131								available += tkt.available;
1132							}
1133						}
1134
1135						if (event.ticketInfo.typeId === -1) {
1136							available = event.ticketInfo.available;
1137						}
1138
1139						if (available <= 0) {
1140							if (self.settings.allowWaitlistSubscription) {
1141								$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>');
1142								$('<button data-eventid="' + event.id + '" data-recurringid="' + event.recurring_id + '" data-regtype="-1" class="button subscribeToEvent"></button>')
1143									.text(self.settings.customInterestedText)
1144									.on('click', function () {
1145										self._showInterestedPopup($(this).attr('data-eventid'), $(this).attr('data-recurringid'), event);
1146									})
1147									.appendTo($item)
1148							} else {
1149								$item.append('<div class="regclosed">This ' + escapeHtml(self.settings.customEventLabel) + ' is full</div>');
1150							}
1151
1152							//$btn.text('Event Full').prop('disabled', true);
1153							return $item;
1154						}
1155					}
1156
1157					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))));
1158					var regAllowed = parseInt(event.total_registrants, 10) < parseInt(event.max_attendee, 10) || allowWaitList;
1159
1160					if (parseInt(event.third_party_reg, 10) === 0) {
1161						if (!regAllowed) {
1162							$item.append('<div class="regclosed">This ' + escapeHtml(self.settings.customEventLabel) + ' is full</div>');
1163							return $item;
1164						}
1165					}
1166
1167					if (event.version == 2 && parseInt(event.third_party_reg, 10) === 1) {
1168						if (event.reg_url.length > 0) {
1169							$('<a target="' + self.settings.linkTarget + '" href="' + event.reg_url + '" class="button eventRegButton">Register</a>').appendTo($item);
1170						}
1171					} else if (parseInt(event.staff_only_registration, 10) === 1) {
1172						$('<div class="regclosed eventStaffRegMessage">Please contact the library to register for this event</div>').appendTo($item);
1173					} else {
1174						var event_id = event.id;
1175						var location_id = event.location_id;
1176						var $btn, $onlineBtn;
1177
1178						if (parseInt(event.total_registrants, 10) >= parseInt(event.max_attendee, 10)) {
1179							var m = 'Join the wait list';
1180							var regstatus = '0';
1181							var t = '2';
1182						} else {
1183							var m = 'Register';
1184							var regstatus = '1';
1185							var t = '1';
1186						}
1187
1188						if (parseInt(event.whole_serires_only, 10) === 1 && moment(event.series_starts).subtract(event.registration_close, 'minutes').tz(self.settings.timezone).diff() < 0) {
1189							if (parseInt(event.third_party_reg, 10) == 0) {
1190								$item.append('<div class="regclosed">Registration is now closed</div>');
1191							}
1192						} else if (parseInt(event.whole_serires_only, 10) === 0 || (parseInt(event.whole_serires_only, 10) === 1)) {
1193							$btn = null, $onlineBtn = null;
1194							if (event.event_type !== undefined && event.event_type !== '') {
1195								if (event.event_type == 'INPERSON' && event.webinar_seats && event.venue_type == 'external') {
1196									event.event_type = 'HYBRID';
1197									event.max_attendee_virtual = event.webinar_seats;
1198								}
1199
1200								switch (event.event_type) {
1201									case 'INPERSON':
1202										var ariaLabel = m + " for " + event.title + " in person" + " on " + event.datestring + " at " + event.start_time;
1203										$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();
1204										break;
1205									case 'ONLINE':
1206										var ariaLabel = m + " for " + event.title + " on line" + " on " + event.datestring + " at " + event.start_time;
1207										$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();
1208										break;
1209									case 'HYBRID':
1210										if (parseInt(event.allow_waitlist, 10) == 1 && parseInt(event.total_inperson_registrants, 10) >= parseInt(event.max_attendee_in_branch, 10)) {
1211											var m_in_branch = 'Join the wait list';
1212											var t_in_branch = '2';
1213										} else if (parseInt(event.max_attendee_in_branch, 10) > 0) {
1214											var m_in_branch = 'Register';
1215											var t_in_branch = '1';
1216										}
1217
1218										if (parseInt(event.allow_waitlist, 10) == 1 && parseInt(event.total_online_registrants, 10) >= parseInt(event.max_attendee_virtual, 10)) {
1219											var m_online = 'Join the wait list';
1220											var t_online = '2';
1221										} else if (parseInt(event.max_attendee_virtual, 10) > 0) {
1222											var m_online = 'Register';
1223											var t_online = '1';
1224										}
1225
1226										if (parseInt(event.allow_waitlist, 10) == 1 || parseInt(event.total_inperson_registrants, 10) < parseInt(event.max_attendee_in_branch, 10)) {
1227											var ariaLabel = m_in_branch + " for " + event.title + " to attend in person" + " on " + event.datestring + " at " + event.start_time;
1228											if (parseInt(event.max_attendee_in_branch, 10) > 0) {
1229												$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();
1230											}
1231										}
1232
1233										if (parseInt(event.allow_waitlist, 10) == 1 || parseInt(event.total_online_registrants, 10) < parseInt(event.max_attendee_virtual, 10)) {
1234											var ariaLabelOnline = m_online + " for " + event.title + " to attend online" + " on " + event.datestring + " at " + event.start_time;
1235											if (parseInt(event.max_attendee_virtual, 10) > 0) {
1236												$onlineBtn = $('<button aria-label="' + ariaLabelOnline + '" data-eventid="' + event.i
1236d + '" 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();
1237											}
1238										}
1239
1240										break;
1241								}
1242							} else {
1243								var ariaLabelOther = m + " for " + event.title + " on " + event.datestring + " at " + event.start_time;
1244								$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();
1245							}
1246
1247							// Time juggling for the reg button CH10228.
1248							// Due to some oddities in the way moment works and calculates differences
1249							// No TZ conversion here since time is saved as a TZ time to the database. It also screws with comparisons.
1250							// 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:
1251							// E.g(2). 2019-02-01T11:00:00-6:00 as tz calculation takes into account a tz-less time. Confusing. I know.
1252							var regOpens = moment(event.reg_opens).format('YYYY-MM-DD HH:mm:ss');
1253							// Add the timezone here to account for local computer time
1254							// 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.
1255							var nowTime = moment().tz(self.settings.timezone).format('YYYY-MM-DD HH:mm:ss');
1256							// Formatted times can now be compared, because the TZ string has been stripped. So basically we are comparing two different times.
1257							// E.g. 2019-02-01 1:30:00 (now) >= 2019-02-01 12:00:00 (opens) would be true.
1258							var regOpened = nowTime >= regOpens;
1259
1260							if (parseInt(event.open_registration_imidiatly, 10) === 0 && !regOpened) {
1261								if ($btn) {
1262									$btn.prop('disabled', true);
1263								}
1264
1265								if ($onlineBtn) {
1266									$onlineBtn.prop('disabled', true);
1267								}
1268
1269								$('<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);
1270							}
1271						}
1272
1273
1274					}
1275				}
1276
1277				if (typeof event.enable_billing !== 'undefined' && parseInt(event.enable_billing) === 1) {
1278					// Then there is class/ticketdata info
1279					$('.ticket-group', $item).remove();
1280					var coEventBillingInfo = new EventBillingInfo(event.recurring_id, 1, event.ticketInfo);
1281					var infoBox = $('<div class="eelistgroup ticket-group"></div>');
1282					infoBox.append(coEventBillingInfo.info).appendTo($item);
1283				}
1284			}
1285			return $item;
1286		},
1287		_getFeed: function (type) {
1288			var self = this;
1289			var feedData = {
1290				feedType: type,
1291				filters: {
1292					location: [],
1293					ages: [],
1294					types: [],
1295					tags: [],
1296					term: '',
1297					days: 1
1298				}
1299			};
1300
1301			if (self.settings.filters) {
1302				var filters = self.settings.filters;
1303				feedData.filters.ages = filters.ages.length > 0 ? filters.ages : ['all'];
1304				feedData.filters.types = filters.types.length > 0 ? filters.types : ['all'];
1305				feedData.filters.location = self.settings.locations.length > 0 ? self.settings.locations : ['all'];
1306			}
1307			if($('.events-search-field', self.element).val() && $('.events-search-field', self.element).val().length > 0) {
1308				feedData.filters.term = $('.events-search-field', self.element).val();
1309			} else if (self.settings.term && self.settings.term.length > 0) {
1310				feedData.filters.term = self.settings.term;
1311			}
1312
1313			var urlFilters = JSON.stringify(feedData);
1314			var url = window.location.protocol + "//" + window.location.hostname + '/feeds?data=' + btoa(urlFilters);
1315			this._showFeedPopup(url, feedData);
1316			//window.open('/feeds?data='+ urlFilters , '_blank');
1317		},
1318		_showFeedPopup: function (url, filterData) {
1319			var self = this;
1320			var term = filterData.filters.term;
1321			var filterString = 'Showing ';
1322
1323			if (term && term.length > 2) {
1324				filterString += 'search results for <b>' + term + '</b> for ';
1325			}
1326
1327			if (filterData.filters.types.length > 0) {
1328				filterString += filterData.filters.types.join(', ') + ' events';
1329			} else {
1330				filterString += 'all ' + self.settings.customEventLabel + 'types';
1331			}
1332			filterString += ' at ';
1333			if (self.settings.filters.locations.length > 0) {
1334				if (self.settings.filters.locations.length > 1) {
1335					filterString += 'these locations ' + self.settings.filters.locations.join(', ');
1336				} else {
1337					if (self.settings.filters.locations[0] !== 'all') {
1338						filterString += self.settings.filters.locations[0];
1339					} else {
1340						filterString += 'all locations';
1341					}
1342				}
1343			} else {
1344				filterString += 'all locations';
1345			}
1346
1347			filterString += ' for ';
1348			if (filterData.filters.ages.length > 0) {
1349				if (filterData.filters.ages.length > 1) {
1350					filterString += 'the age groups ' + filterData.filters.ages.join(', ');
1351				} else {
1352					filterString += 'the age group ' + filterData.filters.ages[0];
1353				}
1354			} else {
1355				filterString += 'all ages';
1356			}
1357
1358
1359			if (filterData) {
1360				var icon = "fa fa-rss";
1361				var style = "color:#f26522";
1362				var title = "RSS";
1363
1364				if (filterData.feedType === 'ical') {
1365					icon = "fa fa-calendar";
1366					style = "color:#3981c6";
1367					title = "iCal";
1368				}
1369
1370				var html = '<div id="brochure-popup">' +
1371					'	<div class="row">' +
1372					'		<div class="col-md-12">' +
1373					'			<div style="float:right;"><a class="close-brochure-popup" style="cursor: pointer;">close</a></div>' +
1374					'			<h3 class="patron-popup heading-text"><i class="' + icon + '" style="' + style + '"></i>&nbsp;&nbsp;' + title + '&nbsp;Events Feed</h3>' +
1375					'		</div>' +
1376					'	</div>' +
1377					'	<div class="patron-popup top-row row">' +
1378					'		<div class="col-md-12">' +
1379					'			<strong>Feed will contain up to 500 events with filters selected, excluding dates.</strong>' +
1380					'		</div>' +
1381					'		<div class="col-md-12">' +
1382					'			Filtered By: ' + filterString +
1383					'		</div>' +
1384					'	</div>' +
1385					'	<div class="patron-popup row" style="margin-left:15px;margin-right:15px;padding-top:35px;padding-bottom:20px;">' +
1386					'		<div class="col-md-12">' +
1387					'			<input type="text" id="feed-link" class="form-control" aria-label="Feed URL" value="' + url + '"/>' +
1388					'		</div>' +
1389					'	</div>' +
1390					'	<div class="patron-popup button-row" style="margin-left:15px;">' +
1391					'		<div class="col-md-3 col-md-offset-6" style="padding-top: 10px;">' +
1392					'			<button class="copy-button brochure-button btn-block">Copy Link to Clipboard</button>' +
1393					'		</div>' +
1394					'		<div class="col-md-3" style="padding-top: 10px;">' +
1395					'			<button class="view-button brochure-button btn-block">Download File</button>' +
1396					'		</div>' +
1397					'	</div>' +
1398
1399					'</div>';
1400
1401				var popup = $('<div class="events2-brochure"></div>
1401').amPopup({
1402					title: '',
1403					popUpClass: '',
1404					showTitle: false
1405				});
1406				popup.append(html);
1407				popup.on('click', '.close-brochure-popup', function () {
1408					popup.data('plugin_amPopup').close();
1409				})
1410					.on('click', '.copy-button', function () {
1411						var el = document.getElementById("feed-link");
1412						el.select();
1413						document.execCommand('copy');
1414					}).on('click', '.view-button', function () {
1415						window.open(url, '_blank');
1416					});
1417				popup.data('plugin_amPopup').open();
1418			}
1419		},
1420		_showBrochurePopup: function () {
1421			var filters = {};
1422			var options = {};
1423			var self = this;
1424
1425			if (self.settings.filters) {
1426				filters = self.settings.filters;
1427			}
1428
1429			if (self.settings.filterOptions) {
1430				options = self.settings.filterOptions;
1431			}
1432
1433			var subData = {
1434				filters: filters,
1435				options: options,
1436				allowSubs: self.settings.allowSubs,
1437				customEventsLabel: self.settings.customEventsLabel,
1438				customEventLabel: self.settings.customEventLabel
1439			};
1440
1441			var ps = new patronSubscriptions(subData);
1442			ps.showPopup();
1443		},
1444		// CH14299
1445		_showInterestedPopup: function (dateId, recurringId, eventInfo) {
1446			var self = this;
1447			var eventInfo = {
1448				title: eventInfo.title + ' - ' + eventInfo.sub_title,
1449				datestring: eventInfo.datestring + ' - ' + eventInfo.time_string,
1450				location: eventInfo.location
1451			};
1452
1453			var interested = new EventBillingSubscriptions(dateId, recurringId, eventInfo, true);
1454			var interestedForm = interested.subscriptionForm;
1455			interestedForm.appendTo('body');
1456
1457			$('.Event_Full-dialog').modal({
1458				backdrop: 'static',
1459				keyboard: false
1460			}).modal('show');
1461
1462		},
1463		_update: function () {
1464			var self = this;
1465			if (self.paused) return;
1466			if (!self.initComplete) return;
1467			var options = {};
1468			var filter = {};
1469			options.private = self.settings.private;
1470
1471			var term = $('.events-search-field', self.element).val();
1472			if (term && term.length > 2) {
1473				options.search = term;
1474			}
1475
1476			if (self.settings.view == 'list') {
1477				var end = moment($('.events-range.input-daterange input[name=end]', self.listViewLeft).datepicker('getDate'));
1478				var start = moment($('.events-range.input-daterange input[name=start]', self.listViewLeft).datepicker('getDate'));
1479				var days = end.diff(start, 'days');
1480
1481				if (self.settings.filters.date === start.format('YYYY-MM-DD')) {
1482					options.date = self.settings.filters.date;
1483				} else {
1484					options.date = start.format('YYYY-MM-DD');
1485				}
1486
1487				if (self.settings.filters.days === days) {
1488					options.days = self.settings.filters.days;
1489				} else {
1490					options.days = end.diff(start, 'days');
1491				}
1492
1493			} else {
1494				var date = self.gridView.data('date').clone();
1495				// options.date=date.isoWeekday(1).format('YYYY-MM-DD');
1496				options.date = date.startOf('week').format('YYYY-MM-DD');
1497				options.days = 42;
1498			}
1499
1500			//if (options.days === 0) options.days = 1;
1501
1502			$(".events-brochure, .brochure-grid, .events-rss, .events-ical").remove();
1503			var navigation = $('.events-filter-row').first();
1504			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>' : '';
1505			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>' : '';
1506			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>' : '';
1507			var left = $('.events-left');
1508
1509			if (self.settings.view === "grid") {
1510				navigation.append($('<div class="col-md-12 brochure-grid" style="margin-top:10px;">' + brochureButton + '</div>'));
1511			} else {
1512				left.append(brochureButton);
1513				left.append(rssButton);
1514				left.append(icalButton);
1515			}
1516
1517			$('.events-brochure-button').on('click', function () {
1518				//self._getBrochure();
1519				if (self.settings.brochureNoPopup) {
1520					window.location.href = "/brochure";
1521				} else {
1522					self._showBrochurePopup();
1523				}
1524			});
1525
1526			$('.events-rss-button').on('click', function () {
1527				//self._getBrochure();
1528				self._getFeed('rss');
1529			});
1530
1531			$('.events-ical-button').on('click', function () {
1532				//self._getBrochure();
1533				self._getFeed('ical');
1534			});
1535
1536			$('.events-day-title', self.listViewRight).toggle(options.days == 0);
1537			$('.events-range-title', self.listViewRight).toggle(options.days >= 1);
1538
1539			if (!$('#loc_all', self.listViewLeft).is(':checked')) {
1540				options.locations = [];
1541				filter.locations = [];
1542				$('[id^=loc_]', self.locationList).each(function () {
1543					if ($(this).is(':checked')) {
1544						options.locations.push($(this).attr('id').substr(4));
1545						filter.locations.push($.trim($(this).parent().text()));
1546					}
1547				});
1548			}
1549			if (!$('#group_all', self.listViewLeft).is(':checked')) {
1550				options.ages = [];
1551				filter.ages = [];
1552				$('[id^=group_]', self.ageList).each(function () {
1553					if ($(this).is(':checked')) {
1554						options.ages.push(encodeURIComponent($(this).attr('value')));
1555						filter.ages.push($.trim($(this).parent().text()));
1556					}
1557				});
1558			}
1559			if (!$('#type_all', self.listViewLeft).is(':checked')) {
1560				options.types = [];
1561				filter.types = [];
1562				$('[id^=type_]', self.typeList).each(function () {
1563					if ($(this).is(':checked')) {
1564						options.types.push(encodeURIComponent($(this).attr('value')));
1565						filter.types.push($.trim($(this).parent().text()));
1566					}
1567				});
1568			}
1569			if ((term && term.length > 2) || filter.locations.length > 0 || filter.ages.length > 0 || filter.types.length > 0) {
1570				var filterString = 'Showing ';
1571				if (term && term.length > 2) {
1572					filterString += 'search results for <b>' + term + '</b> for ';
1573				}
1574				if (filter.types.length > 0) {
1575					filterString += filter.types.join(', ') + ' events';
1576				} else {
1577					filterString += 'all ' + self.settings.customEventLabel + ' types';
1578				}
1579				filterString += ' at ';
1580				if (filter.locations.length > 0) {
1581					filterString += filter.locations.join(', ');
1582				} else {
1583					filterString += 'all locations';
1584				}
1585				filterString += ' for ';
1586				if (filter.ages.length > 0) {
1587					if (filter.ages.length > 1) {
1588						filterString += 'the age groups ' + filter.ages.join(', ');
1589					} else {
1590						filterString += 'the age group ' + filter.ages[0];
1591					}
1592				} else {
1593					filterString += 'all ages';
1594				}
1595				$('.events-filter-message', self.views).html(filterString);
1596				$('.events-filter-details', self.views).fadeIn();
1597			} else {
1598				$('.events-filter-details', self.views).hide();
1599			}
1600			$('.events-date-range-string', self.listViewRight).text('');
1601
1602			var $list = $('.events-body', self.listViewRight).empty().text('Loading events...');
1603
1604			var $grid = $('.events-grid-body', self.gridView).empty().text('Loading events...');
1605
1606			// Fix for bad dates.
1607			options.days++;
1608
1609			// Request for brochure on front-end, need to save off the filters - which are all the options used to get event data
1610			var filterOptions = {
1611				ages: filter.ages,
1612				locations: filter.locations,
1613				types: filter.types,
1614				days: options.days,
1615				date: options.date,
1616				private: options.private,
1617				term: options.search
1618			};
1619
1620			self.settings.locations = options.locations;
1621			self.settings.filters = filterOptions;
1622			if (typeof (filterString) !== "undefined") {
1623				self.settings.filters.display = filterString.substr(7);
1624			} else {
1625				self.settings.filters.display = "All events for date range, no filters.";
1626			}
1627
1628			if (typeof (options.display) !== "undefined") {
1629				delete options.display;
1630			}
1631
1632			self.settings.filterOptions = options;
1633			self._setURLPieces();
1634			self._updateQuickRangeLinks();
1635
1636			var website = (window.websiteUrl || "");
1637			$.getJSON(website + '/eeventcaldata?event_type=' + self.settings.type + '&req=' + encodeURIComponent(JSON.stringify(options)), function (data) {
1638				if (self.settings.view == 'list') {
1639					$('.events-date-string', self.listViewRight).text(start.format('MMMM D YYYY'));
1640					$('.events-date-range-string', self.listViewRight).text(start.format('MMMM D YYYY') + ' - ' + end.format('MMMM D YYYY'));
1641					$list.empty();
1642					$.each(data, function (index, val) {
1643
1644						var cancelled = parseInt(val.changed);
1645						if ((cancelled !== 1 || isNaN(cancelled)) || self.settings.eventsCalendarShowCancelledEvents) {
1646							$('<div class="eelistevent"></div>').append(self._getEventBlock(val, false)).appendTo($list);
1647						}
1648					});
1649					if ($list.children().length === 0) $list.html('<div class="eelisttitle" style="margin-left:20px">Sorry there are no events that match your selection.</div>');
1650				} else if (self.settings.view == 'grid') {
1651					var sortedData = {};
1652					$.each(data, function (index, val) {
1653						var d = moment(val.raw_start_time).format('YYYY-M-D');
1654						if (!sortedData[d]) sortedData[d] = [];
1655
1656						var cancelled = parseInt(val.changed);
1657						if ((cancelled !== 1 || isNaN(cancelled)) || self.settings.eventsCalendarShowCancelledEvents) {
1658							sortedData[d].push(val);
1659						}
1660					});
1661					$grid.empty();
1662					var month = self.gridView.data('date').format('M');
1663					$('.events-date-string', self.gridView).text(self.gridView.data('date').format('MMMM YYYY'));
1664
1665					var a = moment(options.date);
1666					var b = a.clone().add(42, 'days');
1667					var row = false;
1668					for (var m = a; m.isBefore(b); m.add(1, 'days')) {
1669						if (m.format('e') == 0) {
1670							row = $('<div class="events-grid-row"></div>').appendTo($grid);
1671						}
1672						var cell = $('<div class="events-grid-cell noselect"></div>').appendTo(row);
1673
1674						$('<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);
1675						var cellContent = $('<div class="events-grid-cell-content"></div>').appendTo(cell);
1676						var cellTypes = $('<div class="events-grid-cell-types"></div>').appendTo(cell);
1677
1678						if (m.format('M') !== month) cell.addClass('events-grid-cell-fade');
1679						if (m.isSame(moment().startOf('day'))) cell.addClass('events-grid-cell-today');
1680						cell.data('events', sortedData[m.format('YYYY-M-D')] || []);
1681
1682						if (cell.data('events').length > 0) {
1683							var dayData = cell.data('events');
1684							var evts = $('<div class="events-grid-cell-event-holder"></div>').appendTo(cell);
1685							if (dayData.length <= self.settings.inCellEvents) {
1686								$.each(dayData, function (index, val) {
1687									if (self.settings.eventsGroupCalenderBAge) {
1688										var age = val.agesArray[0];
1689										if (parseInt(val.changed, 10) === 2) {
1690											var start = (val.changed_start_time == '12:00am' && val.changed_end_time == '11:59pm') ? 'All day' : val.changed_start_time;
1691										} else {
1692											var start = (val.start_time == '12:00am' && val.end_time == '11:59pm') ? 'All day' : val.start_time;
1693										}
1694										var dotColour = typeof self.ages[age] !== 'undefined' ? self.ages[age] : self._genColour(age);
1695									} else {
1696										var tag = val.tagsArray[0];
1697										if (parseInt(val.changed, 10) === 2) {
1698											var start = (val.changed_start_time == '12:00am' && val.changed_end_time == '11:59pm') ? 'All day' : val.changed_start_time;
1699										} else {
1700											var start = (val.start_time == '12:00am' && val.end_time == '11:59pm') ? 'All day' : val.start_time;
1701										}
1702										var dotColour = typeof self.tags[tag] !== 'undefined' ? self.tags[tag] : self._genColour(tag);
1703									}
1704									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);
1705									evcell.qtip({
1706										content: {
1707											text: function (event, api) {
1708												return self._getEventBlock(val, true);
1709											}
1710										},
1711										style: {
1712											classes: 'qtip-bootstrap events-popup',
1713											def: false,
1714											width: 500
1715										},
1716										hide: {
1717											fixed: true,
1718											delay: 300
1719										},
1720										position: {
1721											viewport: $(window)
1722										}
1723									});
1724
1725								});
1726							} else {
1727								var locs = [];
1728								var tags = {};
1729								$.each(dayData, function (index, val) {
1730									if (parseInt(val.changed, 10) == 1) return;
1730 // Do not show cancelled events in total
1731									locs.push(val.location);
1732									if (self.settings.eventsGroupCalenderBAge) {
1733										if (val.ages) {
1734											$.each(val.agesArray, function (index, val) {
1735												if (!tags[val]) {
1736													tags[val] = 1;
1737												} else {
1738													tags[val]++;
1739												}
1740											});
1741										}
1742									} else {
1743										if (val.tags) {
1744											$.each(val.tagsArray, function (index, val) {
1745												if (!tags[val]) {
1746													tags[val] = 1;
1747												} else {
1748													tags[val]++;
1749												}
1750											});
1751										}
1752									}
1753								});
1754								locs = locs.filter(function (item, i, ar) {
1755									return ar.indexOf(item) === i;
1756								});
1757
1758								var i = 0;
1759								$.each(tags, function (type, total) {
1760									i++;
1761									if (i <= 14) {
1762										if (self.settings.eventsGroupCalenderBAge) {
1763											var textColour = self._getContrastYIQ(self.ages[type] || self._genColour(type));
1764											$('<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);
1765										} else {
1766											var textColour = self._getContrastYIQ(self.tags[type] || self._genColour(type));
1767											$('<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);
1768										}
1769									}
1770								});
1771								if (i > 14) {
1772									$('<div class="events-grid-cell-more">+ ' + (i - 14) + ' more ' + escapeHtml(self.settings.customEventLabel) + ' types</div>').appendTo(cellTypes);
1773								}
1774								var summary = dayData.length + ' ' + escapeHtml(self.settings.customEventsLabel) + ' at<br>';
1775								summary += locs.length > 1 ? locs.length + ' locations' : ' 1 location';
1776								cellContent.prepend(summary);
1777							}
1778						}
1779
1780					}
1781
1782				}
1783				self._scheduleHeightPost();
1784			});
1785		},
1786		_eraseCookie: function (name) {
1787			createCookie(name, "", -1);
1788		},
1789		_createCookie: function (name, value, days) {
1790			var expires = "";
1791			if (days) {
1792				var date = new Date();
1793				date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
1794				expires = "; expires=" + date.toGMTString();
1795			}
1796			document.cookie = name + "=" + value + expires + "; path=/";
1797		},
1798		_readCookie: function (name) {
1799			var nameEQ = name + "=";
1800			var ca = document.cookie.split(';');
1801			for (var i = 0; i < ca.length; i++) {
1802				var c = ca[i];
1803				while (c.charAt(0) == ' ') c = c.substring(1, c.length);
1804				if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
1805			}
1806			return null;
1807		},
1808		setView: function (view) {
1809			var self = this;
1810			view = (view === 'list' || view === 'grid') ? view : 'list';
1811			self._createCookie('eventsView', view, 365);
1812			$('.linkcolour', self.viewPicker).removeClass('linkcolour');
1813			$('[data-view="' + view + '"]', self.viewPicker).addClass('linkcolour');
1814			$('[data-view]', self.views).hide();
1815			$('[data-view="' + view + '"]', self.views).show();
1816
1817			if (self.settings.view == view) return;
1818			self.settings.view = view;
1819			self._update();
1820		},
1821		reload: function () {
1822			this._update();
1823		},
1824		_getContrastYIQ: function (hexcolor) {
1825			var r = parseInt(hexcolor.substr(1, 2), 16);
1826			var g = parseInt(hexcolor.substr(3, 2), 16);
1827			var b = parseInt(hexcolor.substr(5, 2), 16);
1828			var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
1829			return (yiq >= 128) ? 'black' : 'white';
1830		},
1831		_genColour: function (str) {
1832			for (var i = 0, hash = 0; i < str.length; hash = str.charCodeAt(i++) + ((hash << 5) - hash));
1833			for (var i = 0, colour = "#"; i < 3; colour += ("00" + ((hash >> i++ * 8) & 0xFF).toString(16)).slice(-2));
1834			return colour;
1835		},
1836		_genColumn: function (item, idx, targetEle, last) {
1837			var self = this;
1838			if (self.settings.filterCurrentColumn === null) {
1839				self.settings.filterCurrentColumn = $('<div style="float:left;width:250px;" />');
1840			}
1841
1842			var limit = (typeof this.settings.filterMaxItems !== "undefined") ? this.settings.filterMaxItems : 15;
1843			if (idx % limit === 0) {
1844				self.settings.filterCurrentColumn.appendTo(targetEle);
1845				if (idx > 0) {
1846					self.settings.filterCurrentColumn = $('<div style="float:left;width:250px;" />');
1847					self.settings.filterCurrentColumnCount++;
1848				}
1849			}
1850
1851			item.appendTo(self.settings.filterCurrentColumn);
1852
1853			if (last) {
1854				var colsMax = 250;
1855				self.settings.filterCurrentColumn.appendTo(targetEle);
1856				colsMax = (self.settings.filterCurrentColumnCount + 1) * 250;
1857				targetEle.css('max-width', colsMax.toString() + "px !important");
1858				self.settings.filterCurrentColumn = null;
1859				self.settings.filterCurrentColumnCount = 0;
1860			}
1861		},
1862		_loadFilters: function () {
1863			var self = this;
1864			$.getJSON(this.settings.apiServer + '/v1/' + self.settings.client + '/locations', function (json, textStatus) {
1865				self.locations = json;
1866
1867				$.each(json, function (index, val) {
1868					var lcName = val.name.toLowerCase().replace(/,/g, '');
1869					var checked = self.settings.locations.indexOf(lcName) >= 0 ? 'checked="checked"' : '';
1870					if (self.settings.excludedLocations.length > 0) {
1871						checked = self.settings.excludedLocations.indexOf(lcName) >= 0 ? '' : 'checked="checked"';
1872					}
1873					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>');
1874					self._genColumn(item, index, self.locationList, index === (json.length - 1));
1875				});
1876
1877				self.locationsLoaded = true;
1878				if (self.agesLoaded && self.locationsLoaded && self.tagsLoaded) self._update();
1879			}).error(function (jqXHR, textStatus, errorThrown) {
1880				console.log(errorThrown);
1881			});
1882
1883			$.getJSON(this.settings.apiServer + '/v1/' + self.settings.client + '/eventstags', function (json, textStatus) {
1884				if (self.settings.eventsGroupCalenderBAge) {
1885					self.tags = json;
1886
1887					$.each(json, function (index, val) {
1888						var lcName = val.name.toLowerCase();
1889						var checked = self.settings.types.indexOf(lcName) >= 0 ? 'checked="checked"' : '';
1890						if (self.settings.excludedTypes.length > 0) {
1891							checked = self.settings.excludedTypes.indexOf(lcName) >= 0 ? '' : 'checked="checked"';
1892						}
1893						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>');
1894						self._genColumn(item, index, self.typeList, index === (json.length - 1));
1895					});
1896				} else {
1897					self.tags = {};
1898
1899					$.each(json, function (index, val) {
1900						if (!val.colour) {
1901							val.colour = self._genColour(val.name);
1902						}
1903
1904						self.tags[val.name] = val.colour;
1905						var lcName = val.name.toLowerCase();
1906						var checked = '';
1907						if (self.settings.types.length > 0) {
1908							checked = self.settings.types.indexOf(lcName) >= 0 ? 'checked="checked"' : '';
1909						} else if (self.settings.excludedTypes.length > 0) {
1910							checked = self.settings.excludedTypes.indexOf(lcName) >= 0 ? '' : 'checked="checked"';
1911						} else {
1912							//checked = 'checked="checked"';
1913						}
1914						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>');
1915						self._genColumn(item, index, self.typeList, index === (json.length - 1));
1916					});
1917				}
1918
1919				self.tagsLoaded = true;
1920				if (self.agesLoaded && self.locationsLoaded && self.tagsLoaded) self._update();
1921			});
1922			$.getJSON(this.settings.apiServer + '/v1/' + self.settings.client + '/eventsages', function (json, textStatus) {
1923				if (self.settings.eventsGroupCalenderBAge) {
1924					self.ages = {};
1925
1926					$.each(json, function (index, val) {
1927						if (!val.colour) {
1928							val.colour = self._genColour(val.name);
1929						}
1930
1931						self.ages[val.name] = val.colour;
1932						var lcName = val.name.toLowerCase();
1933						var checked = self.settings.ages.indexOf(lcName) >= 0 ? 'checked="checked"' : '';
1934						if (self.settings.excludedAges.length > 0) {
1935							checked = self.settings.excludedAges.indexOf(lcName) >= 0 ? '' : 'checked="checked"';
1936						}
1937						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>');
1938						self._genColumn(item, index, self.ageList, index === (json.length - 1));
1939					});
1940				} else {
1941					self.ages = json;
1942
1943					$.each(json, function (index, val) {
1944						var lcName = val.name.toLowerCase();
1945						var checked = self.settings.ages.indexOf(lcName) >= 0 ? 'checked="checked"' : '';
1946						if (self.settings.excludedAges.length > 0) {
1947							checked = self.settings.excludedAges.indexOf(lcName) >= 0 ? '' : 'checked="checked"';
1948						}
1949						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>');
1950						self._genColumn(item, index, self.ageList, index === (json.length - 1));
1951					});
1952				}
1953				self.agesLoaded = true;
1954				if (self.agesLoaded && self.locationsLoaded && self.tagsLoaded) self._update();
1955			});
1956		}
1957	});
1958
1959	$.fn[pluginName] = function (options) {
1960		return this.each(function () {
1961			if (!$.data(this, "plugin_" + pluginName)) {
1962				$.data(this, "plugin_" + pluginName, new Plugin(this, options));
1963			}
1964		});
1965	};
1966})(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.