Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,397 @@
/* global ValuePicker */
/**
* SpinDatePicker is a html/js "widget" which enables users
* pick a specific date. It display the date in the way based
* on the language setting.
*
* The SpinDatePicker itself contains no UI for the controls.
*
* Example usage:
*
* // All necessary UI elements are contained in the root element.
* var picker = new SpinDatePicker(root);
* picker.value = new Date();
* // after users pick a date
* var newDate = picker.value;
*/
window.SpinDatePicker = (function() {
'use strict';
var GLOBAL_MIN_YEAR = 1900;
var GLOBAL_MAX_YEAR = 2099;
var DateRange = (function DateRange(min, max) {
var _minYear = min.getFullYear();
var _maxYear = max.getFullYear();
var _minMonth = min.getMonth();
var _maxMonth = max.getMonth();
var _minDate = min.getDate();
var _maxDate = max.getDate();
return {
get min() {
return min;
},
get max() {
return max;
},
get minYear() {
return _minYear;
},
get maxYear() {
return _maxYear;
},
get minMonth() {
return _minMonth;
},
get maxMonth() {
return _maxMonth;
},
get minDate() {
return _minDate;
},
get maxDate() {
return _maxDate;
}
};
});
var _dateRange = new DateRange(new Date(GLOBAL_MIN_YEAR, 0, 1),
new Date(GLOBAL_MAX_YEAR, 11, 31));
function getYearText() {
var yearText = [];
var dateTimeFormat = navigator.mozL10n.DateTimeFormat();
for (var i = GLOBAL_MIN_YEAR; i <= GLOBAL_MAX_YEAR; i++) {
var date = new Date(i, 0, 1);
yearText.push(dateTimeFormat.localeFormat(date, '%Y'));
}
return yearText;
}
function getMonthText() {
var monthText = [];
var date = new Date(1970, 0, 1);
var dateTimeFormat = navigator.mozL10n.DateTimeFormat();
for (var i = 0; i < 12; i++) {
date.setMonth(i);
monthText.push(dateTimeFormat.localeFormat(date, '%B'));
}
return monthText;
}
function getDateText() {
var dateText = [];
var date = new Date(1970, 0, 1);
var dateTimeFormat = navigator.mozL10n.DateTimeFormat();
for (var i = 1; i <= 31; i++) {
date.setDate(i);
dateText.push(dateTimeFormat.localeFormat(date, '%d'));
}
return dateText;
}
function getDaysInMonth(year, month) {
var date = new Date(year, month + 1, 0);
return date.getDate();
}
/**
* Get the order of date components.
*
* @param {String} date format.
*/
function getDateComponentOrder() {
var format = navigator.mozL10n.get('dateTimeFormat_%x');
var order = '';
var tokens = format.match(/(%E.|%O.|%.)/g);
if (tokens) {
tokens.forEach(function(token) {
switch (token) {
case '%Y':
case '%y':
case '%Oy':
case 'Ey':
case 'EY':
order += 'Y';
break;
case '%B':
case '%b':
case '%m':
case '%Om':
order += 'M';
break;
case '%d':
case '%e':
case '%Od':
case '%Oe':
order += 'D';
break;
}
});
}
if (order.length != 3) {
order = 'DMY';
}
return order;
}
/**
* Initialize a date picker widget.
*
* @param {HTMLELement} element target of widget creation.
*/
function SpinDatePicker(element, minDate, maxDate) {
this.element = element;
this.yearPicker = null;
this.monthPicker = null;
this.datePicker = null;
//XXX: When the document is localized again
// we must also re-render the month because
// the week days may have changed?
// This will only happen when we change timezones
// unless we add this information to the locales.
var pickerContainer =
element.querySelector('.picker-container');
var yearPickerContainer =
element.querySelector('.value-picker-year');
var monthPickerContainer =
element.querySelector('.value-picker-month');
var datePickerContainer =
element.querySelector('.value-picker-date');
var updateCurrentValue = (function spd_updateCurrentValue() {
var selectedYear = this.yearPicker.getSelectedIndex() + GLOBAL_MIN_YEAR;
var selectedMonth = this.monthPicker.getSelectedIndex();
var selectedDate = this.datePicker.getSelectedIndex() + 1;
this._value = new Date(selectedYear, selectedMonth, selectedDate);
}).bind(this);
var updatePickersRange =
(function spd_updatePickersRange() {
var selectedYear = this.yearPicker.getSelectedIndex() + GLOBAL_MIN_YEAR;
var selectedMonth = this.monthPicker.getSelectedIndex();
var minMonth = 0;
var maxMonth = 11;
var minDate = 1;
var maxDate = 31;
if (selectedYear === _dateRange.minYear) {
minMonth = _dateRange.minMonth;
if (selectedMonth === _dateRange.minMonth) {
minDate = _dateRange.minDate;
}
}
if (selectedYear === _dateRange.maxYear) {
maxMonth = _dateRange.maxMonth;
if (selectedMonth === _dateRange.maxMonth) {
maxDate = _dateRange.maxDate;
}
}
var days = getDaysInMonth(this.yearPicker.getSelectedIndex() +
GLOBAL_MIN_YEAR, this.monthPicker.getSelectedIndex());
minDate = Math.min(minDate, days);
maxDate = Math.min(maxDate, days);
this.monthPicker.setRange(minMonth, maxMonth);
this.datePicker.setRange(minDate - 1, maxDate - 1);
}).bind(this);
var onvaluechangeInternal =
(function spd_onvaluechangeInternal(newDateValue) {
this.yearPicker.setSelectedIndex(
newDateValue.getFullYear() - GLOBAL_MIN_YEAR);
this.monthPicker.setSelectedIndex(newDateValue.getMonth());
this.datePicker.setSelectedIndex(newDateValue.getDate() - 1);
updatePickersRange.apply(this);
updateCurrentValue.apply(this);
}).bind(this);
var onSelectedYearChanged =
function spd_onSelectedYearChanged(selectedYear) {
updatePickersRange.apply(this);
updateCurrentValue.apply(this);
};
var onSelectedMonthChanged =
function spd_onSelectedMonthChanged(selectedMonth) {
updatePickersRange.apply(this);
updateCurrentValue.apply(this);
};
var onSelectedDateChanged =
function spd_onSelectedDateChanged(selectedDate) {
updateCurrentValue.apply(this);
};
var unitClassName = 'picker-unit';
// year value picker
var yearUnitStyle = {
valueDisplayedText: getYearText(),
className: unitClassName
};
if (this.yearPicker) {
this.yearPicker.uninit();
}
this.yearPicker = new ValuePicker(yearPickerContainer, yearUnitStyle);
this.yearPicker.onselectedindexchange = onSelectedYearChanged.bind(this);
// month value picker
var monthUnitStyle = {
valueDisplayedText: getMonthText(),
className: unitClassName
};
if (this.monthPicker) {
this.monthPicker.uninit();
}
this.monthPicker =
new ValuePicker(monthPickerContainer, monthUnitStyle);
this.monthPicker.onselectedindexchange = onSelectedMonthChanged.bind(this);
// date value picker
var dateUnitStyle = {
valueDisplayedText: getDateText(),
className: unitClassName
};
if (this.datePicker) {
this.datePicker.uninit();
}
this.datePicker = new ValuePicker(datePickerContainer, dateUnitStyle);
this.datePicker.onselectedindexchange =
onSelectedDateChanged.bind(this);
// set component order
var dateComponentOrder = getDateComponentOrder();
var pickerClassList = pickerContainer.classList;
pickerClassList.remove('YMD');
pickerClassList.remove('DMY');
pickerClassList.remove('MDY');
pickerClassList.add(dateComponentOrder);
// Prevent focus being taken away by us for time picker.
// The event listener on outer box will not be triggered cause
// there is a evt.stopPropagation() in value_picker.js
this.pickerElements = [monthPickerContainer, yearPickerContainer,
datePickerContainer];
this.pickerElements.forEach((function pickerElements_forEach(picker) {
picker.addEventListener('mousedown', this);
}).bind(this));
this.onvaluechangeInternal = onvaluechangeInternal.bind(this);
}
SpinDatePicker.prototype = {
/**
* Internal value not exposed so we can fire events
* when the getter/setter's are used.
*
* @type Date
*/
_value: null,
/**
* Gets current value
*
* @return {Null|Date} date or null.
*/
get value() {
return this._value;
},
/**
* Sets the current value of the date picker.
* When value differs from the currently set the
* `onvaluechange` event will be fired with the new/old value.
*/
set value(value) {
var old = this._value;
if (old !== value) {
this._value = value;
this.onvaluechangeInternal(value);
}
},
/**
* Getter is used for date normalization.
*/
get year() {
return this._value.getFullYear();
},
/**
* Getter is used for date normalization.
*/
get month() {
return this._value.getMonth();
},
get date() {
return this._value.getDate();
},
handleEvent: function vs_handleEvent(evt) {
switch (evt.type) {
case 'mousedown':
// Prevent focus being taken away by us.
evt.preventDefault();
break;
}
},
setRange: function vs_setRange(minDate, maxDate) {
if (!minDate) {
minDate = new Date(GLOBAL_MIN_YEAR, 0, 1);
}
if (!maxDate) {
maxDate = new Date(GLOBAL_MAX_YEAR, 11, 31);
}
_dateRange = new DateRange(minDate, maxDate);
// set date picker
this.yearPicker.setRange(minDate.getFullYear() - GLOBAL_MIN_YEAR,
maxDate.getFullYear() - GLOBAL_MIN_YEAR);
},
uninit: function() {
if (this.yearPicker) {
this.yearPicker.uninit();
}
if (this.monthPicker) {
this.monthPicker.uninit();
}
if (this.datePicker) {
this.datePicker.uninit();
}
this.pickerElements.forEach((function pickerElements_forEach(picker) {
picker.removeEventListener('mousedown', this);
}).bind(this));
},
/**
* Called when the selected date changes.
*/
onvaluechangeInternal: function(date) {}
};
return SpinDatePicker;
}());

View file

@ -0,0 +1,284 @@
/* global KeyEvent */
/**
* base widget used in ValueSelector and SpinDatePicker widget
*/
window.ValuePicker = (function() {
'use strict';
//
// Constructor
//
function VP(e, unitStyle) {
this.element = e;
this.container = e.parentNode;
this._valueDisplayedText = unitStyle.valueDisplayedText;
this._unitClassName = unitStyle.className;
this._top = 0;
this._lower = 0;
this._upper = unitStyle.valueDisplayedText.length - 1;
this._range = unitStyle.valueDisplayedText.length;
this._unitStyle = unitStyle;
this._currentIndex = 0;
this.init();
}
//
// Public methods
//
VP.prototype.getSelectedIndex = function() {
var selectedIndex = this._currentIndex;
return selectedIndex;
};
VP.prototype.getSelectedDisplayedText = function() {
var displayedText = this._valueDisplayedText[this._currentIndex];
return displayedText;
};
VP.prototype.setSelectedIndex = function(tunedIndex, ignorePicker) {
if ((tunedIndex % 1) > 0.5) {
tunedIndex = Math.floor(tunedIndex) + 1;
} else {
tunedIndex = Math.floor(tunedIndex);
}
if (tunedIndex < this._lower) {
tunedIndex = this._lower;
}
if (tunedIndex > this._upper) {
tunedIndex = this._upper;
}
var beforeIndex = this._currentIndex;
if (this._currentIndex != tunedIndex) {
this._currentIndex = tunedIndex;
this.onselectedindexchange(this._currentIndex);
}
this.updateUI(tunedIndex, ignorePicker);
var elementChildren = this.element.children;
elementChildren[beforeIndex].classList.remove('selected');
elementChildren[this._currentIndex].classList.add('selected');
return tunedIndex;
};
VP.prototype.setSelectedIndexByDisplayedText = function(displayedText) {
var newIndex = this._valueDisplayedText.indexOf(displayedText);
if (newIndex != -1) {
if (this._currentIndex != newIndex) {
this.setSelectedIndex(newIndex);
}
}
};
VP.prototype.setRange = function(lower, upper) {
if (lower !== null) {
this._lower = lower;
} else {
this._lower = 0;
}
if (upper !== null) {
this._upper = upper;
} else {
this._upper = this._unitStyle.valueDisplayedText.length - 1;
}
var unitElement = this.element.firstElementChild;
var index = 0;
while (unitElement) {
unitElement.dataset.disabled =
(index < this._lower || index > this._upper);
unitElement = unitElement.nextElementSibling;
index++;
}
this.container.setAttribute('aria-valuemin', this._lower);
this.container.setAttribute('aria-valuemax', this._upper);
this._range = this._upper - this._lower + 1;
this.setSelectedIndex(this._currentIndex);
};
//
// Internal methods
//
VP.prototype.init = function() {
this.initUI();
this.setSelectedIndex(0); // Default Index is zero
this.keypressHandler = vp_keypress.bind(this);
this.touchstartHandler = vp_touchstart.bind(this);
this.touchmoveHandler = vp_touchmove.bind(this);
this.touchendHandler = vp_touchend.bind(this);
this.addEventListeners();
};
VP.prototype.initUI = function() {
this.container.setAttribute('role', 'spinbutton');
this.container.setAttribute('aria-valuemin', this._lower);
this.container.setAttribute('aria-valuemax', this._upper);
var unitCount = this._valueDisplayedText.length;
for (var i = 0; i < unitCount; ++i) {
this.addPickerUnit(i);
}
// cache the size of picker
this._pickerUnits = this.element.children;
this._pickerUnitsHeight = this._pickerUnits[0].clientHeight;
this._pickerHeight = this._pickerUnits[0].clientHeight *
this._pickerUnits.length;
this._space = this._pickerHeight / this._range;
};
VP.prototype.addPickerUnit = function(index) {
var html = this._valueDisplayedText[index];
var unit = document.createElement('div');
unit.className = this._unitClassName;
unit.textContent = html;
this.element.appendChild(unit);
};
VP.prototype.updateUI = function(index, ignorePicker) {
if (true !== ignorePicker) {
this._top = -index * this._space;
this.element.style.transform = 'translateY(' + this._top + 'px)';
this.container.setAttribute('aria-valuenow', index);
this.container.setAttribute('aria-valuetext',
this._valueDisplayedText[index]);
}
};
VP.prototype.addEventListeners = function() {
this.container.addEventListener('keypress', this.keypressHandler, false);
this.element.addEventListener('touchstart', this.touchstartHandler, false);
};
VP.prototype.removeEventListeners = function() {
this.element.removeEventListener('touchend', this.touchendHandler, false);
this.element.removeEventListener('touchmove', this.touchmoveHandler, false);
};
VP.prototype.uninit = function() {
this._top = 0;
this.element.removeEventListener(
'touchstart', this.touchstartHandler, false);
this.element.removeEventListener('touchend', this.touchendHandler, false);
this.element.removeEventListener('touchmove', this.touchmoveHandler, false);
this.element.style.transform = 'translateY(0px)';
this.container.removeEventListener('keypress', this.keypressHandler, false);
this.container.removeAttribute('role');
this.container.removeAttribute('aria-valuemin');
this.container.removeAttribute('aria-valuemax');
this.container.removeAttribute('aria-valuenow');
this.container.removeAttribute('aria-valuetext');
this.onselectedindexchange = null;
empty(this.element);
};
VP.prototype.onselectedindexchange = function(index) {};
function cloneEvent(evt) {
return {
x: evt.touches[0].pageX,
y: evt.touches[0].pageY,
timestamp: evt.timeStamp
};
}
function empty(element) {
while (element.hasChildNodes()) {
element.removeChild(element.lastChild);
}
element.innerHTML = '';
}
//
// Tuneable parameters
//
var SPEED_THRESHOLD = 0.1;
var currentEvent, startEvent, currentSpeed;
var tunedIndex = 0;
function toFixed(value) {
return parseFloat(value.toFixed(1));
}
function calcSpeed() {
var movingSpace = startEvent.y - currentEvent.y;
var deltaTime = currentEvent.timestamp - startEvent.timestamp;
var speed = movingSpace / deltaTime;
currentSpeed = parseFloat(speed.toFixed(2));
}
function calcTargetIndex(space) {
return tunedIndex - getMovingSpace() / space;
}
// If the user swap really slow, narrow down the moving space
// So the user can fine tune value.
function getMovingSpace() {
var movingSpace = currentEvent.y - startEvent.y;
var reValue = Math.abs(currentSpeed) > SPEED_THRESHOLD ?
movingSpace : movingSpace / 4;
return reValue;
}
function vp_touchmove(event) {
/*jshint validthis: true */
event.stopPropagation();
event.target.setCapture(true);
currentEvent = cloneEvent(event);
calcSpeed();
// move selected index
this._top = this._top + getMovingSpace();
this.element.style.transform = 'translateY(' + this._top + 'px)';
tunedIndex = calcTargetIndex(this._space);
var roundedIndex = Math.round(tunedIndex * 10) / 10;
if (roundedIndex != this._currentIndex) {
this.setSelectedIndex(toFixed(roundedIndex), true);
}
startEvent = currentEvent;
}
function vp_touchend(event) {
/*jshint validthis: true */
event.stopPropagation();
this.removeEventListeners();
// Add momentum if speed is higher than a given threshold.
if (Math.abs(currentSpeed) > SPEED_THRESHOLD) {
var direction = currentSpeed > 0 ? 1 : -1;
tunedIndex += Math.min(Math.abs(currentSpeed) * 5, 5) * direction;
}
tunedIndex = this.setSelectedIndex(toFixed(tunedIndex));
currentSpeed = 0;
}
function vp_touchstart(event) {
/*jshint validthis: true */
event.stopPropagation();
startEvent = currentEvent = cloneEvent(event);
tunedIndex = this._currentIndex;
this.removeEventListeners();
this.element.addEventListener('touchmove', this.touchmoveHandler, false);
this.element.addEventListener('touchend', this.touchendHandler, false);
}
function vp_keypress(event) {
/*jshint validthis: true */
if (event.keyCode == KeyEvent.DOM_VK_DOWN) {
this.setSelectedIndex(this._currentIndex - 1);
} else {
this.setSelectedIndex(this._currentIndex + 1);
}
}
return VP;
}());

View file

@ -0,0 +1,648 @@
/* global BaseUI, LazyLoader, InputParser, ValueSelector, SpinDatePicker,
ValuePicker, Template */
'use strict';
(function(exports) {
var _id = 0;
function toCamelCase(str) {
return str.replace(/\-(.)/g, function replacer(str, p1) {
return p1.toUpperCase();
});
}
exports.ValueSelector = function(app) {
this.app = app;
this.containerElement = app.element;
this.instanceID = _id++;
this._currentPickerType = null;
this._currentInputType = null;
this._currentDatetimeValue = '';
this._injected = false;
this._datePicker = null;
this._timePicker = null;
app.element.addEventListener('_opening', this);
app.element.addEventListener('_closing', this);
app.element.addEventListener('_inputmethod-contextchange', this);
app.element.addEventListener('_sheetsgesturebegin', this);
app.element.addEventListener('_localized', this);
window.addEventListener('timeformatchange', this);
};
ValueSelector.prototype = Object.create(BaseUI.prototype);
ValueSelector.prototype.CLASS_NAME = 'ValueSelector';
ValueSelector.prototype.ELEMENT_PREFIX = 'value-selector-';
ValueSelector.prototype.EVENT_PREFIX = 'value-selector-';
ValueSelector.prototype.customID = function vs_customID() {
if (this.app) {
return '[' + this.app.origin + ']';
} else {
return '';
}
};
ValueSelector.prototype._im = navigator.mozInputMethod;
ValueSelector.prototype.destroy = function() {
window.removeEventListener('timeformatchange', this);
};
ValueSelector.prototype.handleEvent = function vs_handleEvent(evt) {
this.app.debug('handling ' + evt.type);
var target = evt.target;
switch (evt.type) {
case 'submit':
case 'mousedown':
evt.preventDefault();
break;
case 'click':
if (target.dataset.type === 'cancel') {
this.cancel();
} else if (target.dataset.type === 'ok') {
this.confirm();
} else {
this.handleSelect(target);
}
break;
case '_opening':
case '_closing':
if (this._injected) {
this.hide();
}
break;
case '_localized':
// invalidate the current date and time picker when language setting
// changes
if (this._datePicker) {
this._datePicker.uninit();
this._datePicker = null;
}
if (this._timePicker) {
this._timePicker.uninit();
this._timePicker = null;
}
break;
case 'timeformatchange':
if (this._timePicker) {
this._timePicker.uninit();
this._timePicker = null;
}
break;
case '_sheetsgesturebegin':
// Only cancel if the value selector was rendered.
if (this._injected) {
this.cancel();
}
break;
case '_inputmethod-contextchange':
var typesToHandle = ['select-one', 'select-multiple', 'date', 'time',
'datetime', 'datetime-local', 'blur'];
// handle the <select> element and inputs with type of date/time
// in system app for now
if (typesToHandle.indexOf(evt.detail.inputType) < 0) {
return;
}
if (this._injected) {
this.show(evt.detail);
} else {
this.render(function afterRender() {
this.show(evt.detail);
}.bind(this));
}
break;
}
};
ValueSelector.prototype.render = function vs_render(callback) {
this.publish('willrender');
LazyLoader.load('shared/js/template.js', function onTemplateLoaded(){
this.containerElement.insertAdjacentHTML('beforeend', this.view());
this._fetchElements();
this._registerEvents();
this._injected = true;
this.publish('rendered');
callback();
}.bind(this));
};
ValueSelector.prototype._fetchElements = function vs__fetchElements() {
this.element = document.getElementById(this.CLASS_NAME + this.instanceID);
this.elements = {};
this.elementClasses = ['select-option-popup', 'container',
'options-container', 'options-title', 'select-options-buttons',
'time-picker-popup', 'time-picker', 'time-picker-container',
'time-picker-buttons', 'spin-date-picker-popup', 'spin-date-picker',
'spin-date-picker-buttons'];
// Loop and add element with camel style name to Value Selector attribute.
this.elementClasses.forEach(function createElementRef(name) {
this.elements[toCamelCase(name)] =
this.element.querySelector('.' + this.ELEMENT_PREFIX + name);
}, this);
};
ValueSelector.prototype.view = function vs_view() {
var template = new Template('value-selector-template');
return template.interpolate({
id: this.CLASS_NAME + this.instanceID
});
};
ValueSelector.prototype._registerEvents = function vs__registerEvents() {
this.elements.container.addEventListener('click', this);
// Prevent the form from submit.
this.elements.selectOptionPopup.addEventListener('submit', this);
this.element.addEventListener('mousedown', this);
['selectOptionsButtons', 'timePickerButtons',
'spinDatePickerButtons'].forEach(function(elementId) {
this.elements[elementId].addEventListener('click', this);
}, this);
};
ValueSelector.prototype._format = function vs__format(date, format) {
if (!date) {
return '';
}
date = new Date(date);
return date.toLocaleFormat(format);
};
ValueSelector.prototype.show = function vs_show(detail) {
var currentInputType = detail.inputType;
var currentValue = detail.value;
this._currentDatetimeValue = currentValue;
this._currentInputType = currentInputType;
if (currentInputType === 'blur') {
this.hide();
return;
}
this.publish('shown');
var min = detail.min;
var max = detail.max;
if (detail.choices) {
detail.choices = JSON.parse(detail.choices);
}
this.app._setVisibleForScreenReader(false);
if (this.element.hidden) {
this.element.hidden = false;
}
switch (currentInputType) {
case 'select-one':
case 'select-multiple':
this.app.debug('select triggered' + JSON.stringify(detail));
this._currentPickerType = currentInputType;
this.showOptions(detail);
break;
case 'date':
this.showDatePicker(currentValue, min, max);
break;
case 'time':
this.showTimePicker(currentValue);
break;
case 'datetime':
case 'datetime-local':
this.showDatePicker(this._format(currentValue, '%Y-%m-%d'), min,
max);
break;
}
};
ValueSelector.prototype.showPanel = function vs_showPanel(type) {
['selectOptionPopup', 'timePickerPopup', 'spinDatePickerPopup'].forEach(
function(elementId) {
this.elements[elementId].hidden = (type !== elementId);
}, this);
},
ValueSelector.prototype.hide = function vs_hide() {
this.app._setVisibleForScreenReader(true);
if (this.element.hidden) {
return;
}
this.element.blur();
this.element.hidden = true;
if (this.app) {
this.app.focus();
}
this.publish('hidden');
};
ValueSelector.prototype.handleSelect = function vs_handleSelect(target) {
if (target.dataset === undefined ||
(target.dataset.optionIndex === undefined &&
target.dataset.optionValue === undefined)) {
return;
}
var selectee;
if (this._currentPickerType === 'select-one') {
selectee = this.elements.container.querySelectorAll(
'[aria-selected="true"]');
for (var i = 0; i < selectee.length; i++) {
selectee[i].removeAttribute('aria-selected');
}
target.setAttribute('aria-selected', 'true');
} else if (target.getAttribute('aria-selected') === 'true') {
target.removeAttribute('aria-selected');
} else {
target.setAttribute('aria-selected', 'true');
}
// setValue here to trigger change event
var singleOptionIndex;
var optionIndices = [];
selectee = this.elements.container.querySelectorAll(
'[aria-selected="true"]');
if (this._currentPickerType === 'select-one') {
if (selectee.length > 0) {
singleOptionIndex = selectee[0].dataset.optionIndex;
}
this._im.setSelectedOption(singleOptionIndex);
} else if (this._currentPickerType === 'select-multiple') {
// Multiple select case
for (var i = 0; i < selectee.length; i++) { // jshint ignore:line
var index = parseInt(selectee[i].dataset.optionIndex);
optionIndices.push(index);
}
this._im.setSelectedOptions(optionIndices);
}
};
ValueSelector.prototype.cancel = function vs_cancel() {
this.app.debug('cancel invoked');
this._im.removeFocus();
this.hide();
};
ValueSelector.prototype.confirm = function vs_confirm() {
var currentInputType = this._currentInputType;
switch (currentInputType) {
case 'time':
var timeValue = this._timePicker.getTimeValue();
this.app.debug('output value: ' + timeValue);
this._im.setValue(timeValue);
break;
case 'date':
// The format should be 2012-09-19
var dateValue = this._format(this._datePicker.value, '%Y-%m-%d');
this.app.debug('output value: ' + dateValue);
this._im.setValue(dateValue);
break;
case 'datetime':
case 'datetime-local':
var currentDatetimeValue = this._currentDatetimeValue;
if (this._currentPickerType === 'date') {
this.hide();
this.showTimePicker(this._format(currentDatetimeValue, '%H:%M'));
return;
} else if (this._currentPickerType === 'time') {
var selectedDate = this._datePicker.value;
var hour = this._timePicker.getHour();
var minute = this._timePicker.minute.getSelectedDisplayedText();
var second = '';
var millisecond = '';
var date = null;
// The second and millisecond values can't be selected by picker.
// So set these values as same as
// the current value of datetime/datetime-local input field
// when currentDatetimeValue is not equal to ''(space),
// or set the values as same as current time.
if (currentDatetimeValue !== '') {
date = new Date(this._currentDatetimeValue);
} else {
date = new Date();
}
second = date.getSeconds();
millisecond = date.getMilliseconds();
selectedDate.setHours(hour);
selectedDate.setMinutes(minute);
selectedDate.setSeconds(second);
selectedDate.setMilliseconds(millisecond);
var datetimeValue = '';
if (currentInputType === 'datetime') {
// The datetime format should be 1983-09-08T14:54:39.123Z
datetimeValue = selectedDate.toISOString();
} else { // if (currentInputType === 'datetime-local')
// The datetime-local format should be 1983-09-08T14:54:39.123
datetimeValue = selectedDate.toLocaleFormat('%Y-%m-%dT%H:%M:%S.') +
selectedDate.getMilliseconds();
}
this.app.debug('output value: ' + datetimeValue);
this._im.setValue(datetimeValue);
}
break;
}
this._im.removeFocus();
this.hide();
};
ValueSelector.prototype.showOptions = function vs_showOptions(detail) {
var options = null;
if (detail.choices && detail.choices.choices) {
options = detail.choices.choices;
}
if (options) {
this.buildOptions(options);
}
this.showPanel('selectOptionPopup');
};
ValueSelector.prototype.buildOptions = function(options) {
if (this.elements.optionsContainer) {
this.elements.optionsContainer.innerHTML = '';
} else {
return;
}
var groupTemplate = new Template('value-selector-groupoption-template');
var template = new Template('value-selector-option-template');
// Add ARIA property to notify if this is a multi-select or not.
this.elements.optionsContainer.setAttribute('aria-multiselectable',
this._currentPickerType !== 'select-one');
options.forEach(function(option) {
if (option.group) {
this.elements.optionsContainer.insertAdjacentHTML('beforeend',
groupTemplate.interpolate({
text: option.text
}));
} else {
this.elements.optionsContainer.insertAdjacentHTML('beforeend',
template.interpolate({
index: option.optionIndex.toString(10),
checked: option.selected.toString(),
for: 'gaia-option-' + option.optionIndex,
text: option.text
}));
}
}, this);
// Apply different style when the options are more than 1 page
this.elements.container.classList.toggle('scrollable', options.length > 5);
// Change the title for multiple select
var titleL10nId = this._currentPickerType === 'select-one' ?
'choose-option' : 'choose-options';
if (this.elements.optionsTitle) {
this.elements.optionsTitle.dataset.l10nId = titleL10nId;
this.elements.optionsTitle.setAttribute('data-l10n-id', titleL10nId);
}
};
ValueSelector.prototype.showTimePicker =
function vs_showTimePicker(currentValue) {
this._currentPickerType = 'time';
this.showPanel('timePickerPopup');
if (!this._timePicker) {
this._timePicker = new TimePicker(this.elements.timePickerContainer);
}
var time;
if (!currentValue) {
var now = new Date();
time = {
hours: now.getHours(),
minutes: now.getMinutes()
};
} else {
time = InputParser.importTime(currentValue);
}
// Set the value of time picker according to the current value
if (this._timePicker.is12hFormat) {
var hour = (time.hours % 12);
hour = (hour === 0) ? 12 : hour;
// 24-hour state value selector: AM = 0, PM = 1
var hour24State = (time.hours >= 12) ? 1 : 0;
this._timePicker.hour.setSelectedIndexByDisplayedText(hour);
this._timePicker.hour24State.setSelectedIndex(hour24State);
} else {
this._timePicker.hour.setSelectedIndex(time.hours);
}
this._timePicker.minute.setSelectedIndex(time.minutes);
};
ValueSelector.prototype._str2Date = function vs__str2Date(str) {
if (!str) {
return null;
}
var dcs = str.split('-');
var date = new Date(dcs[0], parseInt(dcs[1]) - 1, dcs[2]);
if (isNaN(date.getTime())) {
date = null;
}
return date;
};
ValueSelector.prototype.showDatePicker =
function vs_showDatePicker(currentValue, min, max) {
this._currentPickerType = 'date';
this.showPanel('spinDatePickerPopup');
var minDate = null;
var maxDate = null;
minDate = this._str2Date(min);
maxDate = this._str2Date(max);
if (!this._datePicker) {
this._datePicker = new SpinDatePicker(this.elements.spinDatePicker);
}
this._datePicker.setRange(minDate, maxDate);
// Show current date as default value
var date = new Date();
if (currentValue) {
date = InputParser.formatInputDate(currentValue, '');
}
this._datePicker.value = date;
};
function TimePicker(element) {
this.element = element;
this._fetchElements();
var _ = navigator.mozL10n.get;
var is12hFormat = navigator.mozHour12;
var localeTimeFormat = is12hFormat ?
_('shortTimeFormat12') : _('shortTimeFormat24');
var startHour = is12hFormat ? 1 : 0;
var endHour = is12hFormat ? (startHour + 12) : (startHour + 12 * 2);
var unitClassName = 'picker-unit';
this.is12hFormat = is12hFormat;
this.hour = new ValuePicker(this.elements.valuePickerHours, {
valueDisplayedText: this._setDisplayedText(startHour, endHour),
className: unitClassName
});
this.minute = new ValuePicker(this.elements.valuePickerMinutes, {
valueDisplayedText: this._setDisplayedText(0, 60, function(value) {
return (value < 10) ? '0' + value : value;
}),
className: unitClassName
});
if (is12hFormat) {
this.hour24State = new ValuePicker(this.elements.valuePickerHour24State, {
valueDisplayedText: [
_('time_am'),
_('time_pm')
],
className: unitClassName
});
}
var separator = ':';
var minutesPosition = localeTimeFormat.indexOf('%M');
if (minutesPosition > 0) {
separator = localeTimeFormat.substr(minutesPosition - 1, 1);
}
this.elements.hoursMinutesSeparator.textContent = separator;
this.setTimePickerStyle();
this._registerEvents();
}
TimePicker.prototype = {
_fetchElements: function tp__fetchElements() {
this.elements = {};
this.elementClasses = ['value-picker-hours', 'value-picker-minutes',
'value-picker-hour24-state', 'hours-minutes-separator'];
// Loop and add element with camel style name to Time Picker attribute.
this.elementClasses.forEach(function createElementRef(name) {
this.elements[toCamelCase(name)] =
this.element.querySelector('.' + name);
}, this);
},
_registerEvents: function tp__registerEvents() {
// Prevent focus being taken away by us for time picker.
// The event listener on outer box will not be triggered cause there is a
// evt.stopPropagation() in value_picker.js
['valuePickerHours', 'valuePickerMinutes',
'valuePickerHour24State'].forEach(function(elementId) {
this.elements[elementId].addEventListener('mousedown', this);
}, this);
},
handleEvent: function tp_handleEvent(evt) {
evt.preventDefault();
},
_unregisterEvents: function tp__unregisterEvents() {
['valuePickerHours', 'valuePickerMinutes',
'valuePickerHour24State'].forEach(function(elementId) {
this.elements[elementId].removeEventListener('mousedown', this);
}, this);
},
_setDisplayedText: function tp__setDisplayedText(min, max, format) {
var list = [];
for (var i = min; i < max; ++i) {
list.push(format ? format(i) : i);
}
return list;
},
uninit: function tp_uninit() {
this._unregisterEvents();
this.minute.uninit();
this.hour.uninit();
if (this.hour24State) {
this.hour24State.uninit();
}
},
setTimePickerStyle: function tp_setTimePickerStyle() {
var style = 'format24h';
if (this.is12hFormat) {
var localeTimeFormat = navigator.mozL10n.get('shortTimeFormat12');
// handle revert appearance
var reversedPeriod =
(localeTimeFormat.indexOf('%p') < localeTimeFormat.indexOf('%M'));
style = (reversedPeriod) ? 'format12hrev' : 'format12h';
if ('format12' === style) {
this.element.classList.remove('format12hrev');
this.element.classList.remove('format24h');
if (!this.element.classList.contains(style)) {
this.element.classList.add(style);
}
} else {
this.element.classList.remove('format12');
this.element.classList.remove('format24h');
if (!this.element.classList.contains(style)) {
this.element.classList.add(style);
}
}
}
if ('format24h' === style) {
this.element.classList.remove('format12h');
this.element.classList.remove('format12hrev');
if (!this.element.classList.contains(style)) {
this.element.classList.add(style);
}
}
},
getHour: function tp_getHours() {
var hour = 0;
if (this.is12hFormat) {
var hour24Offset = 12 * this.hour24State.getSelectedIndex();
hour = this.hour.getSelectedDisplayedText();
hour = (hour == 12) ? 0 : hour;
hour = hour + hour24Offset;
} else {
hour = this.hour.getSelectedIndex();
}
return hour;
},
// return a string for the time value, format: "16:37"
getTimeValue: function tp_getTimeValue() {
var hour = this.getHour();
var minute = this.minute.getSelectedDisplayedText();
return (hour < 10 ? '0' : '') + hour + ':' + minute;
}
};
})(window);