PageSourceSearch

https://beverlyhills.org/Areas/Calendar/Assets/Script/miniCalendar.js

js beverlyhills.org collected 2026-09-24 15:25:17 UTC 7,232 bytes, 231 lines download raw bytes

1var MiniCalendar = function () {
2	var self = this;
3	var $calendar = null;
4	var $nextButton = null;
5	var $prevButton = null;
6	var widgetId = null;
7	var contentContainerID = null;
8	var currentMonth = null;
9	var todayMonth = null;
10	var preventNavigationOnArrows;
11	var urlChangeMonth = '/Calendar/WidgetGet/ChangeMiniCalendarMonth';
12	var urlUpdateList = '/Calendar/WidgetGet/UpdateList';
13
14	function announceMonthChange(monthDate) {
15		var monthName = monthDate.toLocaleString('default', { month: 'long', year: 'numeric' });
16		var $announcement = $calendar.find('#miniCalendarAnnouncement' + widgetId);
17		// Clear then set to ensure aria-live fires even if the same month is somehow set
18		$announcement.text('');
19		setTimeout(function () {
20			$announcement.text(monthName);
21		}, 50);
22	}
23
24	function restoreFocusAfterMonthChange(navigatedForward) {
25		// Determine which button should receive focus based on whether prev is now disabled
26		if (isCurrentMonthTodayMonth()) {
27			// Prev is disabled — focus next
28			$nextButton.focus();
29		} else if (navigatedForward) {
30			$nextButton.focus();
31		} else {
32			$prevButton.focus();
33		}
34	}
35
36	function moveMonth(monthDate, navigatedForward) {
37		var miniCalendarResult;
38		var eventListResult;
39		var async1 = $.ajax({
40			type: 'POST',
41			url: urlChangeMonth,
42			data: JSON.stringify({
43				"month": monthDate.getMonth() + 1,
44				"year": monthDate.getFullYear(),
45				"pageID": $('#pagePageID').val(),
46				"widgetID": widgetId,
47				"contentContainerID": contentContainerID,
48				"moduleID": $('#pageModuleID').val()
49			}),
50			contentType: 'application/json',
51			cache: false,
52			async: true,
53			success: function (response) {
54				miniCalendarResult = response;
55			},
56			error: function (xhr, textStatus, exception) {
57				alert('Error: ' + xhr.statusText + '\nStatus: ' + xhr.status);
58			}
59		});
60		var async2 = $.ajax({
61			type: 'POST',
62			url: urlUpdateList,
63			data: JSON.stringify({
64				"month": monthDate.getMonth() + 1,
65				"year": monthDate.getFullYear(),
66				"pageID": $('#pagePageID').val(),
67				"widgetID": widgetId,
68				"contentContainerID": contentContainerID,
69				"moduleID": $('#pageModuleID').val()
70			}),
71			contentType: 'application/json',
72			cache: false,
73			async: true,
74			success: function (response) {
75				eventListResult = response;
76			},
77			error: function (xhr, textStatus, exception) {
78				alert('Error: ' + xhr.statusText + '\nStatus: ' + xhr.status);
79			}
80		});
81		$.when(async1, async2).done(function () {
82			currentMonth = monthDate;
83			$calendar.find('div[data-minicalendar]').replaceWith(miniCalendarResult);
84			initButtons();
85			$calendar.find('div.row').html(eventListResult);
86			// Announce the new month to screen readers
87			announceMonthChange(monthDate);
88			// Restore focus to the correct button (WCAG 2.4.3)
89			restoreFocusAfterMonthChange(navigatedForward);
90		});
91	};
92
93	function initButtons() {
94		$nextButton = $calendar.find("button.cp-miniCalendar-arrow--right");
95		$nextButton.click(function (e) {
96			if (preventNavigationOnArrows) {
97				e.preventDefault();
98			} else {
99				self.moveNextMonth();
100			}
101		});
102		$prevButton = $calendar.find("button.cp-miniCalendar-arrow--left");
103		if (isCurrentMonthTodayMonth()) {
104			$prevButton.prop("disabled", true);
105		} else {
106			$prevButton.click(function () {
107				if (preventNavigationOnArrows) {
108					e.preventDefault();
109				} else {
110					self.movePrevMonth();
111				}
112			});
113		}
114	}
115
116	function isCurrentMonthTodayMonth() {
117		return (todayMonth.getMonth() === currentMonth.getMonth() && todayMonth.getFullYear() === currentMonth.getFullYear());
118	}
119
120	this.moveNextMonth = function () {
121		var nextMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1);
122		moveMonth(nextMonth, true);
123	};
124
125	this.movePrevMonth = function () {
126		if (!isCurrentMonthTodayMonth()) {
127			var prevMonth = new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1);
128			moveMonth(prevMonth, false);
129		}
130	};
131
132	const focusCell = (event, current, focusables, index) => {
133		event.preventDefault();
134		if (index >= 0 && index < focusables.length) {
135			const next = focusables[index];
136			next.focus();
137			return true
138		}
139		return false;
140	}
141
142	const itemPos = (item)=> {
143		const x = item.parentNode.cellIndex;
144		const y = item.parentNode.parentNode.rowIndex - 1;
145		return [x, y];
146	}
147
148	const processKeydown = (e) => {
149		// handle key events when focus is on a calendar day link.
150		const currentFocused = e.target.closest("a");
151		if (!currentFocused) {
152			return;
153		}
154
155		if (e.key === "Tab") {
156			return;
157		}
158		const widget = e.currentTarget;
159		const tbody = widget.querySelector('.miniCalendar tbody');
160		const focusables = [...tbody.querySelectorAll("a")];
161		const currentIdx = focusables.indexOf(currentFocused);
162		if (currentIdx < 0) {
163			return;
164		}
165		const [currentCellIdx, currentRowIdx] = itemPos(currentFocused);
166		switch (e.key) {
167			case "ArrowRight":
168				focusCell(e, currentFocused, focusables, currentIdx + 1)
169				break;
170			case "ArrowLeft":
171				focusCell(e, currentFocused, focusables, currentIdx - 1)
172				break;
173			case "ArrowDown":
174				const nextSameCellIdx = focusables.findIndex((item, idx) => {
175					const [x, _] = itemPos(item);
176					return idx > currentIdx && x === currentCellIdx;
177				});
178				focusCell(e, currentFocused, focusables, nextSameCellIdx);
179				break;
180			case "ArrowUp":
181				const prevSameCellIdx = focusables.findLastIndex((item, idx) => {
182					const [x, _] = itemPos(item);
183					return idx < currentIdx && x === currentCellIdx;
184				});
185				focusCell(e, currentFocused, focusables, prevSameCellIdx);
186				break;
187			case "End":
188				if (e.ctrlKey) {
189					focusCell(e, currentFocused, focusables, focusables.length - 1)
190					break;
191				} else {
192					const lastInSameRow = focusables.findLastIndex((item, idx) => {
193						const [_, y] = itemPos(item);
194						return idx > currentIdx && y === currentRowIdx;
195					});
196					focusCell(e, currentFocused, focusables, lastInSameRow);
197					break;
198				}
199			case "Home":
200				if (e.ctrlKey) {
201					focusCell(e, currentFocused, focusables, 0)
202					break;
203				} else {
204					const firstInSameRow = focusables.findIndex((item, idx) => {
205						const [_, y] = itemPos(item);
206						return idx < currentIdx && y === currentRowIdx;
207					});
208					focusCell(e, currentFocused, focusables, firstInSameRow);
209					break;
210				}
211		}
212	};
213
214
215	this.init = function (options) {
216		widgetId = options.widgetId;
217		preventNavigationOnArrows = options.preventNavigationOnArrows;
218		$calendar = $("#widgetCalendar" + widgetId);
219		$calendar[0].addEventListener("keydown", processKeydown);
220		var $pageContent = $calendar.parents('.pageContent').filter(":first");
221		contentContainerID = $pageContent.data('containerid');
222		var $monthToRender = $calendar.find('input[name="monthToRender"]');
223		var month = parseInt($monthToRender.data("month"), 10);
224		var year = parseInt($monthToRender.data("year"), 10);
225		currentMonth = new Date(year, month - 1, 1);
226		var now = new Date();
227		todayMonth = new Date(now.getFullYear(), now.getMonth(), 1);
228		initButtons();
229	};
230
231};

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.