3 * @class Ext.picker.Month
4 * @extends Ext.Component
5 * <p>A month picker component. This class is used by the {@link Ext.picker.Date DatePicker} class
6 * to allow browsing and selection of year/months combinations.</p>
8 * Create a new MonthPicker
9 * @param {Object} config The config object
13 Ext.define('Ext.picker.Month', {
14 extend: 'Ext.Component',
15 requires: ['Ext.XTemplate', 'Ext.util.ClickRepeater', 'Ext.Date', 'Ext.button.Button'],
16 alias: 'widget.monthpicker',
17 alternateClassName: 'Ext.MonthPicker',
20 '<div class="{baseCls}-body">',
21 '<div class="{baseCls}-months">',
23 '<div class="{parent.baseCls}-item {parent.baseCls}-month"><a href="#" hidefocus="on">{.}</a></div>',
26 '<div class="{baseCls}-years">',
27 '<div class="{baseCls}-yearnav">',
28 '<button class="{baseCls}-yearnav-prev"></button>',
29 '<button class="{baseCls}-yearnav-next"></button>',
32 '<div class="{parent.baseCls}-item {parent.baseCls}-year"><a href="#" hidefocus="on">{.}</a></div>',
36 '<div class="' + Ext.baseCSSPrefix + 'clear"></div>',
37 '<tpl if="showButtons">',
38 '<div class="{baseCls}-buttons"></div>',
43 * @cfg {String} okText The text to display on the ok button. Defaults to <tt>'OK'</tt>
48 * @cfg {String} cancelText The text to display on the cancel button. Defaults to <tt>'Cancel'</tt>
53 * @cfg {String} baseCls The base CSS class to apply to the picker element. Defaults to <tt>'x-monthpicker'</tt>
55 baseCls: Ext.baseCSSPrefix + 'monthpicker',
58 * @cfg {Boolean} showButtons True to show ok and cancel buttons below the picker. Defaults to <tt>true</tt>.
63 * @cfg {String} selectedCls The class to be added to selected items in the picker. Defaults to
64 * <tt>'x-monthpicker-selected'</tt>
68 * @cfg {Date/Array} value The default value to set. See {#setValue setValue}
78 yearOffset: 5, // 10 years in total, 2 per row
79 monthOffset: 6, // 12 months, 2 per row
81 // private, inherit docs
82 initComponent: function(){
85 me.selectedCls = me.baseCls + '-selected';
89 * Fires when the cancel button is pressed.
90 * @param {Ext.picker.Month} this
96 * Fires when a month is clicked.
97 * @param {Ext.picker.Month} this
98 * @param {Array} value The current value
103 * @event monthdblclick
104 * Fires when a month is clicked.
105 * @param {Ext.picker.Month} this
106 * @param {Array} value The current value
112 * Fires when the ok button is pressed.
113 * @param {Ext.picker.Month} this
114 * @param {Array} value The current value
120 * Fires when a month/year is selected.
121 * @param {Ext.picker.Month} this
122 * @param {Array} value The current value
128 * Fires when a year is clicked.
129 * @param {Ext.picker.Month} this
130 * @param {Array} value The current value
135 * @event yeardblclick
136 * Fires when a year is clicked.
137 * @param {Ext.picker.Month} this
138 * @param {Array} value The current value
143 me.setValue(me.value);
144 me.activeYear = me.getYear(new Date().getFullYear() - 4, -4);
148 // private, inherit docs
149 onRender: function(ct, position){
153 shortName = Ext.Date.getShortMonthName,
154 monthLen = me.monthOffset;
156 for (; i < monthLen; ++i) {
157 months.push(shortName(i), shortName(i + monthLen));
160 Ext.apply(me.renderData, {
162 years: me.getYears(),
163 showButtons: me.showButtons
166 Ext.apply(me.renderSelectors, {
167 bodyEl: '.' + me.baseCls + '-body',
168 prevEl: '.' + me.baseCls + '-yearnav-prev',
169 nextEl: '.' + me.baseCls + '-yearnav-next',
170 buttonsEl: '.' + me.baseCls + '-buttons'
172 this.callParent([ct, position]);
175 // private, inherit docs
176 afterRender: function(){
179 buttonsEl = me.buttonsEl;
183 me.mon(body, 'click', me.onBodyClick, me);
184 me.mon(body, 'dblclick', me.onBodyClick, me);
186 // keep a reference to the year/month elements since we'll be re-using them
187 me.years = body.select('.' + me.baseCls + '-year a');
188 me.months = body.select('.' + me.baseCls + '-month a');
190 if (me.showButtons) {
191 me.okBtn = Ext.create('Ext.button.Button', {
194 handler: me.onOkClick,
197 me.cancelBtn = Ext.create('Ext.button.Button', {
200 handler: me.onCancelClick,
205 me.backRepeater = Ext.create('Ext.util.ClickRepeater', me.prevEl, {
206 handler: Ext.Function.bind(me.adjustYear, me, [-me.totalYears])
209 me.prevEl.addClsOnOver(me.baseCls + '-yearnav-prev-over');
210 me.nextRepeater = Ext.create('Ext.util.ClickRepeater', me.nextEl, {
211 handler: Ext.Function.bind(me.adjustYear, me, [me.totalYears])
213 me.nextEl.addClsOnOver(me.baseCls + '-yearnav-next-over');
218 * Set the value for the picker.
219 * @param {Date/Array} value The value to set. It can be a Date object, where the month/year will be extracted, or
220 * it can be an array, with the month as the first index and the year as the second.
221 * @return {Ext.picker.Month} this
223 setValue: function(value){
225 active = me.activeYear,
226 offset = me.monthOffset,
231 me.value = [null, null];
232 } else if (Ext.isDate(value)) {
233 me.value = [value.getMonth(), value.getFullYear()];
235 me.value = [value[0], value[1]];
241 if ((year < active || year > active + me.yearOffset)) {
242 me.activeYear = year - me.yearOffset + 1;
252 * Gets the selected value. It is returned as an array [month, year]. It may
253 * be a partial value, for example [null, 2010]. The month is returned as
255 * @return {Array} The selected value
257 getValue: function(){
262 * Checks whether the picker has a selection
263 * @return {Boolean} Returns true if both a month and year have been selected
265 hasSelection: function(){
266 var value = this.value;
267 return value[0] !== null && value[1] !== null;
271 * Get an array of years to be pushed in the template. It is not in strict
272 * numerical order because we want to show them in columns.
274 * @return {Array} An array of years
276 getYears: function(){
278 offset = me.yearOffset,
279 start = me.activeYear, // put the "active" year on the left
280 end = start + offset,
284 for (; i < end; ++i) {
285 years.push(i, i + offset);
292 * Update the years in the body based on any change
295 updateBody: function(){
299 yearNumbers = me.getYears(),
300 cls = me.selectedCls,
301 value = me.getYear(null),
303 monthOffset = me.monthOffset,
307 years.removeCls(cls);
308 months.removeCls(cls);
309 years.each(function(el, all, index){
310 year = yearNumbers[index];
311 el.dom.innerHTML = year;
313 el.dom.className = cls;
316 if (month !== null) {
317 if (month < monthOffset) {
320 month = (month - monthOffset) * 2 + 1;
322 months.item(month).addCls(cls);
328 * Gets the current year value, or the default.
330 * @param {Number} defaultValue The default value to use if the year is not defined.
331 * @param {Number} offset A number to offset the value by
332 * @return {Number} The year value
334 getYear: function(defaultValue, offset) {
335 var year = this.value[1];
336 offset = offset || 0;
337 return year === null ? defaultValue : year + offset;
341 * React to clicks on the body
344 onBodyClick: function(e, t) {
346 isDouble = e.type == 'dblclick';
348 if (e.getTarget('.' + me.baseCls + '-month')) {
350 me.onMonthClick(t, isDouble);
351 } else if (e.getTarget('.' + me.baseCls + '-year')) {
353 me.onYearClick(t, isDouble);
358 * Modify the year display by passing an offset.
359 * @param {Number} offset The offset to move by. If not specified, it defaults to 10.
361 adjustYear: function(offset){
362 if (typeof offset != 'number') {
363 offset = this.totalYears;
365 this.activeYear += offset;
370 * React to the ok button being pressed
373 onOkClick: function(){
374 this.fireEvent('okclick', this, this.value);
378 * React to the cancel button being pressed
381 onCancelClick: function(){
382 this.fireEvent('cancelclick', this);
386 * React to a month being clicked
388 * @param {HTMLElement} target The element that was clicked
389 * @param {Boolean} isDouble True if the event was a doubleclick
391 onMonthClick: function(target, isDouble){
393 me.value[0] = me.resolveOffset(me.months.indexOf(target), me.monthOffset);
395 me.fireEvent('month' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
396 me.fireEvent('select', me, me.value);
400 * React to a year being clicked
402 * @param {HTMLElement} target The element that was clicked
403 * @param {Boolean} isDouble True if the event was a doubleclick
405 onYearClick: function(target, isDouble){
407 me.value[1] = me.activeYear + me.resolveOffset(me.years.indexOf(target), me.yearOffset);
409 me.fireEvent('year' + (isDouble ? 'dbl' : '') + 'click', me, me.value);
410 me.fireEvent('select', me, me.value);
415 * Returns an offsetted number based on the position in the collection. Since our collections aren't
416 * numerically ordered, this function helps to normalize those differences.
418 * @param {Object} index
419 * @param {Object} offset
420 * @return {Number} The correctly offsetted number
422 resolveOffset: function(index, offset){
423 if (index % 2 === 0) {
426 return offset + Math.floor(index / 2);
430 // private, inherit docs
431 beforeDestroy: function(){
433 me.years = me.months = null;
434 Ext.destroyMembers('backRepeater', 'nextRepeater', 'okBtn', 'cancelBtn');