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.