PageSourceSearch

https://www.housing-ombudsman.org.uk/app/themes/HousingOmbudsman_Theme/_assets/js/datepicker.js?ver=1791065519

js housing-ombudsman.org.uk collected 2026-10-03 22:13:12 UTC 41,273 bytes, 1,554 lines download raw bytes

1function dayTripper() {
2  $(".ui-datepicker-trigger").click(function () {
3    setTimeout(function () {
4      var today = $(".ui-datepicker-today a")[0];
5
6      if (!today) {
7        today = $(".ui-state-active")[0] || $(".ui-state-default")[0];
8      }
9
10      // Hide the entire page (except the date picker)
11      // from screen readers to prevent document navigation
12      // (by headings, etc.) while the popup is open
13      // $("main").attr("id", "dp-container");
14      // $("#dp-container").attr('aria-hidden','true');
15      //  $("#skipnav").attr("aria-hidden", "true");
16
17      // Hide the "today" button because it doesn't do what
18      // you think it supposed to do
19      $(".ui-datepicker-current").hide();
20
21      today.focus();
22      datePickHandler();
23      $(document).on(
24        "click",
25        "#ui-datepicker-div .ui-datepicker-close",
26        function () {
27          closeCalendar();
28        }
29      );
30    }, 0);
31  });
32}
33
34function datePickHandler() {
35  var activeDate;
36  var container = document.getElementById("ui-datepicker-div");
37  var input = document.getElementById("datepicker");
38
39  if (!container || !input) {
40    return;
41  }
42
43  // $(container).find('table').first().attr('role', 'grid');
44
45  container.setAttribute("role", "application");
46  container.setAttribute("aria-label", "Calendar view date-picker");
47
48  // the top controls:
49  var prev = $(".ui-datepicker-prev", container)[0],
50    next = $(".ui-datepicker-next", container)[0];
51
52  // This is the line that needs to be fixed for use on pages with base URL set in head
53  next.href = "javascript:void(0)";
54  prev.href = "javascript:void(0)";
55
56  next.setAttribute("role", "button");
57  next.removeAttribute("title");
58  prev.setAttribute("role", "button");
59  prev.removeAttribute("title");
60
61  appendOffscreenMonthText(next);
62  appendOffscreenMonthText(prev);
63
64  // delegation won't work here for whatever reason, so we are
65  // forced to attach individual click listeners to the prev /
66  // next month buttons each time they are added to the DOM
67  $(next).on("click", handleNextClicks);
68  $(prev).on("click", handlePrevClicks);
69
70  monthDayYearText();
71
72  $(container).on("keydown", function calendarKeyboardListener(keyVent) {
73    var which = keyVent.which;
74    var target = keyVent.target;
75    var dateCurrent = getCurrentDate(container);
76
77    if (!dateCurrent) {
78      dateCurrent = $("a.ui-state-default")[0];
79      setHighlightState(dateCurrent, container);
80    }
81
82    if (27 === which) {
83      keyVent.stopPropagation();
84      return closeCalendar();
85    } else if (which === 9 && keyVent.shiftKey) {
86      // SHIFT + TAB
87      keyVent.preventDefault();
88      if ($(target).hasClass("ui-datepicker-close")) {
89        // close button
90        $(".ui-datepicker-prev")[0].focus();
91      } else if ($(target).hasClass("ui-state-default")) {
92        // a date link
93        $(".ui-datepicker-close")[0].focus();
94      } else if ($(target).hasClass("ui-datepicker-prev")) {
95        // the prev link
96        $(".ui-datepicker-next")[0].focus();
97      } else if ($(target).hasClass("ui-datepicker-next")) {
98        // the next link
99        activeDate = $(".ui-state-highlight") || $(".ui-state-active")[0];
100        if (activeDate) {
101          activeDate.focus();
102        }
103      }
104    } else if (which === 9) {
105      // TAB
106      keyVent.preventDefault();
107      if ($(target).hasClass("ui-datepicker-close")) {
108        // close button
109        activeDate = $(".ui-state-highlight") || $(".ui-state-active")[0];
110        if (activeDate) {
111          activeDate.focus();
112        }
113      } else if ($(target).hasClass("ui-state-default")) {
114        $(".ui-datepicker-next")[0].focus();
115      } else if ($(target).hasClass("ui-datepicker-next")) {
116        $(".ui-datepicker-prev")[0].focus();
117      } else if ($(target).hasClass("ui-datepicker-prev")) {
118        $(".ui-datepicker-close")[0].focus();
119      }
120    } else if (which === 37) {
121      // LEFT arrow key
122      // if we're on a date link...
123      if (
124        !$(target).hasClass("ui-datepicker-close") &&
125        $(target).hasClass("ui-state-default")
126      ) {
127        keyVent.preventDefault();
128        previousDay(target);
129      }
130    } else if (which === 39) {
131      // RIGHT arrow key
132      // if we're on a date link...
133      if (
134        !$(target).hasClass("ui-datepicker-close") &&
135        $(target).hasClass("ui-state-default")
136      ) {
137        keyVent.preventDefault();
138        nextDay(target);
139      }
140    } else if (which === 38) {
141      // UP arrow key
142      if (
143        !$(target).hasClass("ui-datepicker-close") &&
144        $(target).hasClass("ui-state-default")
145      ) {
146        keyVent.preventDefault();
147        upHandler(target, container, prev);
148      }
149    } else if (which === 40) {
150      // DOWN arrow key
151      if (
152        !$(target).hasClass("ui-datepicker-close") &&
153        $(target).hasClass("ui-state-default")
154      ) {
155        keyVent.preventDefault();
156        downHandler(target, container, next);
157      }
158    } else if (which === 13) {
159      // ENTER
160      if ($(target).hasClass("ui-state-default")) {
161        setTimeout(function () {
162          closeCalendar();
163        }, 100);
164      } else if ($(target).hasClass("ui-datepicker-prev")) {
165        handlePrevClicks();
166      } else if ($(target).hasClass("ui-datepicker-next")) {
167        handleNextClicks();
168      }
169    } else if (32 === which) {
170      if (
171        $(target).hasClass("ui-datepicker-prev") ||
172        $(target).hasClass("ui-datepicker-next")
173      ) {
174        target.click();
175      }
176    } else if (33 === which) {
177      // PAGE UP
178      moveOneMonth(target, "prev");
179    } else if (34 === which) {
180      // PAGE DOWN
181      moveOneMonth(target, "next");
182    } else if (36 === which) {
183      // HOME
184      var firstOfMonth = $(target)
185        .closest("tbody")
186        .find(".ui-state-default")[0];
187      if (firstOfMonth) {
188        firstOfMonth.focus();
189        setHighlightState(firstOfMonth, $("#ui-datepicker-div")[0]);
190      }
191    } else if (35 === which) {
192      // END
193      var $daysOfMonth = $(target).closest("tbody").find(".ui-state-default");
194      var lastDay = $daysOfMonth[$daysOfMonth.length - 1];
195      if (lastDay) {
196        lastDay.focus();
197        setHighlightState(lastDay, $("#ui-datepicker-div")[0]);
198      }
199    }
200    $(".ui-datepicker-current").hide();
201  });
202}
203
204function closeCalendar() {
205  var container = $("#ui-datepicker-div");
206  $(container).off("keydown");
207  var input = $(".datepicker-access")[0];
208  $(input).datepicker("hide");
209
210  input.focus();
211}
212
213function removeAria() {
214  // make the rest of the page accessible again:
215  $("#dp-container").removeAttr("aria-hidden");
216  $("#skipnav").removeAttr("aria-hidden");
217}
218
219///////////////////////////////
220//////////////////////////// //
221///////////////////////// // //
222// UTILITY-LIKE THINGS // // //
223///////////////////////// // //
224//////////////////////////// //
225///////////////////////////////
226function isOdd(num) {
227  return num % 2;
228}
229
230function moveOneMonth(currentDate, dir) {
231  var button =
232    dir === "next" ? $(".ui-datepicker-next")[0] : $(".ui-datepicker-prev")[0];
233
234  if (!button) {
235    return;
236  }
237
238  var ENABLED_SELECTOR = "#ui-datepicker-div tbody td:not(.ui-state-disabled)";
239  var $currentCells = $(ENABLED_SELECTOR);
240  var currentIdx = $.inArray(currentDate.parentNode, $currentCells);
241
242  button.click();
243  setTimeout(function () {
244    updateHeaderElements();
245
246    var $newCells = $(ENABLED_SELECTOR);
247    var newTd = $newCells[currentIdx];
248    var newAnchor = newTd && $(newTd).find("a")[0];
249
250    while (!newAnchor) {
251      currentIdx--;
252      newTd = $newCells[currentIdx];
253      newAnchor = newTd && $(newTd).find("a")[0];
254    }
255
256    setHighlightState(newAnchor, $("#ui-datepicker-div")[0]);
257    newAnchor.focus();
258  }, 0);
259}
260
261function handleNextClicks() {
262  setTimeout(function () {
263    updateHeaderElements();
264    prepHighlightState();
265    $(".ui-datepicker-next").focus();
266    $(".ui-datepicker-current").hide();
267  }, 0);
268}
269
270function handlePrevClicks() {
271  setTimeout(function () {
272    updateHeaderElements();
273    prepHighlightState();
274    $(".ui-datepicker-prev").focus();
275    $(".ui-datepicker-current").hide();
276  }, 0);
277}
278
279function previousDay(dateLink) {
280  var container = document.getElementById("ui-datepicker-div");
281  if (!dateLink) {
282    return;
283  }
284  var td = $(dateLink).closest("td");
285  if (!td) {
286    return;
287  }
288
289  var prevTd = $(td).prev(),
290    prevDateLink = $("a.ui-state-default", prevTd)[0];
291
292  if (prevTd && prevDateLink) {
293    setHighlightState(prevDateLink, container);
294    prevDateLink.focus();
295  } else {
296    handlePrevious(dateLink);
297  }
298}
299
300function handlePrevious(target) {
301  var container = document.getElementById("ui-datepicker-div");
302  if (!target) {
303    return;
304  }
305  var currentRow = $(target).closest("tr");
306  if (!currentRow) {
307    return;
308  }
309  var previousRow = $(currentRow).prev();
310
311  if (!previousRow || previousRow.length === 0) {
312    // there is not previous row, so we go to previous month...
313    previousMonth();
314  } else {
315    var prevRowDates = $("td a.ui-state-default", previousRow);
316    var prevRowDate = prevRowDates[prevRowDates.length - 1];
317
318    if (prevRowDate) {
319      setTimeout(function () {
320        setHighlightState(prevRowDate, container);
321        prevRowDate.focus();
322      }, 0);
323    }
324  }
325}
326
327function previousMonth() {
328  var prevLink = $(".ui-datepicker-prev")[0];
329  var container = document.getElementById("ui-datepicker-div");
330  prevLink.click();
331  // focus last day of new month
332  setTimeout(function () {
333    var trs = $("tr", container),
334      lastRowTdLinks = $("td a.ui-state-default", trs[trs.length - 1]),
335      lastDate = lastRowTdLinks[lastRowTdLinks.length - 1];
336
337    // updating the cached header elements
338    updateHeaderElements();
339
340    setHighlightState(lastDate, container);
341    lastDate.focus();
342  }, 0);
343}
344
345///////////////// NEXT /////////////////
346/**
347 * Handles right arrow key navigation
348 * @param  {HTMLElement} dateLink The target of the keyboard event
349 */
350function nextDay(dateLink) {
351  var container = document.getElementById("ui-datepicker-div");
352  if (!dateLink) {
353    return;
354  }
355  var td = $(dateLink).closest("td");
356  if (!td) {
357    return;
358  }
359  var nextTd = $(td).next(),
360    nextDateLink = $("a.ui-state-default", nextTd)[0];
361
362  if (nextTd && nextDateLink) {
363    setHighlightState(nextDateLink, container);
364    nextDateLink.focus(); // the next day (same row)
365  } else {
366    handleNext(dateLink);
367  }
368}
369
370function handleNext(target) {
371  var container = document.getElementById("ui-datepicker-div");
372  if (!target) {
373    return;
374  }
375  var currentRow = $(target).closest("tr"),
376    nextRow = $(currentRow).next();
377
378  if (!nextRow || nextRow.length === 0) {
379    nextMonth();
380  } else {
381    var nextRowFirstDate = $("a.ui-state-default", nextRow)[0];
382    if (nextRowFirstDate) {
383      setHighlightState(nextRowFirstDate, container);
384      nextRowFirstDate.focus();
385    }
386  }
387}
388
389function nextMonth() {
390  nextMon = $(".ui-datepicker-next")[0];
391  var container = document.getElementById("ui-datepicker-div");
392  nextMon.click();
393  // focus the first day of the new month
394  setTimeout(function () {
395    // updating the cached header elements
396    updateHeaderElements();
397
398    var firstDate = $("a.ui-state-default", container)[0];
399    setHighlightState(firstDate, container);
400    firstDate.focus();
401  }, 0);
402}
403
404/////////// UP ///////////
405/**
406 * Handle the up arrow navigation through dates
407 * @param  {HTMLElement} target   The target of the keyboard event (day)
408 * @param  {HTMLElement} cont     The calendar container
409 * @param  {HTMLElement} prevLink Link to navigate to previous month
410 */
411function upHandler(target, cont, prevLink) {
412  prevLink = $(".ui-datepicker-prev")[0];
413  var rowContext = $(target).closest("tr");
414  if (!rowContext) {
415    return;
416  }
417  var rowTds = $("td", rowContext),
418    rowLinks = $("a.ui-state-default", rowContext),
419    targetIndex = $.inArray(target, rowLinks),
420    prevRow = $(rowContext).prev(),
421    prevRowTds = $("td", prevRow),
422    parallel = prevRowTds[targetIndex],
423    linkCheck = $("a.ui-state-default", parallel)[0];
424
425  if (prevRow && parallel && linkCheck) {
426    // there is a previous row, a td at the same index
427    // of the target AND theres a link in that td
428    setHighlightState(linkCheck, cont);
429    linkCheck.focus();
430  } else {
431    // we're either on the first row of a month, or we're on the
432    // second and there is not a date link directly above the target
433    prevLink.click();
434    setTimeout(function () {
435      // updating the cached header elements
436      updateHeaderElements();
437      var newRows = $("tr", cont),
438        lastRow = newRows[newRows.length - 1],
439        lastRowTds = $("td", lastRow),
440        tdParallelIndex = $.inArray(target.parentNode, rowTds),
441        newParallel = lastRowTds[tdParallelIndex],
442        newCheck = $("a.ui-state-default", newParallel)[0];
443
444      if (lastRow && newParallel && newCheck) {
445        setHighlightState(newCheck, cont);
446        newCheck.focus();
447      } else {
448        // theres no date link on the last week (row) of the new month
449        // meaning its an empty cell, so we'll try the 2nd to last week
450        var secondLastRow = newRows[newRows.length - 2],
451          secondTds = $("td", secondLastRow),
452          targetTd = secondTds[tdParallelIndex],
453          linkCheck = $("a.ui-state-default", targetTd)[0];
454
455        if (linkCheck) {
456          setHighlightState(linkCheck, cont);
457          linkCheck.focus();
458        }
459      }
460    }, 0);
461  }
462}
463
464//////////////// DOWN ////////////////
465/**
466 * Handles down arrow navigation through dates in calendar
467 * @param  {HTMLElement} target   The target of the keyboard event (day)
468 * @param  {HTMLElement} cont     The calendar container
469 * @param  {HTMLElement} nextLink Link to navigate to next month
470 */
471function downHandler(target, cont, nextLink) {
472  nextLink = $(".ui-datepicker-next")[0];
473  var targetRow = $(target).closest("tr");
474  if (!targetRow) {
475    return;
476  }
477  var targetCells = $("td", targetRow),
478    cellIndex = $.inArray(target.parentNode, targetCells), // the td (parent of target) index
479    nextRow = $(targetRow).next(),
480    nextRowCells = $("td", nextRow),
481    nextWeekTd = nextRowCells[cellIndex],
482    nextWeekCheck = $("a.ui-state-default", nextWeekTd)[0];
483
484  if (nextRow && nextWeekTd && nextWeekCheck) {
485    // theres a next row, a TD at the same index of `target`,
486    // and theres an anchor within that td
487    setHighlightState(nextWeekCheck, cont);
488    nextWeekCheck.focus();
489  } else {
490    nextLink.click();
491
492    setTimeout(function () {
493      // updating the cached header elements
494      updateHeaderElements();
495
496      var nextMonthTrs = $("tbody tr", cont),
497        firstTds = $("td", nextMonthTrs[0]),
498        firstParallel = firstTds[cellIndex],
499        firstCheck = $("a.ui-state-default", firstParallel)[0];
500
501      if (firstParallel && firstCheck) {
502        setHighlightState(firstCheck, cont);
503        firstCheck.focus();
504      } else {
505        // lets try the second row b/c we didnt find a
506        // date link in the first row at the target's index
507        var secondRow = nextMonthTrs[1],
508          secondTds = $("td", secondRow),
509          secondRowTd = secondTds[cellIndex],
510          secondCheck = $("a.ui-state-default", secondRowTd)[0];
511
512        if (secondRow && secondCheck) {
513          setHighlightState(secondCheck, cont);
514          secondCheck.focus();
515        }
516      }
517    }, 0);
518  }
519}
520
521function onCalendarHide() {
522  closeCalendar();
523}
524
525// add an aria-label to the date link indicating the currently focused date
526// (formatted identically to the required format: mm/dd/yyyy)
527function monthDayYearText() {
528  var cleanUps = $(".amaze-date");
529
530  $(cleanUps).each(function (clean) {
531    // each(cleanUps, function (clean) {
532    clean.parentNode.removeChild(clean);
533  });
534
535  var datePickDiv = document.getElementById("ui-datepicker-div");
536  // in case we find no datepick div
537  if (!datePickDiv) {
538    return;
539  }
540
541  var dates = $("a.ui-state-default", datePickDiv);
542  $(dates)
543    .attr("role", "button")
544    .on("keydown", function (e) {
545      if (e.which === 32) {
546        e.preventDefault();
547        e.target.click();
548        setTimeout(function () {
549          closeCalendar();
550        }, 100);
551      }
552    });
553  $(dates).each(function (index, date) {
554    var currentRow = $(date).closest("tr"),
555      currentTds = $("td", currentRow),
556      currentIndex = $.inArray(date.parentNode, currentTds),
557      headThs = $("thead tr th", datePickDiv),
558      dayIndex = headThs[currentIndex],
559      daySpan = $("span", dayIndex)[0],
560      monthName = $(".ui-datepicker-month", datePickDiv)[0].innerHTML,
561      year = $(".ui-datepicker-year", datePickDiv)[0].innerHTML,
562      number = date.innerHTML;
563
564    if (!daySpan || !monthName || !number || !year) {
565      return;
566    }
567
568    // AT Reads: {month} {date} {year} {day}
569    // "December 18 2014 Thursday"
570    var dateText =
571      date.innerHTML + " " + monthName + " " + year + " " + daySpan.title;
572    // AT Reads: {date(number)} {name of day} {name of month} {year(number)}
573    // var dateText = date.innerHTML + ' ' + daySpan.title + ' ' + monthName + ' ' + year;
574    // add an aria-label to the date link reading out the currently focused date
575    date.setAttribute("aria-label", dateText);
576  });
577}
578
579// update the cached header elements because we're in a new month or year
580function updateHeaderElements() {
581  var context = document.getElementById("ui-datepicker-div");
582  if (!context) {
583    return;
584  }
585
586  //  $(context).find('table').first().attr('role', 'grid');
587
588  prev = $(".ui-datepicker-prev", context)[0];
589  next = $(".ui-datepicker-next", context)[0];
590
591  //make them click/focus - able
592  next.href = "javascript:void(0)";
593  prev.href = "javascript:void(0)";
594
595  next.setAttribute("role", "button");
596  prev.setAttribute("role", "button");
597  appendOffscreenMonthText(next);
598  appendOffscreenMonthText(prev);
599
600  $(next).on("click", handleNextClicks);
601  $(prev).on("click", handlePrevClicks);
602
603  // add month day year text
604  monthDayYearText();
605}
606
607function prepHighlightState() {
608  var highlight;
609  var cage = document.getElementById("ui-datepicker-div");
610  highlight =
611    $(".ui-state-highlight", cage)[0] || $(".ui-state-default", cage)[0];
612  if (highlight && cage) {
613    setHighlightState(highlight, cage);
614  }
615}
616
617// Set the highlighted class to date elements, when focus is received
618function setHighlightState(newHighlight, container) {
619  var prevHighlight = getCurrentDate(container);
620  // remove the highlight state from previously
621  // highlighted date and add it to our newly active date
622  $(prevHighlight).removeClass("ui-state-highlight");
623  $(prevHighlight).removeAttr("aria-current");
624  $(newHighlight).attr("aria-current", "date");
625  $(newHighlight).addClass("ui-state-highlight");
626}
627
628// grabs the current date based on the highlight class
629function getCurrentDate(container) {
630  var currentDate = $(".ui-state-highlight", container)[0];
631  return currentDate;
632}
633
634/**
635 * Appends logical next/prev month text to the buttons
636 * - ex: Next Month, January 2015
637 *       Previous Month, November 2014
638 */
639function appendOffscreenMonthText(button) {
640  var buttonText;
641  var isNext = $(button).hasClass("ui-datepicker-next");
642  var months = [
643    "january",
644    "february",
645    "march",
646    "april",
647    "may",
648    "june",
649    "july",
650    "august",
651    "september",
652    "october",
653    "november",
654    "december",
655  ];
656
657  var currentMonth = $(".ui-datepicker-title .ui-datepicker-month")
658    .text()
659    .toLowerCase();
660  var monthIndex = $.inArray(currentMonth.toLowerCase(), months);
661  var currentYear = $(".ui-datepicker-title .ui-datepicker-year")
662    .text()
663    .toLowerCase();
664  var adjacentIndex = isNext ? monthIndex + 1 : monthIndex - 1;
665
666  if (isNext && currentMonth === "december") {
667    currentYear = parseInt(currentYear, 10) + 1;
668    adjacentIndex = 0;
669  } else if (!isNext && currentMonth === "january") {
670    currentYear = parseInt(currentYear, 10) - 1;
671    adjacentIndex = months.length - 1;
672  }
673
674  buttonText = isNext
675    ? "Next Month, " + firstToCap(months[adjacentIndex]) + " " + currentYear
676    : "Previous Month, " +
677      firstToCap(months[adjacentIndex]) +
678      " " +
679      currentYear;
680
681  $(button).find(".ui-icon").html(buttonText);
682}
683
684// Returns the string with the first letter capitalized
685function firstToCap(s) {
686  return s.charAt(0).toUpperCase() + s.slice(1);
687}
688
689/*
690 *   This content is licensed according to the W3C Software License at
691 *   https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
692 *
693 *   File:   DatePickerDialog.js
694 */
695
696("use strict");
697
698class DatePickerDialog {
699  constructor(cdp) {
700    this.buttonLabelChoose = "Choose Date";
701    this.buttonLabelChange = "Change Date";
702    this.dayLabels = [
703      "Sunday",
704      "Monday",
705      "Tuesday",
706      "Wednesday",
707      "Thursday",
708      "Friday",
709      "Saturday",
710    ];
711    this.monthLabels = [
712      "January",
713      "February",
714      "March",
715      "April",
716      "May",
717      "June",
718      "July",
719      "August",
720      "September",
721      "October",
722      "November",
723      "December",
724    ];
725
726    this.messageCursorKeys = "Cursor keys can navigate dates";
727    this.lastMessage = "";
728
729    this.textboxNode = cdp.querySelector('input[type="text"');
730    this.buttonNode = cdp.querySelector(".group button");
731    this.dialogNode = cdp.querySelector('[role="dialog"]');
732    this.messageNode = this.dialogNode.querySelector(".dialog-message");
733
734    this.monthYearNode = this.dialogNode.querySelector(".month-year");
735
736    this.prevYearNode = this.dialogNode.querySelector(".prev-year");
737    this.prevMonthNode = this.dialogNode.querySelector(".prev-month");
738    this.nextMonthNode = this.dialogNode.querySelector(".next-month");
739    this.nextYearNode = this.dialogNode.querySelector(".next-year");
740
741    this.okButtonNode = this.dialogNode.querySelector('button[value="ok"]');
742    this.cancelButtonNode = this.dialogNode.querySelector(
743      'button[value="cancel"]'
744    );
745
746    this.tbodyNode = this.dialogNode.querySelector("table.dates tbody");
747
748    this.lastRowNode = null;
749
750    this.days = [];
751
752    this.focusDay = new Date();
753    this.selectedDay = new Date(0, 0, 1);
754
755    this.lastDate = -1;
756
757    this.isMouseDownOnBackground = false;
758
759    this.textboxNode.addEventListener(
760      "blur",
761      this.setDateForButtonLabel.bind(this)
762    );
763
764    this.buttonNode.addEventListener(
765      "keydown",
766      this.handleButtonKeydown.bind(this)
767    );
768    this.buttonNode.addEventListener(
769      "click",
770      this.handleButtonClick.bind(this)
771    );
772
773    this.okButtonNode.addEventListener("click", this.handleOkButton.bind(this));
774    this.okButtonNode.addEventListener(
775      "keydown",
776      this.handleOkButton.bind(this)
777    );
778
779    this.cancelButtonNode.addEventListener(
780      "click",
781      this.handleCancelButton.bind(this)
782    );
783    this.cancelButtonNode.addEventListener(
784      "keydown",
785      this.handleCancelButton.bind(this)
786    );
787
788    this.prevMonthNode.addEventListener(
789      "click",
790      this.handlePreviousMonthButton.bind(this)
791    );
792    this.nextMonthNode.addEventListener(
793      "click",
794      this.handleNextMonthButton.bind(this)
795    );
796    this.prevYearNode.addEventListener(
797      "click",
798      this.handlePreviousYearButton.bind(this)
799    );
800    this.nextYearNode.addEventListener(
801      "click",
802      this.handleNextYearButton.bind(this)
803    );
804
805    this.prevMonthNode.addEventListener(
806      "keydown",
807      this.handlePreviousMonthButton.bind(this)
808    );
809    this.nextMonthNode.addEventListener(
810      "keydown",
811      this.handleNextMonthButton.bind(this)
812    );
813    this.prevYearNode.addEventListener(
814      "keydown",
815      this.handlePreviousYearButton.bind(this)
816    );
817    this.nextYearNode.addEventListener(
818      "keydown",
819      this.handleNextYearButton.bind(this)
820    );
821
822    document.body.addEventListener(
823      "pointerup",
824      this.handleBackgroundMouseUp.bind(this),
825      true
826    );
827
828    // Create Grid of Dates
829
830    this.tbodyNode.innerHTML = "";
831    for (let i = 0; i < 6; i++) {
832      const row = this.tbodyNode.insertRow(i);
833      this.lastRowNode = row;
834      for (let j = 0; j < 7; j++) {
835        const cell = document.createElement("td");
836
837        cell.tabIndex = -1;
838        cell.addEventListener("click", this.handleDayClick.bind(this));
839        cell.addEventListener("keydown", this.handleDayKeyDown.bind(this));
840        cell.addEventListener("focus", this.handleDayFocus.bind(this));
841
842        cell.textContent = "-1";
843
844        row.appendChild(cell);
845        this.days.push(cell);
846      }
847    }
848
849    this.updateGrid();
850    this.close(false);
851    this.setDateForButtonLabel();
852  }
853
854  isSameDay(day1, day2) {
855    return (
856      day1.getFullYear() == day2.getFullYear() &&
857      day1.getMonth() == day2.getMonth() &&
858      day1.getDate() == day2.getDate()
859    );
860  }
861
862  isNotSameMonth(day1, day2) {
863    return (
864      day1.getFullYear() != day2.getFullYear() ||
865      day1.getMonth() != day2.getMonth()
866    );
867  }
868
869  updateGrid() {
870    const fd = this.focusDay;
871
872    this.monthYearNode.textContent =
873      this.monthLabels[fd.getMonth()] + " " + fd.getFullYear();
874
875    let firstDayOfMonth = new Date(fd.getFullYear(), fd.getMonth(), 1);
876    let dayOfWeek = firstDayOfMonth.getDay();
877
878    firstDayOfMonth.setDate(firstDayOfMonth.getDate() - dayOfWeek);
879
880    const d = new Date(firstDayOfMonth);
881
882    for (let i = 0; i < this.days.length; i++) {
883      const flag = d.getMonth() != fd.getMonth();
884      this.updateDate(
885        this.days[i],
886        flag,
887        d,
888        this.isSameDay(d, this.selectedDay)
889      );
890      d.setDate(d.getDate() + 1);
891
892      // Hide last row if all dates are disabled (e.g. in next month)
893      if (i === 35) {
894        if (flag) {
895          this.lastRowNode.style.visibility = "hidden";
896        } else {
897          this.lastRowNode.style.visibility = "visible";
898        }
899      }
900    }
901  }
902
903  updateDate(domNode, disable, day, selected) {
904    let d = day.getDate().toString();
905    if (day.getDate() <= 9) {
906      d = "0" + d;
907    }
908
909    let m = day.getMonth() + 1;
910    if (day.getMonth() < 9) {
911      m = "0" + m;
912    }
913
914    domNode.tabIndex = -1;
915    domNode.removeAttribute("aria-selected");
916    domNode.setAttribute("data-date", day.getFullYear() + "-" + m + "-" + d);
917
918    if (disable) {
919      domNode.classList.add("disabled");
920      domNode.textContent = "";
921    } else {
922      domNode.classList.remove("disabled");
923      domNode.textContent = day.getDate();
924      if (selected) {
925        domNode.setAttribute("aria-selected", "true");
926        domNode.tabIndex = 0;
927      }
928    }
929  }
930
931  moveFocusToDay(day) {
932    const d = this.focusDay;
933
934    this.focusDay = day;
935
936    if (
937      d.getMonth() != this.focusDay.getMonth() ||
938      d.getFullYear() != this.focusDay.getFullYear()
939    ) {
940      this.updateGrid();
941    }
942    this.setFocusDay();
943  }
944
945  setFocusDay(flag) {
946    if (typeof flag !== "boolean") {
947      flag = true;
948    }
949
950    for (let i = 0; i < this.days.length; i++) {
951      const dayNode = this.days[i];
952      const day = this.getDayFromDataDateAttribute(dayNode);
953
954      dayNode.tabIndex = -1;
955      if (this.isSameDay(day, this.focusDay)) {
956        dayNode.tabIndex = 0;
957        if (flag) {
958          dayNode.focus();
959        }
960      }
961    }
962  }
963
964  open() {
965    this.dialogNode.style.display = "block";
966    this.dialogNode.style.zIndex = 2;
967
968    this.getDateFromTextbox();
969    this.updateGrid();
970    this.lastDate = this.focusDay.getDate();
971  }
972
973  isOpen() {
974    return window.getComputedStyle(this.dialogNode).display !== "none";
975  }
976
977  close(flag) {
978    if (typeof flag !== "boolean") {
979      // Default is to move focus to combobox
980      flag = true;
981    }
982
983    this.setMessage("");
984    this.dialogNode.style.display = "none";
985
986    if (flag) {
987      this.buttonNode.focus();
988    }
989  }
990
991  changeMonth(currentDate, numMonths) {
992    const getDays = (year, month) => new Date(year, month, 0).getDate();
993
994    const isPrev = numMonths < 0;
995    const numYears = Math.trunc(Math.abs(numMonths) / 12);
996    numMonths = Math.abs(numMonths) % 12;
997
998    const newYear = isPrev
999      ? currentDate.getFullYear() - numYears
1000      : currentDate.getFullYear() + numYears;
1001
1002    const newMonth = isPrev
1003      ? currentDate.getMonth() - numMonths
1004      : currentDate.getMonth() + numMonths;
1005
1006    const newDate = new Date(newYear, newMonth, 1);
1007
1008    const daysInMonth = getDays(newDate.getFullYear(), newDate.getMonth() + 1);
1009
1010    // If lastDat is not initialized set to current date
1011    this.lastDate = this.lastDate ? this.lastDate : currentDate.getDate();
1012
1013    if (this.lastDate > daysInMonth) {
1014      newDate.setDate(daysInMonth);
1015    } else {
1016      newDate.setDate(this.lastDate);
1017    }
1018
1019    return newDate;
1020  }
1021
1022  moveToNextYear() {
1023    this.focusDay = this.changeMonth(this.focusDay, 12);
1024    this.updateGrid();
1025  }
1026
1027  moveToPreviousYear() {
1028    this.focusDay = this.changeMonth(this.focusDay, -12);
1029    this.updateGrid();
1030  }
1031
1032  moveToNextMonth() {
1033    this.focusDay = this.changeMonth(this.focusDay, 1);
1034    this.updateGrid();
1035  }
1036
1037  moveToPreviousMonth() {
1038    this.focusDay = this.changeMonth(this.focusDay, -1);
1039    this.updateGrid();
1040  }
1041
1042  moveFocusToNextDay() {
1043    const d = new Date(this.focusDay);
1044    d.setDate(d.getDate() + 1);
1045    this.lastDate = d.getDate();
1046    this.moveFocusToDay(d);
1047  }
1048
1049  moveFocusToNextWeek() {
1050    const d = new Date(this.focusDay);
1051    d.setDate(d.getDate() + 7);
1052    this.lastDate = d.getDate();
1053    this.moveFocusToDay(d);
1054  }
1055
1056  moveFocusToPreviousDay() {
1057    const d = new Date(this.focusDay);
1058    d.setDate(d.getDate() - 1);
1059    this.lastDate = d.getDate();
1060    this.moveFocusToDay(d);
1061  }
1062
1063  moveFocusToPreviousWeek() {
1064    const d = new Date(this.focusDay);
1065    d.setDate(d.getDate() - 7);
1066    this.lastDate = d.getDate();
1067    this.moveFocusToDay(d);
1068  }
1069
1070  moveFocusToFirstDayOfWeek() {
1071    const d = new Date(this.focusDay);
1072    d.setDate(d.getDate() - d.getDay());
1073    this.lastDate = d.getDate();
1074    this.moveFocusToDay(d);
1075  }
1076
1077  moveFocusToLastDayOfWeek() {
1078    const d = new Date(this.focusDay);
1079    d.setDate(d.getDate() + (6 - d.getDay()));
1080    this.lastDate = d.getDate();
1081    this.moveFocusToDay(d);
1082  }
1083
1084  // Day methods
1085
1086  isDayDisabled(domNode) {
1087    return domNode.classList.contains("disabled");
1088  }
1089
1090  getDayFromDataDateAttribute(domNode) {
1091    const parts = domNode.getAttribute("data-date").split("-");
1092    return new Date(parts[0], parseInt(parts[1]) - 1, parts[2]);
1093  }
1094
1095  // Textbox methods
1096
1097  setTextboxDate(domNode) {
1098    let d = this.focusDay;
1099
1100    if (domNode) {
1101      d = this.getDayFromDataDateAttribute(domNode);
1102      // updated aria-selected
1103      this.days.forEach((day) =>
1104        day === domNode
1105          ? day.setAttribute("aria-selected", "true")
1106          : day.removeAttribute("aria-selected")
1107      );
1108    }
1109
1110    this.textboxNode.value =
1111      d.getMonth() + 1 + "/" + d.getDate() + "/" + d.getFullYear();
1112    this.setDateForButtonLabel();
1113  }
1114
1115  getDateFromTextbox() {
1116    const parts = this.textboxNode.value.split("/");
1117    const month = parseInt(parts[0]);
1118    const day = parseInt(parts[1]);
1119    let year = parseInt(parts[2]);
1120
1121    if (
1122      parts.length === 3 &&
1123      Number.isInteger(month) &&
1124      Number.isInteger(day) &&
1125      Number.isInteger(year)
1126    ) {
1127      if (year < 100) {
1128        year = 2000 + year;
1129      }
1130      this.focusDay = new Date(year, month - 1, day);
1131      this.selectedDay = new Date(this.focusDay);
1132    } else {
1133      // If not a valid date (MM/DD/YY) initialize with todays date
1134      this.focusDay = new Date();
1135      this.selectedDay = new Date(0, 0, 1);
1136    }
1137  }
1138
1139  setDateForButtonLabel() {
1140    const parts = this.textboxNode.value.split("/");
1141
1142    if (
1143      parts.length === 3 &&
1144      Number.isInteger(parseInt(parts[0])) &&
1145      Number.isInteger(parseInt(parts[1])) &&
1146      Number.isInteger(parseInt(parts[2]))
1147    ) {
1148      const day = new Date(
1149        parseInt(parts[2]),
1150        parseInt(parts[0]) - 1,
1151        parseInt(parts[1])
1152      );
1153
1154      let label = this.buttonLabelChange;
1155      label += ", " + this.dayLabels[day.getDay()];
1156      label += " " + this.monthLabels[day.getMonth()];
1157      label += " " + day.getDate();
1158      label += ", " + day.getFullYear();
1159      this.buttonNode.setAttribute("aria-label", label);
1160    } else {
1161      // If not a valid date, initialize with "Choose Date"
1162      this.buttonNode.setAttribute("aria-label", this.buttonLabelChoose);
1163    }
1164  }
1165
1166  setMessage(str) {
1167    function setMessageDelayed() {
1168      this.messageNode.textContent = str;
1169    }
1170
1171    if (str !== this.lastMessage) {
1172      setTimeout(setMessageDelayed.bind(this), 200);
1173      this.lastMessage = str;
1174    }
1175  }
1176
1177  // Event handlers
1178
1179  handleOkButton(event) {
1180    let flag = false;
1181
1182    switch (event.type) {
1183      case "keydown":
1184        switch (event.key) {
1185          case "Tab":
1186            if (!event.shiftKey) {
1187              this.prevYearNode.focus();
1188              flag = true;
1189            }
1190            break;
1191
1192          case "Esc":
1193          case "Escape":
1194            this.close();
1195            flag = true;
1196            break;
1197
1198          default:
1199            break;
1200        }
1201        break;
1202
1203      case "click":
1204        this.setTextboxDate();
1205        this.close();
1206        flag = true;
1207        break;
1208
1209      default:
1210        break;
1211    }
1212
1213    if (flag) {
1214      event.stopPropagation();
1215      event.preventDefault();
1216    }
1217  }
1218
1219  handleCancelButton(event) {
1220    let flag = false;
1221
1222    switch (event.type) {
1223      case "keydown":
1224        switch (event.key) {
1225          case "Esc":
1226          case "Escape":
1227            this.close();
1228            flag = true;
1229            break;
1230
1231          default:
1232            break;
1233        }
1234        break;
1235
1236      case "click":
1237        this.close();
1238        flag = true;
1239        break;
1240
1241      default:
1242        break;
1243    }
1244
1245    if (flag) {
1246      event.stopPropagation();
1247      event.preventDefault();
1248    }
1249  }
1250
1251  handleNextYearButton(event) {
1252    let flag = false;
1253
1254    switch (event.type) {
1255      case "keydown":
1256        switch (event.key) {
1257          case "Esc":
1258          case "Escape":
1259            this.close();
1260            flag = true;
1261            break;
1262
1263          case "Enter":
1264            this.moveToNextYear();
1265            this.setFocusDay(false);
1266            flag = true;
1267            break;
1268        }
1269
1270        break;
1271
1272      case "click":
1273        this.moveToNextYear();
1274        this.setFocusDay(false);
1275        break;
1276
1277      default:
1278        break;
1279    }
1280
1281    if (flag) {
1282      event.stopPropagation();
1283      event.preventDefault();
1284    }
1285  }
1286
1287  handlePreviousYearButton(event) {
1288    let flag = false;
1289
1290    switch (event.type) {
1291      case "keydown":
1292        switch (event.key) {
1293          case "Enter":
1294            this.moveToPreviousYear();
1295            this.setFocusDay(false);
1296            flag = true;
1297            break;
1298
1299          case "Tab":
1300            if (event.shiftKey) {
1301              this.okButtonNode.focus();
1302              flag = true;
1303            }
1304            break;
1305
1306          case "Esc":
1307          case "Escape":
1308            this.close();
1309            flag = true;
1310            break;
1311
1312          default:
1313            break;
1314        }
1315
1316        break;
1317
1318      case "click":
1319        this.moveToPreviousYear();
1320        this.setFocusDay(false);
1321        break;
1322
1323      default:
1324        break;
1325    }
1326
1327    if (flag) {
1328      event.stopPropagation();
1329      event.preventDefault();
1330    }
1331  }
1332
1333  handleNextMonthButton(event) {
1334    let flag = false;
1335
1336    switch (event.type) {
1337      case "keydown":
1338        switch (event.key) {
1339          case "Esc":
1340          case "Escape":
1341            this.close();
1342            flag = true;
1343            break;
1344
1345          case "Enter":
1346            this.moveToNextMonth();
1347            this.setFocusDay(false);
1348            flag = true;
1349            break;
1350        }
1351
1352        break;
1353
1354      case "click":
1355        this.moveToNextMonth();
1356        this.setFocusDay(false);
1357        break;
1358
1359      default:
1360        break;
1361    }
1362
1363    if (flag) {
1364      event.stopPropagation();
1365      event.preventDefault();
1366    }
1367  }
1368
1369  handlePreviousMonthButton(event) {
1370    let flag = false;
1371
1372    switch (event.type) {
1373      case "keydown":
1374        switch (event.key) {
1375          case "Esc":
1376          case "Escape":
1377            this.close();
1378            flag = true;
1379            break;
1380
1381          case "Enter":
1382            this.moveToPreviousMonth();
1383            this.setFocusDay(false);
1384            flag = true;
1385            break;
1386        }
1387
1388        break;
1389
1390      case "click":
1391        this.moveToPreviousMonth();
1392        this.setFocusDay(false);
1393        flag = true;
1394        break;
1395
1396      default:
1397        break;
1398    }
1399
1400    if (flag) {
1401      event.stopPropagation();
1402      event.preventDefault();
1403    }
1404  }
1405
1406  handleDayKeyDown(event) {
1407    let flag = false;
1408
1409    switch (event.key) {
1410      case "Esc":
1411      case "Escape":
1412        this.close();
1413        break;
1414
1415      case " ":
1416        this.setTextboxDate(event.currentTarget);
1417        flag = true;
1418        break;
1419
1420      case "Enter":
1421        this.setTextboxDate(event.currentTarget);
1422        this.close();
1423        flag = true;
1424        break;
1425
1426      case "Tab":
1427        this.cancelButtonNode.focus();
1428        if (event.shiftKey) {
1429          this.nextYearNode.focus();
1430        }
1431        this.setMessage("");
1432        flag = true;
1433        break;
1434
1435      case "Right":
1436      case "ArrowRight":
1437        this.moveFocusToNextDay();
1438        flag = true;
1439        break;
1440
1441      case "Left":
1442      case "ArrowLeft":
1443        this.moveFocusToPreviousDay();
1444        flag = true;
1445        break;
1446
1447      case "Down":
1448      case "ArrowDown":
1449        this.moveFocusToNextWeek();
1450        flag = true;
1451        break;
1452
1453      case "Up":
1454      case "ArrowUp":
1455        this.moveFocusToPreviousWeek();
1456        flag = true;
1457        break;
1458
1459      case "PageUp":
1460        if (event.shiftKey) {
1461          this.moveToPreviousYear();
1462        } else {
1463          this.moveToPreviousMonth();
1464        }
1465        this.setFocusDay();
1466        flag = true;
1467        break;
1468
1469      case "PageDown":
1470        if (event.shiftKey) {
1471          this.moveToNextYear();
1472        } else {
1473          this.moveToNextMonth();
1474        }
1475        this.setFocusDay();
1476        flag = true;
1477        break;
1478
1479      case "Home":
1480        this.moveFocusToFirstDayOfWeek();
1481        flag = true;
1482        break;
1483
1484      case "End":
1485        this.moveFocusToLastDayOfWeek();
1486        flag = true;
1487        break;
1488    }
1489
1490    if (flag) {
1491      event.stopPropagation();
1492      event.preventDefault();
1493    }
1494  }
1495
1496  handleDayClick(event) {
1497    if (!this.isDayDisabled(event.currentTarget) && event.which !== 3) {
1498      this.setTextboxDate(event.currentTarget);
1499      this.close();
1500    }
1501
1502    event.stopPropagation();
1503    event.preventDefault();
1504  }
1505
1506  handleDayFocus() {
1507    this.setMessage(this.messageCursorKeys);
1508  }
1509
1510  handleButtonKeydown(event) {
1511    if (event.key === "Enter" || event.key === " ") {
1512      this.open();
1513      this.setFocusDay();
1514
1515      event.stopPropagation();
1516      event.preventDefault();
1517    }
1518  }
1519
1520  handleButtonClick(event) {
1521    if (this.isOpen()) {
1522      this.close();
1523    } else {
1524      this.open();
1525      this.setFocusDay();
1526    }
1527
1528    event.stopPropagation();
1529    event.preventDefault();
1530  }
1531
1532  handleBackgroundMouseUp(event) {
1533    if (
1534      !this.buttonNode.contains(event.target) &&
1535      !this.dialogNode.contains(event.target)
1536    ) {
1537      if (this.isOpen()) {
1538        this.close(false);
1539        event.stopPropagation();
1540        event.preventDefault();
1541      }
1542    }
1543  }
1544}
1545
1546// Initialize menu button date picker
1547
1548window.addEventListener("load", function () {
1549  const datePickers = document.querySelectorAll(".datepicker-access");
1550
1551  datePickers.forEach(function (dp) {
1552    new DatePickerDialog(dp);
1553  });
1554});

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.