/** * A month picker component. This class is used by the {@link Ext.picker.Date Date picker} class * to allow browsing and selection of year/months combinations. */ Ext.define('Ext.picker.Month', { extend: 'Ext.Component', requires: ['Ext.XTemplate', 'Ext.util.ClickRepeater', 'Ext.Date', 'Ext.button.Button'], alias: 'widget.monthpicker', alternateClassName: 'Ext.MonthPicker', renderTpl: [ '<div id="{id}-bodyEl" class="{baseCls}-body">', '<div class="{baseCls}-months">', '<tpl for="months">', '<div class="{parent.baseCls}-item {parent.baseCls}-month"><a href="#" hidefocus="on">{.}</a></div>', '</tpl>', '</div>', '<div class="{baseCls}-years">', '<div class="{baseCls}-yearnav">', '<button id="{id}-prevEl" class="{baseCls}-yearnav-prev"></button>', '<button id="{id}-nextEl" class="{baseCls}-yearnav-next"></button>', '</div>', '<tpl for="years">', '<div class="{parent.baseCls}-item {parent.baseCls}-year"><a href="#" hidefocus="on">{.}</a></div>', '</tpl>', '</div>', '<div class="' + Ext.baseCSSPrefix + 'clear"></div>', '</div>', '<tpl if="showButtons">', '<div id="{id}-buttonsEl" class="{baseCls}-buttons"></div>', '</tpl>' ], /** * @cfg {String} okText The text to display on the ok button. */ okText: 'OK', /** * @cfg {String} cancelText The text to display on the cancel button. */ cancelText: 'Cancel', /** * @cfg {String} baseCls The base CSS class to apply to the picker element. Defaults to <tt>'x-monthpicker'</tt> */ baseCls: Ext.baseCSSPrefix + 'monthpicker', /** * @cfg {Boolean} showButtons True to show ok and cancel buttons below the picker. */ showButtons: true, /** * @cfg {String} selectedCls The class to be added to selected items in the picker. Defaults to * <tt>'x-monthpicker-selected'</tt> */ /** * @cfg {Date/Number[]} value The default value to set. See {@link #setValue} */ width: 178, // used when attached to date picker which isnt showing buttons smallCls: Ext.baseCSSPrefix + 'monthpicker-small', // private totalYears: 10, yearOffset: 5, // 10 years in total, 2 per row monthOffset: 6, // 12 months, 2 per row // private, inherit docs initComponent: function(){ var me = this; me.selectedCls = me.baseCls + '-selected'; me.addEvents( /** * @event cancelclick * Fires when the cancel button is pressed. * @param {Ext.picker.Month} this */ 'cancelclick', /** * @event monthclick * Fires when a month is clicked. * @param {Ext.picker.Month} this * @param {Array} value The current value */ 'monthclick', /** * @event monthdblclick * Fires when a month is clicked. * @param {Ext.picker.Month} this * @param {Array} value The current value */ 'monthdblclick', /** * @event okclick * Fires when the ok button is pressed. * @param {Ext.picker.Month} this * @param {Array} value The current value */ 'okclick', /** * @event select * Fires when a month/year is selected. * @param {Ext.picker.Month} this * @param {Array} value The current value */ 'select', /** * @event yearclick * Fires when a year is clicked. * @param {Ext.picker.Month} this * @param {Array} value The current value */ 'yearclick', /** * @event yeardblclick * Fires when a year is clicked. * @param {Ext.picker.Month} this * @param {Array} value The current value */ 'yeardblclick' ); if (me.small) { me.addCls(me.smallCls); } me.setValue(me.value); me.activeYear = me.getYear(new Date().getFullYear() - 4, -4); this.callParent(); }, // private, inherit docs onRender: function(ct, position){ var me = this, i = 0, months = [], shortName = Ext.Date.getShortMonthName, monthLen = me.monthOffset; for (; i < monthLen; ++i) { months.push(shortName(i), shortName(i + monthLen)); } Ext.apply(me.renderData, { months: months, years: me.getYears(), showButtons: me.showButtons }); me.addChildEls('bodyEl', 'prevEl', 'nextEl', 'buttonsEl'); me.callParent(arguments); }, // private, inherit docs afterRender: function(){ var me = this, body = me.bodyEl, buttonsEl = me.buttonsEl; me.callParent(); me.mon(body, 'click', me.onBodyClick, me); me.mon(body, 'dblclick', me.onBodyClick, me); // keep a reference to the year/month elements since we'll be re-using them me.years = body.select('.' + me.baseCls + '-year a'); me.months = body.select('.' + me.baseCls + '-month a'); if (me.showButtons) { me.okBtn = Ext.create('Ext.button.Button', { text: me.okText, renderTo: buttonsEl, handler: me.onOkClick, scope: me }); me.cancelBtn = Ext.create('Ext.button.Button', { text: me.cancelText, renderTo: buttonsEl, handler: me.onCancelClick, scope: me }); } me.backRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, { handler: Ext.Function.bind(me.adjustYear, me, [-me.totalYears]) }); me.prevEl.addClsOnOver(me.baseCls + '-yearnav-prev-over'); me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, { handler: Ext.Function.bind(me.adjustYear, me, [me.totalYears]) }); me.nextEl.addClsOnOver(me.baseCls + '-yearnav-next-over'); me.updateBody(); }, /** * Set the value for the picker. * @param {Date/Number[]} value The value to set. It can be a Date object, where the month/year will be extracted, or * it can be an array, with the month as the first index and the year as the second. * @return {Ext.picker.Month} this */ setValue: function(value){ var me = this, active = me.activeYear, offset = me.monthOffset, year, index; if (!value) { me.value = [null, null]; } else if (Ext.isDate(value)) { me.value = [value.getMonth(), value.getFullYear()]; } else { me.value = [value[0], value[1]]; } if (me.rendered) { year = me.value[1]; if (year !== null) { if ((year < active || year > active + me.yearOffset)) { me.activeYear = year - me.yearOffset + 1; } } me.updateBody(); } return me; }, /** * Gets the selected value. It is returned as an array [month, year]. It may * be a partial value, for example [null, 2010]. The month is returned as * 0 based. * @return {Number[]} The selected value */ getValue: function(){ return this.value; }, /** * Checks whether the picker has a selection * @return {Boolean} Returns true if both a month and year have been selected */ hasSelection: function(){ var value = this.value; return value[0] !== null && value[1] !== null; }, /** * Get an array of years to be pushed in the template. It is not in strict * numerical order because we want to show them in columns. * @private * @return {Number[]} An array of years */ getYears: function(){ var me = this, offset = me.yearOffset, start = me.activeYear, // put the "active" year on the left end = start + offset, i = start, years = []; for (; i < end; ++i) { years.push(i, i + offset); } return years; }, /** * Update the years in the body based on any change * @private */ updateBody: function(){ var me = this, years = me.years, months = me.months, yearNumbers = me.getYears(), cls = me.selectedCls, value = me.getYear(null), month = me.value[0], monthOffset = me.monthOffset, year; if (me.rendered) { years.removeCls(cls); months.removeCls(cls); years.each(function(el, all, index){ year = yearNumbers[index]; el.dom.innerHTML = year; if (year == value) { el.dom.className = cls; } }); if (month !== null) { if (month < monthOffset) { month = month * 2; } else { month = (month - monthOffset) * 2 + 1; } months.item(month).addCls(cls); } } }, /** * Gets the current year value, or the default. * @private * @param {Number} defaultValue The default value to use if the year is not defined. * @param {Number} offset A number to offset the value by * @return {Number} The year value */ getYear: function(defaultValue, offset) { var year = this.value[1]; offset = offset || 0; return year === null ? defaultValue : year + offset; }, /** * React to clicks on the body * @private */ onBodyClick: function(e, t) { var me = this, isDouble = e.type == 'dblclick'; if (e.getTarget('.' + me.baseCls + '-month')) { e.stopEvent(); me.onMonthClick(t, isDouble); } else if (e.getTarget('.' + me.baseCls + '-year')) { e.stopEvent(); me.onYearClick(t, isDouble); } }, /** * Modify the year display by passing an offset. * @param {Number} [offset=10] The offset to move by. */ adjustYear: function(offset){ if (typeof offset != 'number') { offset = this.totalYears; } this.activeYear += offset; this.updateBody(); }, /** * React to the ok button being pressed * @private */ onOkClick: function(){ this.fireEvent('okclick', this, this.value); }, /** * React to the cancel button being pressed * @private */ onCancelClick: function(){ this.fireEvent('cancelclick', this); }, /** * React to a month being clicked * @private * @param {HTMLElement} target The element that was clicked * @param {Boolean} isDouble True if the event was a doubleclick */ onMonthClick: function(target, isDouble){ var me = this; me.value[0] = me.resolveOffset(me.months.indexOf(target), me.monthOffset); me.updateBody(); me.fireEvent('month' + (isDouble ? 'dbl' : '') + 'click', me, me.value); me.fireEvent('select', me, me.value); }, /** * React to a year being clicked * @private * @param {HTMLElement} target The element that was clicked * @param {Boolean} isDouble True if the event was a doubleclick */ onYearClick: function(target, isDouble){ var me = this; me.value[1] = me.activeYear + me.resolveOffset(me.years.indexOf(target), me.yearOffset); me.updateBody(); me.fireEvent('year' + (isDouble ? 'dbl' : '') + 'click', me, me.value); me.fireEvent('select', me, me.value); }, /** * Returns an offsetted number based on the position in the collection. Since our collections aren't * numerically ordered, this function helps to normalize those differences. * @private * @param {Object} index * @param {Object} offset * @return {Number} The correctly offsetted number */ resolveOffset: function(index, offset){ if (index % 2 === 0) { return (index / 2); } else { return offset + Math.floor(index / 2); } }, // private, inherit docs beforeDestroy: function(){ var me = this; me.years = me.months = null; Ext.destroyMembers(me, 'backRepeater', 'nextRepeater', 'okBtn', 'cancelBtn'); me.callParent(); } });