diff options
Diffstat (limited to 'apps/system/js/value_selector/spin_date_picker.js')
-rw-r--r-- | apps/system/js/value_selector/spin_date_picker.js | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/apps/system/js/value_selector/spin_date_picker.js b/apps/system/js/value_selector/spin_date_picker.js new file mode 100644 index 0000000..feb0602 --- /dev/null +++ b/apps/system/js/value_selector/spin_date_picker.js @@ -0,0 +1,341 @@ +/** + * 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; + */ +var SpinDatePicker = (function SpinDatePicker() { + 'use strict'; + + var FIRST_YEAR = 1900; + var LAST_YEAR = 2099; + + function getYearText() { + var yearText = []; + var dateTimeFormat = navigator.mozL10n.DateTimeFormat(); + + for (var i = FIRST_YEAR; i <= LAST_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(0); + 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(days) { + var dateText = []; + var date = new Date(0); + var dateTimeFormat = navigator.mozL10n.DateTimeFormat(); + + for (var i = 1; i <= days; 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(format) { + 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) { + this.element = element; + + this.yearPicker = null; + this.monthPicker = null; + this.datePickers = { + '28': null, + '29': null, + '30': null, + '31': 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 tmpDatePickerContainers = + element.querySelectorAll('.value-picker-date'); + var datePickerContainers = { + '28': tmpDatePickerContainers[0], + '29': tmpDatePickerContainers[1], + '30': tmpDatePickerContainers[2], + '31': tmpDatePickerContainers[3] + }; + + var updateCurrentValue = function spd_updateCurrentValue() { + var selectedYear = this.yearPicker.getSelectedIndex() + FIRST_YEAR; + var selectedMonth = this.monthPicker.getSelectedIndex(); + var days = getDaysInMonth(selectedYear, selectedMonth); + var datePicker = this.datePickers[days]; + var selectedDate = datePicker.getSelectedIndex() + 1; + + this._value = new Date(selectedYear, selectedMonth, selectedDate); + }; + + var updateDatePickerVisibility = + function spd_updateDatePickerVisibility() { + var days = getDaysInMonth(this.yearPicker.getSelectedIndex() + + FIRST_YEAR, this.monthPicker.getSelectedIndex()); + for (var i = 28; i <= 31; i++) { + datePickerContainers[i].hidden = true; + this.datePickers[i].setSelectedIndex(this._currentSelectedDateIndex); + } + datePickerContainers[days].hidden = false; + }; + + var onvaluechangeInternal = + function spd_onvaluechangeInternal(newDateValue) { + this.yearPicker.setSelectedIndex(newDateValue.getFullYear() - FIRST_YEAR); + this.monthPicker.setSelectedIndex(newDateValue.getMonth()); + for (var i = 28; i <= 31; i++) { + this.datePickers[i].setSelectedIndex(newDateValue.getDate() - 1); + } + updateDatePickerVisibility.apply(this); + updateCurrentValue.apply(this); + }; + + var onSelectedYearChanged = + function spd_onSelectedYearChanged(selectedYear) { + updateDatePickerVisibility.apply(this); + updateCurrentValue.apply(this); + }; + + var onSelectedMonthChanged = + function spd_onSelectedMonthChanged(selectedMonth) { + updateDatePickerVisibility.apply(this); + updateCurrentValue.apply(this); + }; + + var onSelectedDateChanged = + function spd_onSelectedDateChanged(selectedDate) { + this._currentSelectedDateIndex = 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 + for (var i = 28; i <= 31; i++) { + var datePickerContainer = datePickerContainers[i]; + var dateUnitStyle = { + valueDisplayedText: getDateText(i), + className: unitClassName + }; + var datePicker = this.datePickers[i]; + + if (datePicker) + datePicker.uninit(); + datePickerContainer.hidden = false; + this.datePickers[i] = new ValuePicker(datePickerContainer, dateUnitStyle); + this.datePickers[i].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]; + for (var i = 28; i <= 31; i++) { + this.pickerElements.push(datePickerContainers[i]); + } + + 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; + } + }, + + uninit: function() { + if (this.yearPicker) + this.yearPicker.uninit(); + if (this.monthPicker) + this.monthPicker.uninit(); + if (this.datePickers) { + for (var i = 28; i <= 31; i++) { + var datePicker = this.datePickers[i]; + 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; +}()); |