Skip to content

Instantly share code, notes, and snippets.

@pivanov
Last active December 14, 2015 10:08
Show Gist options
  • Save pivanov/5070121 to your computer and use it in GitHub Desktop.
Save pivanov/5070121 to your computer and use it in GitHub Desktop.
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
var ValueSelector = {
_containers: {},
_popups: {},
_buttons: {},
_datePicker: null,
debug: function(msg) {
var debugFlag = false;
if (debugFlag) {
console.log('[ValueSelector] ', msg);
}
},
init: function vs_init() {
var self = this;
window.navigator.mozKeyboard.onfocuschange = function onfocuschange(evt) {
var typeToHandle = ['select-one', 'select-multiple', 'date',
'time', 'datetime', 'datetime-local', 'blur'];
var type = evt.detail.type;
// handle the <select> element and inputs with type of date/time
// in system app for now
if (typeToHandle.indexOf(type) == -1)
return;
var currentValue = evt.detail.value;
switch (evt.detail.type) {
case 'select-one':
case 'select-multiple':
self.debug('select triggered' + JSON.stringify(evt.detail));
self._currentPickerType = evt.detail.type;
self.showOptions(evt.detail);
break;
case 'date':
self.showDatePicker(currentValue);
break;
case 'time':
self.showTimePicker(currentValue);
break;
case 'datetime':
case 'datetime-local':
// TODO
break;
case 'blur':
self.hide();
break;
}
};
this._element = document.getElementById('value-selector');
this._element.addEventListener('mousedown', this);
this._containers['select'] =
document.getElementById('value-selector-container');
this._containers['select'].addEventListener('click', this);
ActiveEffectHelper.enableActive(this._containers['select']);
this._popups['select'] =
document.getElementById('select-option-popup');
this._popups['select'].addEventListener('submit', this);
this._popups['time'] =
document.getElementById('time-picker-popup');
this._popups['date'] =
document.getElementById('spin-date-picker-popup');
this._buttons['select'] = document.getElementById('select-options-buttons');
this._buttons['select'].addEventListener('click', this);
this._buttons['time'] = document.getElementById('time-picker-buttons');
this._buttons['time'].addEventListener('click', this);
this._buttons['date'] = document.getElementById('spin-date-picker-buttons');
this._buttons['date'].addEventListener('click', this);
this._containers['time'] = context.querySelector('.picker-container');
this._containers['date'] = document.getElementById('spin-date-picker');
ActiveEffectHelper.enableActive(this._buttons['select']);
ActiveEffectHelper.enableActive(this._buttons['time']);
ActiveEffectHelper.enableActive(this._buttons['date']);
// 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.context = document.getElementById('time-picker');
var pickerElements = ['.value-picker-hours', '.value-picker-minutes',
'.value-picker-hour24-state'];
pickerElements.forEach(function(className) {
context.querySelector(className).addEventListener('mousedown', this);
}, this);
window.addEventListener('appopen', this);
window.addEventListener('appwillclose', this);
// invalidate the current spin date picker when language setting changes
navigator.mozSettings.addObserver('language.current',
(function language_change(e) {
if (this._datePicker) {
this._datePicker.uninit();
this._datePicker = null;
}}).bind(this));
},
handleEvent: function vs_handleEvent(evt) {
switch (evt.type) {
case 'appopen':
case 'appwillclose':
this.hide();
break;
case 'click':
var currentTarget = evt.currentTarget;
switch (currentTarget) {
case this._buttons['select']:
case this._buttons['time']:
case this._buttons['date']:
var target = evt.target;
if (target.dataset.type == 'cancel') {
this.cancel();
} else if (target.dataset.type == 'ok') {
this.confirm();
}
break;
case this._containers['select']:
this.handleSelect(evt.target);
break;
}
break;
case 'submit':
// Prevent the form from submit.
case 'mousedown':
// Prevent focus being taken away by us.
evt.preventDefault();
break;
default:
this.debug('no event handler defined for' + evt.type);
break;
}
},
handleSelect: function vs_handleSelect(target) {
if (target.dataset === undefined ||
(target.dataset.optionIndex === undefined &&
target.dataset.optionValue === undefined))
return;
if (this._currentPickerType === 'select-one') {
var selectee = this._containers['select'].
querySelectorAll('[aria-checked="true"]');
for (var i = 0; i < selectee.length; i++) {
selectee[i].removeAttribute('aria-checked');
}
target.setAttribute('aria-checked', 'true');
} else if (target.getAttribute('aria-checked') === 'true') {
target.removeAttribute('aria-checked');
} else {
target.setAttribute('aria-checked', 'true');
}
// setValue here to trigger change event
var singleOptionIndex;
var optionIndices = [];
var selectee = this._containers['select'].
querySelectorAll('[aria-checked="true"]');
if (this._currentPickerType === 'select-one') {
if (selectee.length > 0)
singleOptionIndex = selectee[0].dataset.optionIndex;
window.navigator.mozKeyboard.setSelectedOption(singleOptionIndex);
} else if (this._currentPickerType === 'select-multiple') {
// Multiple select case
for (var i = 0; i < selectee.length; i++) {
var index = parseInt(selectee[i].dataset.optionIndex);
optionIndices.push(index);
}
window.navigator.mozKeyboard.setSelectedOptions(optionIndices);
}
},
show: function vs_show(detail) {
this._element.hidden = false;
},
showPanel: function vs_showPanel(type) {
for (var p in this._containers) {
if (p === type) {
this._popups[p].hidden = false;
} else {
this._popups[p].hidden = true;
}
}
},
hide: function vs_hide() {
this._element.hidden = true;
},
cancel: function vs_cancel() {
this.debug('cancel invoked');
window.navigator.mozKeyboard.removeFocus();
this.hide();
},
confirm: function vs_confirm() {
if (this._currentPickerType === 'time') {
var timeValue = TimePicker.getTimeValue();
this.debug('output value: ' + timeValue);
window.navigator.mozKeyboard.setValue(timeValue);
} else if (this._currentPickerType === 'date') {
var dateValue = this._datePicker.value;
// The format should be 2012-09-19
dateValue = dateValue.toLocaleFormat('%Y-%m-%d');
this.debug('output value: ' + dateValue);
window.navigator.mozKeyboard.setValue(dateValue);
}
window.navigator.mozKeyboard.removeFocus();
this.hide();
},
showOptions: function vs_showOptions(detail) {
var options = null;
if (detail.choices && detail.choices.choices)
options = detail.choices.choices;
if (options)
this.buildOptions(options);
this.show();
this.showPanel('select');
},
buildOptions: function(options) {
var optionHTML = '';
function escapeHTML(str) {
var span = document.createElement('span');
span.textContent = str;
return span.innerHTML;
}
for (var i = 0, n = options.length; i < n; i++) {
var checked = options[i].selected ? ' aria-checked="true"' : '';
// This for attribute is created only to avoid applying
// a general rule in building block
var forAttribute = ' for="gaia-option-' + options[i].optionIndex + '"';
optionHTML += '<li data-option-index="' + options[i].optionIndex + '"' +
checked + '>' +
'<label' + forAttribute + '> <span>' +
escapeHTML(options[i].text) +
'</span></label>' +
'</li>';
}
var optionsContainer = document.querySelector(
'#value-selector-container ol');
if (!optionsContainer)
return;
optionsContainer.innerHTML = optionHTML;
// Apply different style when the options are more than 1 page
if (options.length > 5) {
this._containers['select'].classList.add('scrollable');
} else {
this._containers['select'].classList.remove('scrollable');
}
// Change the title for multiple select
var titleL10nId = 'choose-options';
if (this._currentPickerType === 'select-one')
titleL10nId = 'choose-option';
var optionsTitle = document.querySelector(
'#value-selector-container h1');
if (optionsTitle) {
optionsTitle.dataset.l10nId = titleL10nId;
optionsTitle.textContent = navigator.mozL10n.get(titleL10nId);
}
},
showTimePicker: function vs_showTimePicker(currentValue) {
this._currentPickerType = 'time';
this.show();
this.showPanel('time');
if (!this._timePickerInitialized) {
TimePicker.initTimePicker();
this._timePickerInitialized = true;
}
var time;
if (!currentValue) {
var now = new Date();
time = {
hours: now.getHours(),
minutes: now.getMinutes()
};
} else {
var inputParser = ValueSelector.InputParser;
if (!inputParser)
console.error('Cannot get input parser for value selector');
time = inputParser.importTime(currentValue);
}
var timePicker = TimePicker.timePicker;
// Set the value of time picker according to the current value
if (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;
timePicker.hour.setSelectedIndexByDisplayedText(hour);
timePicker.hour24State.setSelectedIndex(hour24State);
} else {
timePicker.hour.setSelectedIndex(time.hours);
}
timePicker.minute.setSelectedIndex(time.minutes);
},
showDatePicker: function vs_showDatePicker(currentValue) {
this._currentPickerType = 'date';
this.show();
this.showPanel('date');
if (!this._datePicker) {
this._datePicker = new SpinDatePicker(this._containers['date']);
}
// Show current date as default value
var date = new Date();
if (currentValue) {
var inputParser = ValueSelector.InputParser;
if (!inputParser)
console.error('Cannot get input parser for value selector');
date = inputParser.formatInputDate(currentValue, '');
}
this._datePicker.value = date;
}
};
var TimePicker = {
timePicker: {
hour: null,
minute: null,
hour24State: null,
is12hFormat: false
},
get hourSelector() {
delete this.hourSelector;
return this.hourSelector =
context.querySelector('.value-picker-hours');
},
get minuteSelector() {
delete this.minuteSelector;
return this.minuteSelector =
context.querySelector('.value-picker-minutes');
},
get hour24StateSelector() {
delete this.hour24StateSelector;
return this.hour24StateSelector =
context.querySelector('.value-picker-hour24-state');
},
initTimePicker: function tp_initTimePicker() {
var localeTimeFormat = navigator.mozL10n.get('dateTimeFormat_%X');
var is12hFormat = (localeTimeFormat.indexOf('%p') >= 0);
this.timePicker.is12hFormat = is12hFormat;
this.setTimePickerStyle();
var startHour = is12hFormat ? 1 : 0;
var endHour = is12hFormat ? (startHour + 12) : (startHour + 12 * 2);
var unitClassName = 'picker-unit';
var hourDisplayedText = [];
for (var i = startHour; i < endHour; i++) {
var value = i;
hourDisplayedText.push(value);
}
var hourUnitStyle = {
valueDisplayedText: hourDisplayedText,
className: unitClassName
};
this.timePicker.hour = new ValuePicker(this.hourSelector, hourUnitStyle);
var minuteDisplayedText = [];
for (var i = 0; i < 60; i++) {
var value = (i < 10) ? '0' + i : i;
minuteDisplayedText.push(value);
}
var minuteUnitStyle = {
valueDisplayedText: minuteDisplayedText,
className: unitClassName
};
this.timePicker.minute =
new ValuePicker(this.minuteSelector, minuteUnitStyle);
if (is12hFormat) {
var hour24StateUnitStyle = {
valueDisplayedText: ['AM', 'PM'],
className: unitClassName
};
this.timePicker.hour24State =
new ValuePicker(this.hour24StateSelector, hour24StateUnitStyle);
}
},
setTimePickerStyle: function tp_setTimePickerStyle() {
var style = (this.timePicker.is12hFormat) ? 'format12h' : 'format24h';
context.querySelector('.picker-container').classList.add(style);
},
// return a string for the time value, format: "16:37"
getTimeValue: function tp_getTimeValue() {
var hour = 0;
if (this.timePicker.is12hFormat) {
var hour24Offset = 12 * this.timePicker.hour24State.getSelectedIndex();
hour = this.timePicker.hour.getSelectedDisplayedText();
hour = (hour == 12) ? 0 : hour;
hour = hour + hour24Offset;
} else {
hour = this.timePicker.hour.getSelectedIndex();
}
var minute = this.timePicker.minute.getSelectedDisplayedText();
return hour + ':' + minute;
}
};
var ActiveEffectHelper = (function() {
var lastActiveElement = null;
function _setActive(element, isActive) {
if (isActive) {
element.classList.add('active');
lastActiveElement = element;
} else {
element.classList.remove('active');
if (lastActiveElement) {
lastActiveElement.classList.remove('active');
lastActiveElement = null;
}
}
}
function _onMouseDown(evt) {
var target = evt.target;
_setActive(target, true);
target.addEventListener('mouseleave', _onMouseLeave);
}
function _onMouseUp(evt) {
var target = evt.target;
_setActive(target, false);
target.removeEventListener('mouseleave', _onMouseLeave);
}
function _onMouseLeave(evt) {
var target = evt.target;
_setActive(target, false);
target.removeEventListener('mouseleave', _onMouseLeave);
}
var _events = {
'mousedown': _onMouseDown,
'mouseup': _onMouseUp
};
function _enableActive(element) {
// Attach event listeners
for (var event in _events) {
var callback = _events[event] || null;
if (callback)
element.addEventListener(event, callback);
}
}
return {
enableActive: _enableActive
};
})();
ValueSelector.init();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment