-
6 Dec 2007 2:21 PM #1
MonthPicker
MonthPicker
Hi folks,
I'm trying to implement a MonthPicker by extracting the month picking code out of the DatePicker. I'm also trying to make a MonthMenu like the DateMenu - that's my eventual goal. I've started with this code, but I'm running into problems left and right, so it's probably due to my lack of understanding of how to make extensions. Can anyone help please?!
Thanks so much!
Joe
** UPDATE **
I've got it working nicely on Firefox but I'm having some trouble on IE:
What I have is an icon on the screen that I launch the MonthMenu from.
The code here is updated and should work with Firefox. What happens with IE is:
1. the width of the menu never changes, and is too small. Whatever I try, I can't get the menu to fit the MonthPicker.
2. The first time opening the menu, the MonthPicker flashes then goes away leaving a white blank menu (which is to narrow - see #1).
** Update #2 **
I've got it working on both Firefox and IE. Enjoy!
Here's the code I have so far:
PHP Code:Ext.namespace('Ext.ux');
/**
* @class Ext.ux.MonthPicker
* @extends Ext.Component
* A picker that allows you to select a month and year
* @constructor
* @param {Object} config Configuration options
* @author Joseph Kralicky
* @version 0.1
*/
Ext.ux.MonthPicker = Ext.extend(Ext.Component, {
format : "M, Y",
okText : " OK ",
cancelText : "Cancel",
constrainToViewport : true,
monthNames : Date.monthNames,
startDay : 0,
value : 0,
noPastYears: true, // only use the current year and future years
initComponent: function(){
Ext.ux.MonthPicker.superclass.initComponent.call(this);
this.value = this.value ?
this.value.clearTime() : new Date().clearTime();
this.addEvents(
'select'
);
if(this.handler){
this.on("select", this.handler, this.scope || this);
}
},
focus : function(){
if(this.el){
this.update(this.activeDate);
}
},
onRender : function(container, position){
var m = [ '<div style="width: 175px; height:175px;"></div>' ]
m[m.length] = '<div class="x-date-mp"></div>';
var el = document.createElement("div");
el.className = "x-date-picker";
el.innerHTML = m.join("");
container.dom.insertBefore(el, position);
this.el = Ext.get(el);
this.monthPicker = this.el.down('div.x-date-mp');
this.monthPicker.enableDisplayMode('block');
this.el.unselectable();
this.showMonthPicker();
if(Ext.isIE){
this.el.repaint();
}
this.update(this.value);
},
createMonthPicker : function(){
if(!this.monthPicker.dom.firstChild){
var buf = ['<table border="0" cellspacing="0">'];
for(var i = 0; i < 6; i++){
buf.push(
'<tr><td class="x-date-mp-month"><a href="#">', this.monthNames[i].substr(0, 3), '</a></td>',
'<td class="x-date-mp-month x-date-mp-sep"><a href="#">', this.monthNames[i+6].substr(0, 3), '</a></td>',
i == 0 ?
'<td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-prev"></a></td><td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-next"></a></td></tr>' :
'<td class="x-date-mp-year"><a href="#"></a></td><td class="x-date-mp-year"><a href="#"></a></td></tr>'
);
}
buf.push(
'<tr class="x-date-mp-btns"><td colspan="4"><button type="button" class="x-date-mp-ok">',
this.okText,
'</button><button type="button" class="x-date-mp-cancel">',
this.cancelText,
'</button></td></tr>',
'</table>'
);
this.monthPicker.update(buf.join(''));
this.monthPicker.on('click', this.onMonthClick, this);
this.monthPicker.on('dblclick', this.onMonthDblClick, this);
this.mpMonths = this.monthPicker.select('td.x-date-mp-month');
this.mpYears = this.monthPicker.select('td.x-date-mp-year');
this.mpMonths.each(function(m, a, i){
i += 1;
if((i%2) == 0){
m.dom.xmonth = 5 + Math.round(i * .5);
}else{
m.dom.xmonth = Math.round((i-1) * .5);
}
});
}
},
showMonthPicker : function(){
this.createMonthPicker();
var size = this.el.getSize();
this.monthPicker.setSize(size);
this.monthPicker.child('table').setSize(size);
this.mpSelMonth = (this.activeDate || this.value).getMonth();
this.updateMPMonth(this.mpSelMonth);
this.mpSelYear = (this.activeDate || this.value).getFullYear();
this.updateMPYear(this.mpSelYear);
this.monthPicker.show();
//this.monthPicker.slideIn('t', {duration:.2});
},
updateMPYear : function(y){
if ( this.noPastYears ) {
var minYear = new Date().getFullYear();
if ( y < (minYear+4) ) {
y = minYear+4;
}
}
this.mpyear = y;
var ys = this.mpYears.elements;
for(var i = 1; i <= 10; i++){
var td = ys[i-1], y2;
if((i%2) == 0){
y2 = y + Math.round(i * .5);
td.firstChild.innerHTML = y2;
td.xyear = y2;
}else{
y2 = y - (5-Math.round(i * .5));
td.firstChild.innerHTML = y2;
td.xyear = y2;
}
this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel');
}
},
updateMPMonth : function(sm){
this.mpMonths.each(function(m, a, i){
m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel');
});
},
selectMPMonth: function(m){
},
onMonthClick : function(e, t){
e.stopEvent();
var el = new Ext.Element(t), pn;
if(el.is('button.x-date-mp-cancel')){
this.hideMonthPicker();
//this.fireEvent("select", this, this.value);
}
else if(el.is('button.x-date-mp-ok')){
this.update(new Date(this.mpSelYear, this.mpSelMonth, (this.activeDate || this.value).getDate()));
//this.hideMonthPicker();
this.fireEvent("select", this, this.value);
}
else if(pn = el.up('td.x-date-mp-month', 2)){
this.mpMonths.removeClass('x-date-mp-sel');
pn.addClass('x-date-mp-sel');
this.mpSelMonth = pn.dom.xmonth;
}
else if(pn = el.up('td.x-date-mp-year', 2)){
this.mpYears.removeClass('x-date-mp-sel');
pn.addClass('x-date-mp-sel');
this.mpSelYear = pn.dom.xyear;
}
else if(el.is('a.x-date-mp-prev')){
this.updateMPYear(this.mpyear-10);
}
else if(el.is('a.x-date-mp-next')){
this.updateMPYear(this.mpyear+10);
}
},
onMonthDblClick : function(e, t){
e.stopEvent();
var el = new Ext.Element(t), pn;
if(pn = el.up('td.x-date-mp-month', 2)){
this.update(new Date(this.mpSelYear, pn.dom.xmonth, (this.activeDate || this.value).getDate()));
//this.hideMonthPicker();
this.fireEvent("select", this, this.value);
}
else if(pn = el.up('td.x-date-mp-year', 2)){
this.update(new Date(pn.dom.xyear, this.mpSelMonth, (this.activeDate || this.value).getDate()));
//this.hideMonthPicker();
this.fireEvent("select", this, this.value);
}
},
hideMonthPicker : function(disableAnim){
Ext.menu.MenuMgr.hideAll();
},
showPrevMonth : function(e){
this.update(this.activeDate.add("mo", -1));
},
showNextMonth : function(e){
this.update(this.activeDate.add("mo", 1));
},
showPrevYear : function(){
this.update(this.activeDate.add("y", -1));
},
showNextYear : function(){
this.update(this.activeDate.add("y", 1));
},
update : function( date ) {
this.activeDate = date;
this.value = date;
if(!this.internalRender){
var main = this.el.dom.firstChild;
var w = main.offsetWidth;
this.el.setWidth(w + this.el.getBorderWidth("lr"));
Ext.fly(main).setWidth(w);
this.internalRender = true;
if(Ext.isOpera && !this.secondPass){
main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + "px";
this.secondPass = true;
this.update.defer(10, this, [date]);
}
}
}
});
Ext.reg('monthpicker', Ext.ux.MonthPicker);
Ext.ux.MonthItem = function(config){
Ext.ux.MonthItem.superclass.constructor.call(this, new Ext.ux.MonthPicker(config), config);
this.picker = this.component;
this.addEvents('select');
this.picker.on("render", function(picker){
picker.getEl().swallowEvent("click");
picker.container.addClass("x-menu-date-item");
});
this.picker.on("select", this.onSelect, this);
};
Ext.extend(Ext.ux.MonthItem, Ext.menu.Adapter, {
onSelect : function(picker, date){
this.fireEvent("select", this, date, picker);
Ext.ux.MonthItem.superclass.handleClick.call(this);
}
});
Ext.ux.MonthMenu = function(config){
Ext.ux.MonthMenu.superclass.constructor.call(this, config);
this.plain = true;
var mi = new Ext.ux.MonthItem(config);
this.add(mi);
this.picker = mi.picker;
this.relayEvents(mi, ["select"]);
};
Ext.extend(Ext.ux.MonthMenu, Ext.menu.Menu, {
cls:'x-date-menu'
});
Last edited by JKralicky; 11 Dec 2007 at 9:32 AM. Reason: Got it to work!
-
10 Dec 2007 7:18 AM #2
I updated the code which now works on Firefox but not IE. See the updated post above.
Thanks for any help!
Joe
-
11 Dec 2007 9:33 AM #3
Ext 2.0 MonthPicker
Ext 2.0 MonthPicker
Hi folks,
I've got the MonthPicker working on IE and Firefox now! Enjoy and let me know if it works for you (or not)!
See the first post for the file.
Thanks!
Joe
-
11 Dec 2007 8:05 PM #4
Brilliant! You rock! I needed this for a credit card expiry field.
-
12 Dec 2007 6:54 AM #5
Thanks! Glad you found it useful.
Thanks also (mostly) go to the Ext team for writing this great framework!
-
17 Dec 2007 5:57 AM #6
-
18 Dec 2007 6:04 AM #7
Hi Shenliu,
If you want to use this as a menu item, use MonthMenu like DateMenu. If you want to use it as a standalone component, use MonthPicker like DatePicker. See the Ext 2.0 Documentation for how to use DateMenu and DatePicker.
Also, for my own purposes, I added a configuration item: "noPastYears = true" which does not show years in the past by default. You can set this to false if you want to be able to select years past.
Good luck!
Joe
-
19 Dec 2007 4:57 AM #8
-
19 Dec 2007 7:39 AM #9
Thanks a bunch I am using this as a menu item.
Post this in the user extension!
Rob.
-
4 Jan 2008 3:32 AM #10
Works well in Ext 1.1
Works well in Ext 1.1
Thanks for this nice extension!
I've tried it with Ext 1.1 and it works with just one mod: remove the following line:
Actually, I've made some further extensions to it, and added a MonthField based on DateField. One new config attribute has been added (useDayDate), that allows you to specify a specific day of the month that should be used to construct the resulting Date().Code:Ext.reg('monthpicker', Ext.ux.MonthPicker);
Set this to null to keep the old functionality.
If this value is set to 31 and the selected month only has 28 days, the minimum value will be used.
Example usage:Extension:Code:new Ext.ux.MonthMenu({ handler : function(dp, date){ alert(date.format('M j, Y')); } }); new Ext.ux.MonthField({ format : 'Y-m-d', useDayDate : 31 });Code:Ext.namespace('Ext.ux'); /** * @class Ext.ux.MonthPicker * @extends Ext.Component * A picker that allows you to select a month and year * @constructor * @param {Object} config Configuration options * @author Joseph Kralicky * @version 0.1 */ Ext.ux.MonthPicker = Ext.extend(Ext.Component, { format : "M, Y", okText : Ext.MessageBox.buttonText.ok, cancelText : Ext.MessageBox.buttonText.cancel, constrainToViewport : true, monthNames : Date.monthNames, value : 0, noPastYears : true, // only use the current year and future years useDayDate : null, // set to a number between 1-31 to use this day when creating the resulting date object (or null to use todays date or keep existing) initComponent: function(){ Ext.ux.MonthPicker.superclass.initComponent.call(this); this.value = this.value ? this.value.clearTime() : new Date().clearTime(); this.addEvents( 'select' ); if(this.handler){ this.on("select", this.handler, this.scope || this); } }, focus : function(){ if(this.el){ this.update(this.activeDate); } }, onRender : function(container, position){ var m = [ '<div style="width: 175px; height:175px;"></div>' ] m[m.length] = '<div class="x-date-mp"></div>'; var el = document.createElement("div"); el.className = "x-date-picker"; el.innerHTML = m.join(""); container.dom.insertBefore(el, position); this.el = Ext.get(el); this.monthPicker = this.el.down('div.x-date-mp'); this.monthPicker.enableDisplayMode('block'); this.el.unselectable(); this.showMonthPicker(); if(Ext.isIE){ this.el.repaint(); } this.update(this.value); }, createMonthPicker : function(){ if(!this.monthPicker.dom.firstChild){ var buf = ['<table border="0" cellspacing="0">']; for(var i = 0; i < 6; i++){ buf.push( '<tr><td class="x-date-mp-month"><a href="#">', this.monthNames[i].substr(0, 3), '</a></td>', '<td class="x-date-mp-month x-date-mp-sep"><a href="#">', this.monthNames[i+6].substr(0, 3), '</a></td>', i == 0 ? '<td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-prev"></a></td><td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-next"></a></td></tr>' : '<td class="x-date-mp-year"><a href="#"></a></td><td class="x-date-mp-year"><a href="#"></a></td></tr>' ); } buf.push( '<tr class="x-date-mp-btns"><td colspan="4"><button type="button" class="x-date-mp-ok">', this.okText, '</button><button type="button" class="x-date-mp-cancel">', this.cancelText, '</button></td></tr>', '</table>' ); this.monthPicker.update(buf.join('')); this.monthPicker.on('click', this.onMonthClick, this); this.monthPicker.on('dblclick', this.onMonthDblClick, this); this.mpMonths = this.monthPicker.select('td.x-date-mp-month'); this.mpYears = this.monthPicker.select('td.x-date-mp-year'); this.mpMonths.each(function(m, a, i){ i += 1; if((i%2) == 0){ m.dom.xmonth = 5 + Math.round(i * .5); }else{ m.dom.xmonth = Math.round((i-1) * .5); } }); } }, showMonthPicker : function(){ this.createMonthPicker(); var size = this.el.getSize(); this.monthPicker.setSize(size); this.monthPicker.child('table').setSize(size); this.mpSelMonth = (this.activeDate || this.value).getMonth(); this.updateMPMonth(this.mpSelMonth); this.mpSelYear = (this.activeDate || this.value).getFullYear(); this.updateMPYear(this.mpSelYear); this.monthPicker.show(); //this.monthPicker.slideIn('t', {duration:.2}); }, updateMPYear : function(y){ if ( this.noPastYears ) { var minYear = new Date().getFullYear(); if ( y < (minYear+4) ) { y = minYear+4; } } this.mpyear = y; var ys = this.mpYears.elements; for(var i = 1; i <= 10; i++){ var td = ys[i-1], y2; if((i%2) == 0){ y2 = y + Math.round(i * .5); td.firstChild.innerHTML = y2; td.xyear = y2; }else{ y2 = y - (5-Math.round(i * .5)); td.firstChild.innerHTML = y2; td.xyear = y2; } this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel'); } }, updateMPMonth : function(sm){ this.mpMonths.each(function(m, a, i){ m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel'); }); }, selectMPMonth: function(m){ }, getAdjustedDate : function (year,month){ return new Date( year, month, this.useDayDate ? // use a specific day date? (Math.min(this.useDayDate, (new Date(year, month, 1)).getDaysInMonth())) // yes, cap it to month max : (this.activeDate || this.value).getDate() // keep existing ); }, onMonthClick : function(e, t){ e.stopEvent(); var el = new Ext.Element(t), pn; if(el.is('button.x-date-mp-cancel')){ this.hideMonthPicker(); //this.fireEvent("select", this, this.value); } else if(el.is('button.x-date-mp-ok')){ this.update(this.getAdjustedDate(this.mpSelYear, this.mpSelMonth)); //this.hideMonthPicker(); this.fireEvent("select", this, this.value); } else if(pn = el.up('td.x-date-mp-month', 2)){ this.mpMonths.removeClass('x-date-mp-sel'); pn.addClass('x-date-mp-sel'); this.mpSelMonth = pn.dom.xmonth; } else if(pn = el.up('td.x-date-mp-year', 2)){ this.mpYears.removeClass('x-date-mp-sel'); pn.addClass('x-date-mp-sel'); this.mpSelYear = pn.dom.xyear; } else if(el.is('a.x-date-mp-prev')){ this.updateMPYear(this.mpyear-10); } else if(el.is('a.x-date-mp-next')){ this.updateMPYear(this.mpyear+10); } }, onMonthDblClick : function(e, t){ e.stopEvent(); var el = new Ext.Element(t), pn; if(pn = el.up('td.x-date-mp-month', 2)){ this.update(this.getAdjustedDate(this.mpSelYear, pn.dom.xmonth)); //this.hideMonthPicker(); this.fireEvent("select", this, this.value); } else if(pn = el.up('td.x-date-mp-year', 2)){ this.update(this.getAdjustedDate(pn.dom.xyear, this.mpSelMonth)); //this.hideMonthPicker(); this.fireEvent("select", this, this.value); } }, hideMonthPicker : function(disableAnim){ Ext.menu.MenuMgr.hideAll(); }, showPrevMonth : function(e){ this.update(this.activeDate.add("mo", -1)); }, showNextMonth : function(e){ this.update(this.activeDate.add("mo", 1)); }, showPrevYear : function(){ this.update(this.activeDate.add("y", -1)); }, showNextYear : function(){ this.update(this.activeDate.add("y", 1)); }, update : function( date ) { this.activeDate = date; this.value = date; if(!this.internalRender){ var main = this.el.dom.firstChild; var w = main.offsetWidth; this.el.setWidth(w + this.el.getBorderWidth("lr")); Ext.fly(main).setWidth(w); this.internalRender = true; if(Ext.isOpera && !this.secondPass){ main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + "px"; this.secondPass = true; this.update.defer(10, this, [date]); } } }, setValue : function( date ) { this.activeDate = date; this.value = date; } }); //Ext.reg('monthpicker', Ext.ux.MonthPicker); Ext.ux.MonthItem = function(config){ Ext.ux.MonthItem.superclass.constructor.call(this, new Ext.ux.MonthPicker(config), config); this.picker = this.component; this.addEvents('select'); this.picker.on("render", function(picker){ picker.getEl().swallowEvent("click"); picker.container.addClass("x-menu-date-item"); }); this.picker.on("select", this.onSelect, this); }; Ext.extend(Ext.ux.MonthItem, Ext.menu.Adapter, { onSelect : function(picker, date){ this.fireEvent("select", this, date, picker); Ext.ux.MonthItem.superclass.handleClick.call(this); } }); Ext.ux.MonthMenu = function(config){ Ext.ux.MonthMenu.superclass.constructor.call(this, config); this.plain = true; var mi = new Ext.ux.MonthItem(config); this.add(mi); this.picker = mi.picker; this.relayEvents(mi, ["select"]); }; Ext.extend(Ext.ux.MonthMenu, Ext.menu.Menu, { cls:'x-date-menu' }); Ext.ux.MonthField = function(config){ Ext.ux.MonthField.superclass.constructor.call(this, config); } Ext.extend(Ext.ux.MonthField, Ext.form.DateField, { format : Ext.ux.MonthPicker.prototype.format, noPastYears: Ext.ux.MonthPicker.prototype.noPastYears, useDayDate: Ext.ux.MonthPicker.prototype.useDayDate, onTriggerClick : function(){ if(this.disabled){ return; } if(this.menu == null){ this.menu = new Ext.ux.MonthMenu(); } Ext.apply(this.menu.picker, { format : this.format, noPastYears : this.noPastYears, useDayDate : this.useDayDate }); this.menu.on(Ext.apply({}, this.menuListeners, { scope:this })); this.menu.picker.setValue(this.getValue() || new Date()); this.menu.show(this.el, "tl-bl?"); } });


Reply With Quote