blob: 0b4860fdbf05de408b0829de6fde89271772f2ad [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
goog.provide('cvox.ChromeVoxHTMLDateWidget');
goog.require('Msgs');
/**
* @fileoverview Gives the user spoken feedback as they interact with the date
* widget (input type=date).
*
*/
/**
* A class containing the information needed to speak
* a text change event to the user.
*
* @constructor
* @param {Element} dateElem The time widget element.
* @param {cvox.TtsInterface} tts The TTS object from ChromeVox.
*/
cvox.ChromeVoxHTMLDateWidget = function(dateElem, tts) {
var self = this;
/**
* Currently selected field in the widget.
* @type {number}
* @private
*/
this.pos_ = 0;
var maxpos = 2;
if (dateElem.type == 'month' || dateElem.type == 'week') {
maxpos = 1;
}
/**
* The maximum number of fields in the widget.
* @type {number}
* @private
*/
this.maxPos_ = maxpos;
/**
* The HTML node of the widget.
* @type {Node}
* @private
*/
this.dateElem_ = dateElem;
/**
* A handle to the ChromeVox TTS object.
* @type {Object}
* @private
*/
this.dateTts_ = tts;
/**
* The previous value of the year field.
* @type {number}
* @private
*/
this.pYear_ = -1;
/**
* The previous value of the month field.
* @type {number}
* @private
*/
this.pMonth_ = -1;
/**
* The previous value of the week field.
* @type {number}
* @private
*/
this.pWeek_ = -1;
/**
* The previous value of the day field.
* @type {number}
* @private
*/
this.pDay_ = -1;
// Use listeners to make this work when running tests inside of ChromeVox.
this.keyListener_ = function(evt) {
self.eventHandler_(evt);
};
this.blurListener_ = function(evt) {
self.shutdown();
};
// Ensure we have a reasonable value to start with.
if (this.dateElem_.value.length == 0) {
this.forceInitTime_();
}
// Move the cursor to the first position so that we are guaranteed to start
// off at the hours position.
for (var i = 0; i < this.maxPos_; i++) {
var evt = document.createEvent('KeyboardEvent');
evt.initKeyboardEvent(
'keydown', true, true, window, 'Left', 0, false, false, false, false);
this.dateElem_.dispatchEvent(evt);
evt = document.createEvent('KeyboardEvent');
evt.initKeyboardEvent(
'keyup', true, true, window, 'Left', 0, false, false, false, false);
this.dateElem_.dispatchEvent(evt);
}
this.dateElem_.addEventListener('keydown', this.keyListener_, false);
this.dateElem_.addEventListener('keyup', this.keyListener_, false);
this.dateElem_.addEventListener('blur', this.blurListener_, false);
this.update_(true);
};
/**
* Removes the key listeners for the time widget.
*
*/
cvox.ChromeVoxHTMLDateWidget.prototype.shutdown = function() {
this.dateElem_.removeEventListener('blur', this.blurListener_, false);
this.dateElem_.removeEventListener('keydown', this.keyListener_, false);
this.dateElem_.removeEventListener('keyup', this.keyListener_, false);
};
/**
* Forces a sensible default value so that there is something there that can
* be inspected with JS.
* @private
*/
cvox.ChromeVoxHTMLDateWidget.prototype.forceInitTime_ = function() {
var currentDate = new Date();
var valueString = '';
var yearString = currentDate.getFullYear() + '';
// Date.getMonth starts at 0, but the value for the HTML5 date widget needs to
// start at 1.
var monthString = currentDate.getMonth() + 1 + '';
if (monthString.length < 2) {
monthString = '0' + monthString; // Month format is MM.
}
var dayString = currentDate.getDate() + '';
switch (this.dateElem_.type) {
case 'month':
valueString = yearString + '-' + monthString;
break;
case 'week':
// Based on info from: http://www.merlyn.demon.co.uk/weekcalc.htm#WNR
currentDate.setHours(0, 0, 0);
// Set to nearest Thursday: current date + 4 - current day number
// Make Sunday's day number 7
currentDate.setDate(
currentDate.getDate() + 4 - (currentDate.getDay() || 7));
// Get first day of year
var yearStart = new Date(currentDate.getFullYear(), 0, 1);
// Calculate full weeks to nearest Thursday
var weekString =
Math.ceil((((currentDate - yearStart) / 86400000) + 1) / 7) + '';
if (weekString.length < 2) {
weekString = '0' + weekString; // Week format is WXX.
}
weekString = 'W' + weekString;
valueString = yearString + '-' + weekString;
break;
default:
valueString = yearString + '-' + monthString + '-' + dayString;
break;
}
this.dateElem_.setAttribute('value', valueString);
};
/**
* Ensure that the position stays within bounds.
* @private
*/
cvox.ChromeVoxHTMLDateWidget.prototype.handlePosChange_ = function() {
this.pos_ = Math.max(this.pos_, 0);
this.pos_ = Math.min(this.pos_, this.maxPos_);
// TODO (clchen, dtseng): Make this logic i18n once there is a way to
// determine what the date format actually is. For now, assume that:
// date == mm/dd/yyyy
// week == ww/yyyy
// month == mm/yyyy.
switch (this.pos_) {
case 0:
if (this.dateElem_.type == 'week') {
this.pWeek_ = -1;
} else {
this.pMonth_ = -1;
}
break;
case 1:
if (this.dateElem_.type == 'date') {
this.pDay_ = -1;
} else {
this.pYear_ = -1;
}
break;
case 2:
this.pYear_ = -1;
break;
}
};
/**
* Speaks any changes to the control.
* @private
* @param {boolean} shouldSpeakLabel Whether or not to speak the label.
*/
cvox.ChromeVoxHTMLDateWidget.prototype.update_ = function(shouldSpeakLabel) {
var splitDate = this.dateElem_.value.split('-');
if (splitDate.length < 1) {
this.forceInitTime_();
return;
}
var year = -1;
var month = -1;
var week = -1;
var day = -1;
year = parseInt(splitDate[0], 10);
if (this.dateElem_.type == 'week') {
week = parseInt(splitDate[1].replace('W', ''), 10);
} else if (this.dateElem_.type == 'date') {
month = parseInt(splitDate[1], 10);
day = parseInt(splitDate[2], 10);
} else {
month = parseInt(splitDate[1], 10);
}
var changeMessage = '';
if (shouldSpeakLabel) {
changeMessage = cvox.DomUtil.getName(this.dateElem_, true, true) + '\n';
}
if (week != this.pWeek_) {
changeMessage =
changeMessage + Msgs.getMsg('datewidget_week') + week + '\n';
this.pWeek_ = week;
}
if (month != this.pMonth_) {
var monthName = '';
switch (month) {
case 1:
monthName = Msgs.getMsg('datewidget_january');
break;
case 2:
monthName = Msgs.getMsg('datewidget_february');
break;
case 3:
monthName = Msgs.getMsg('datewidget_march');
break;
case 4:
monthName = Msgs.getMsg('datewidget_april');
break;
case 5:
monthName = Msgs.getMsg('datewidget_may');
break;
case 6:
monthName = Msgs.getMsg('datewidget_june');
break;
case 7:
monthName = Msgs.getMsg('datewidget_july');
break;
case 8:
monthName = Msgs.getMsg('datewidget_august');
break;
case 9:
monthName = Msgs.getMsg('datewidget_september');
break;
case 10:
monthName = Msgs.getMsg('datewidget_october');
break;
case 11:
monthName = Msgs.getMsg('datewidget_november');
break;
case 12:
monthName = Msgs.getMsg('datewidget_december');
break;
}
changeMessage = changeMessage + monthName + '\n';
this.pMonth_ = month;
}
if (day != this.pDay_) {
changeMessage = changeMessage + day + '\n';
this.pDay_ = day;
}
if (year != this.pYear_) {
changeMessage = changeMessage + year + '\n';
this.pYear_ = year;
}
if (changeMessage.length > 0) {
this.dateTts_.speak(changeMessage, 0, null);
}
};
/**
* Handles user key events.
* @private
* @param {Event} evt The event to be handled.
*/
cvox.ChromeVoxHTMLDateWidget.prototype.eventHandler_ = function(evt) {
var shouldSpeakLabel = false;
if (evt.type == 'keydown') {
// Handle tab/right arrow
if (((evt.keyCode == 9) && !evt.shiftKey) || (evt.keyCode == 39)) {
this.pos_++;
this.handlePosChange_();
shouldSpeakLabel = true;
}
// Handle shift+tab/left arrow
if (((evt.keyCode == 9) && evt.shiftKey) || (evt.keyCode == 37)) {
this.pos_--;
this.handlePosChange_();
shouldSpeakLabel = true;
}
// For all other cases, fall through and let update_ decide if there are any
// changes that need to be spoken.
}
this.update_(shouldSpeakLabel);
};