Skip to content

Commit

Permalink
First pass at making Pikaday accessibile (Pikaday#522)
Browse files Browse the repository at this point in the history
* Added basic aria support

* Added keyboard support

* Removed redundant code

* Extracted logic to function

* Extracted logic to function

* Removed redundant code after extracting the logic

* Fixed function calling

* Fixed deprecated methods of moment

* Merged upstream changes

* Update README.md

* No need for `aria-hidden`

This attribute isn't needed since when the picker is hidden it has
`display: none`. Screen readers will ignore anything with `display:
none` css on it.

* Improve pikaday accessibility
  • Loading branch information
Robdel12 authored and rikkert committed May 20, 2016
1 parent a64b4cf commit 3f4b9c9
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 22 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Pikaday

![Pikaday Screenshot][screenshot]

**Production ready?** Since version 1.0.0 Pikaday is stable and used in production. If you do however find bugs or have feature requests please submit them to the [GitHub issue tracker][issues].
**Production ready?** Since version 1.0.0 Pikaday is stable and used in production. If you do however find bugs or have feature requests please submit them to the [GitHub issue tracker][issues].
Also see the [changelog](CHANGELOG.md)


Expand Down Expand Up @@ -50,7 +50,7 @@ var picker = new Pikaday({
field.parentNode.insertBefore(picker.el, field.nextSibling);
```

For advanced formatting load [Moment.js][moment] prior to Pikaday:
For advanced formatting load [Moment.js][moment] prior to Pikaday:
See the [moment.js example][] for a full version.

```html
Expand Down Expand Up @@ -79,7 +79,7 @@ Pikaday has many useful options:
* `bound` automatically show/hide the datepicker on `field` focus (default `true` if `field` is set)
* `position` preferred position of the datepicker relative to the form field, e.g.: `top right`, `bottom right` **Note:** automatic adjustment may occur to avoid datepicker from being displayed outside the viewport, see [positions example][] (default to 'bottom left')
* `reposition` can be set to false to not reposition datepicker within the viewport, forcing it to take the configured `position` (default: true)
* `container` DOM node to render calendar into, see [container example][] (default: undefined)
* `container` DOM node to render calendar into, see [container example][] (default: undefined)
* `format` the default output format for `.toString()` and `field` value (requires [Moment.js][moment] for custom formatting)
* `formatStrict` the default flag for moment's strict date parsing (requires [Moment.js][moment] for custom formatting)
* `defaultDate` the initial date to view when first opened
Expand All @@ -106,7 +106,7 @@ Pikaday has many useful options:

## jQuery Plugin

The normal version of Pikaday does not require jQuery, however there is a jQuery plugin if that floats your boat (see `plugins/pikaday.jquery.js` in the repository). This version requires jQuery, naturally, and can be used like other plugins:
The normal version of Pikaday does not require jQuery, however there is a jQuery plugin if that floats your boat (see `plugins/pikaday.jquery.js` in the repository). This version requires jQuery, naturally, and can be used like other plugins:
See the [jQuery example][] for a full version.

```html
Expand All @@ -126,15 +126,15 @@ $('.datepicker').eq(0).pikaday('show').pikaday('gotoYear', 2042);

## AMD support

If you use a modular script loader than Pikaday is not bound to the global object and will fit nicely in your build process. You can require Pikaday just like any other module.
If you use a modular script loader than Pikaday is not bound to the global object and will fit nicely in your build process. You can require Pikaday just like any other module.
See the [AMD example][] for a full version.

```javascript
require(['pikaday'], function(Pikaday) {
var picker = new Pikaday({ field: document.getElementById('datepicker') });
});
```
The same applies for the jQuery plugin mentioned above.
The same applies for the jQuery plugin mentioned above.
See the [jQuery AMD example][] for a full version.

```javascript
Expand Down Expand Up @@ -272,11 +272,11 @@ You must provide 12 months and 7 weekdays (with abbreviations). Always specify w

### Timepicker

Pikaday is a pure datepicker. It will not support picking a time of day. However, there have been efforts to add time support to Pikaday.
Pikaday is a pure datepicker. It will not support picking a time of day. However, there have been efforts to add time support to Pikaday.
See [#1][issue1] and [#18][issue18]. These reside in their own fork.

You can use the work [@owenmead][owenmead] did most recently at [owenmead/Pikaday][owen Pika]
A more simple time selection approach done by [@xeeali][xeeali] at [xeeali/Pikaday][xeeali Pika] is based on version 1.2.0.
You can use the work [@owenmead][owenmead] did most recently at [owenmead/Pikaday][owen Pika]
A more simple time selection approach done by [@xeeali][xeeali] at [xeeali/Pikaday][xeeali Pika] is based on version 1.2.0.
Also [@stas][stas] has a fork [stas/Pikaday][stas Pika], but is now quite old


Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ <h2>What is this?</h2>
{
field: document.getElementById('datepicker'),
firstDay: 1,
minDate: new Date(2000, 0, 1),
minDate: new Date(),
maxDate: new Date(2020, 12, 31),
yearRange: [2000,2020]
});
Expand Down
85 changes: 73 additions & 12 deletions pikaday.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@
renderDay = function(opts)
{
var arr = [];
var ariaSelected = 'false';
if (opts.isEmpty) {
if (opts.showDaysInNextAndPreviousMonths) {
arr.push('is-outside-current-month');
Expand All @@ -296,6 +297,7 @@
}
if (opts.isSelected) {
arr.push('is-selected');
ariaSelected = 'true';
}
if (opts.isInRange) {
arr.push('is-inrange');
Expand All @@ -306,7 +308,7 @@
if (opts.isEndRange) {
arr.push('is-endrange');
}
return '<td data-day="' + opts.day + '" class="' + arr.join(' ') + '">' +
return '<td data-day="' + opts.day + '" class="' + arr.join(' ') + '" aria-selected="' + ariaSelected + '">' +
'<button class="pika-button pika-day" type="button" ' +
'data-pika-year="' + opts.year + '" data-pika-month="' + opts.month + '" data-pika-day="' + opts.day + '">' +
opts.day +
Expand Down Expand Up @@ -343,13 +345,13 @@
return '<thead><tr>' + (opts.isRTL ? arr.reverse() : arr).join('') + '</tr></thead>';
},

renderTitle = function(instance, c, year, month, refYear)
renderTitle = function(instance, c, year, month, refYear, randId)
{
var i, j, arr,
opts = instance._o,
isMinYear = year === opts.minYear,
isMaxYear = year === opts.maxYear,
html = '<div class="pika-title">',
html = '<div id="' + randId + '" class="pika-title" role="heading" aria-live="assertive">',
monthHtml,
yearHtml,
prev = true,
Expand All @@ -361,6 +363,7 @@
((isMinYear && i < opts.minMonth) || (isMaxYear && i > opts.maxMonth) ? 'disabled="disabled"' : '') + '>' +
opts.i18n.months[i] + '</option>');
}

monthHtml = '<div class="pika-label">' + opts.i18n.months[month] + '<select class="pika-select pika-select-month" tabindex="-1">' + arr.join('') + '</select></div>';

if (isArray(opts.yearRange)) {
Expand Down Expand Up @@ -402,9 +405,9 @@
return html += '</div>';
},

renderTable = function(opts, data)
renderTable = function(opts, data, randId)
{
return '<table cellpadding="0" cellspacing="0" class="pika-table">' + renderHead(opts) + renderBody(data) + '</table>';
return '<table cellpadding="0" cellspacing="0" class="pika-table" role="grid" aria-labelledby="' + randId + '">' + renderHead(opts) + renderBody(data) + '</table>';
},


Expand Down Expand Up @@ -474,6 +477,34 @@
}
};

self._onKeyChange = function(e)
{
e = e || window.event;

if (self.isVisible()) {

switch(e.keyCode){
case 13:
case 27:
opts.field.blur();
break;
case 37:
e.preventDefault();
self.adjustDate('subtract', 1);
break;
case 38:
self.adjustDate('subtract', 7);
break;
case 39:
self.adjustDate('add', 1);
break;
case 40:
self.adjustDate('add', 7);
break;
}
}
};

self._onInputChange = function(e)
{
var date;
Expand Down Expand Up @@ -556,6 +587,7 @@
addEvent(self.el, 'mousedown', self._onMouseDown, true);
addEvent(self.el, 'touchend', self._onMouseDown, true);
addEvent(self.el, 'change', self._onChange);
addEvent(document, 'keydown', self._onKeyChange);

if (opts.field) {
if (opts.container) {
Expand Down Expand Up @@ -692,11 +724,11 @@
},

/**
* return a Date object of the current selection
* return a Date object of the current selection with fallback for the current date
*/
getDate: function()
{
return isDate(this._d) ? new Date(this._d.getTime()) : null;
return isDate(this._d) ? new Date(this._d.getTime()) : new Date();
},

/**
Expand Down Expand Up @@ -777,6 +809,30 @@
this.adjustCalendars();
},

adjustDate: function(sign, days) {

var day = this.getDate();
var difference = parseInt(days)*24*60*60*1000;

var newDay;

if (sign === 'add') {
newDay = new Date(day.valueOf() + difference);
} else if (sign === 'subtract') {
newDay = new Date(day.valueOf() - difference);
}

if (hasMoment) {
if (sign === 'add') {
newDay = moment(day).add(days, "days").toDate();
} else if (sign === 'subtract') {
newDay = moment(day).subtract(days, "days").toDate();
}
}

this.setDate(newDay);
},

adjustCalendars: function() {
this.calendars[0] = adjustCalendar(this.calendars[0]);
for (var c = 1; c < this._o.numberOfMonths; c++) {
Expand Down Expand Up @@ -890,7 +946,8 @@
maxYear = opts.maxYear,
minMonth = opts.minMonth,
maxMonth = opts.maxMonth,
html = '';
html = '',
randId;

if (this._y <= minYear) {
this._y = minYear;
Expand All @@ -905,8 +962,10 @@
}
}

randId = 'pika-title-' + Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 2);

for (var c = 0; c < opts.numberOfMonths; c++) {
html += '<div class="pika-lendar">' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year) + this.render(this.calendars[c].year, this.calendars[c].month) + '</div>';
html += '<div class="pika-lendar">' + renderTitle(this, c, this.calendars[c].year, this.calendars[c].month, this.calendars[0].year, randId) + this.render(this.calendars[c].year, this.calendars[c].month, randId) + '</div>';
}

this.el.innerHTML = html;
Expand All @@ -922,6 +981,8 @@
if (typeof this._o.onDraw === 'function') {
this._o.onDraw(this);
}
// let the screen reader user know to use arrow keys
this._o.field.setAttribute('aria-label', 'Use the arrow keys to pick a date');
},

adjustPosition: function()
Expand Down Expand Up @@ -978,7 +1039,7 @@
/**
* render HTML for a particular month
*/
render: function(year, month)
render: function(year, month, randId)
{
var opts = this._o,
now = new Date(),
Expand Down Expand Up @@ -1058,7 +1119,7 @@
r = 0;
}
}
return renderTable(opts, data);
return renderTable(opts, data, randId);
},

isVisible: function()
Expand All @@ -1068,7 +1129,7 @@

show: function()
{
if (!this._v) {
if (!this.isVisible()) {
removeClass(this.el, 'is-hidden');
this._v = true;
this.draw();
Expand Down

0 comments on commit 3f4b9c9

Please sign in to comment.