--- /dev/null
+/*\r
+ * Ext JS Library 2.0 RC 1\r
+ * Copyright(c) 2006-2007, Ext JS, LLC.\r
+ * licensing@extjs.com\r
+ * \r
+ * http://extjs.com/license\r
+ */\r
+\r
+/**\r
+ * @class Ext.MultiMonthCalendar\r
+ * @extends Ext.Component\r
+ * Multi-month Calendar\r
+ * @constructor\r
+ * @param {Object} config The config object\r
+ */\r
+Ext.ux.MultiMonthCalendar = Ext.extend(Ext.Component, {\r
+ /**\r
+ * @cfg {Date} minDate\r
+ * Minimum allowable date (JavaScript date object, defaults to null)\r
+ */\r
+ minDate : null,\r
+ /**\r
+ * @cfg {Date} maxDate\r
+ * Maximum allowable date (JavaScript date object, defaults to null)\r
+ */\r
+ maxDate : null,\r
+ /**\r
+ * @cfg {String} minText\r
+ * The error text to display if the minDate validation fails (defaults to "This date is before the minimum date")\r
+ */\r
+ minText : "This date is before the minimum date",\r
+ /**\r
+ * @cfg {String} maxText\r
+ * The error text to display if the maxDate validation fails (defaults to "This date is after the maximum date")\r
+ */\r
+ maxText : "This date is after the maximum date",\r
+ /**\r
+ * @cfg {String} format\r
+ * The default date format string which can be overriden for localization support. The format must be\r
+ * valid according to {@link Date#parseDate} (defaults to 'm/d/y').\r
+ */\r
+ format : "m/d/y",\r
+ /**\r
+ * @cfg {Array} disabledDays\r
+ * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).\r
+ */\r
+ disabledDays : null,\r
+ /**\r
+ * @cfg {String} disabledDaysText\r
+ * The tooltip to display when the date falls on a disabled day (defaults to "")\r
+ */\r
+ disabledDaysText : "",\r
+ /**\r
+ * @cfg {RegExp} disabledDatesRE\r
+ * JavaScript regular expression used to disable a pattern of dates (defaults to null)\r
+ */\r
+ disabledDatesRE : null,\r
+ /**\r
+ * @cfg {String} disabledDatesText\r
+ * The tooltip text to display when the date falls on a disabled date (defaults to "")\r
+ */\r
+ disabledDatesText : "",\r
+ /**\r
+ * @cfg {Boolean} constrainToViewport\r
+ * True to constrain the date picker to the viewport (defaults to true)\r
+ */\r
+ constrainToViewport : true,\r
+ /**\r
+ * @cfg {Array} monthNames\r
+ * An array of textual month names which can be overriden for localization support (defaults to Date.monthNames)\r
+ */\r
+ monthNames : Date.monthNames,\r
+ /**\r
+ * @cfg {Array} dayNames\r
+ * An array of textual day names which can be overriden for localization support (defaults to Date.dayNames)\r
+ */\r
+ dayNames : Date.dayNames,\r
+ /**\r
+ * @cfg {String} nextText\r
+ * The next month navigation button tooltip (defaults to 'Next Month (Control+Right)')\r
+ */\r
+ nextText: 'Next Month (Control+Right)',\r
+ /**\r
+ * @cfg {String} prevText\r
+ * The previous month navigation button tooltip (defaults to 'Previous Month (Control+Left)')\r
+ */\r
+ prevText: 'Previous Month (Control+Left)', \r
+ /**\r
+ * @cfg {Number} startDay\r
+ * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)\r
+ */\r
+ startDay : 0,\r
+ /**\r
+ * @cfg {Number} noOfMonth\r
+ * No of Month to be displayed\r
+ */\r
+ noOfMonth : 2,\r
+/**\r
+ * @cfg {Array} eventDates\r
+ * List of Dates which have an event (show as selected in UI)\r
+ */ \r
+ eventDates : null,\r
+ \r
+ /**\r
+ * @cfg {Array} noOfMonthPerRow\r
+ * No. Of Month to be displayed in a row\r
+ */ \r
+ noOfMonthPerRow : 3,\r
+ \r
+ initComponent : function(){\r
+ Ext.ux.MultiMonthCalendar.superclass.initComponent.call(this);\r
+ this.value = this.value ?\r
+ this.value.clearTime() : new Date().clearTime();\r
+ this.initDisabledDays(); \r
+ this.noOfMonthPerRow = this.noOfMonthPerRow > this.noOfMonth ?this.noOfMonth : this.noOfMonthPerRow \r
+ }, \r
+ \r
+ // private\r
+ initDisabledDays : function(){\r
+ if(!this.disabledDatesRE && this.disabledDates){\r
+ var dd = this.disabledDates;\r
+ var re = "(?:";\r
+ for(var i = 0; i < dd.length; i++){\r
+ re += dd[i];\r
+ if(i != dd.length-1) re += "|";\r
+ }\r
+ this.disabledDatesRE = new RegExp(re + ")");\r
+ }\r
+ },\r
+ \r
+ /**\r
+ * Sets the value of the date field\r
+ * @param {Date} value The date to set\r
+ */\r
+\r
+ setValue : function(value){\r
+ var old = this.value;\r
+ this.value = value.clearTime(true);\r
+ if(this.el){\r
+ this.update(this.value);\r
+ }\r
+ },\r
+\r
+ /**\r
+ * Gets the current selected value of the date field\r
+ * @return {Date} The selected date\r
+ */\r
+ getValue : function(){\r
+ return this.value;\r
+ },\r
+\r
+\r
+ // private\r
+ focus : function(){\r
+ if(this.el){\r
+ this.update(this.activeDate);\r
+ }\r
+ },\r
+\r
+ // private\r
+ onRender : function(container, position){\r
+ var m = ["<table cellspacing='0'>"];\r
+ if(this.noOfMonthPerRow > 1) {\r
+ m.push("<tr><td class='x-date-left'><a href='#' title='", this.prevText ,"'> </a></td>");\r
+ m.push("<td class='x-date-left' colspan='"+ eval(this.noOfMonthPerRow *2 -3) +"'></td>");\r
+ m.push("<td class='x-date-right'><a href='#' title='", this.nextText ,"'> </a></td></tr><tr>");\r
+ } else {\r
+ //Special case of only one month\r
+ m.push("<tr><td><table cellspacing='0' width='100%'><tr><td class='x-date-left'><a href='#' title='", this.prevText ,"'> </a></td>");\r
+ m.push("<td class='x-date-right'><a href='#' title='", this.nextText ,"'> </a></td></tr></table></td></tr><tr>");\r
+ } \r
+ for(var x=0; x<this.noOfMonth; x++) { \r
+ m.push("<td><table border='1' cellspacing='0'><tr>");\r
+ m.push("<td class='x-date-middle' align='center'><span id='monthLabel" + x + "'></span></td>");\r
+ m.push("</tr><tr><td><table class='x-date-inner' id='inner-date"+x+"' cellspacing='0'><thead><tr>"); \r
+ var dn = this.dayNames;\r
+ for(var i = 0; i < 7; i++){\r
+ var d = this.startDay+i;\r
+ if(d > 6){\r
+ d = d-7;\r
+ }\r
+ m.push("<th><span>", dn[d].substr(0,1), "</span></th>");\r
+ }\r
+ m[m.length] = "</tr></thead><tbody><tr>";\r
+ for(var i = 0; i < 42; i++) {\r
+ if(i % 7 == 0 && i != 0){\r
+ m[m.length] = "</tr><tr>";\r
+ }\r
+ m[m.length] = '<td><a href="#" hidefocus="on" class="x-date-date" tabIndex="1"><em><span></span></em></a></td>';\r
+ }\r
+ m[m.length] = '</tr></tbody></table></td></tr></table></td>';\r
+ if(x != this.noOfMonth-1) {\r
+ m[m.length] = "<td width='3'></td>";\r
+ }\r
+ if( (x+1) % this.noOfMonthPerRow == 0 && x!= 0) {\r
+ m[m.length] = "</tr><tr>"; \r
+ } \r
+ }\r
+ m[m.length] = "</tr></table>"; \r
+ var el = document.createElement("div");\r
+ el.className = "x-date-picker";\r
+ el.innerHTML = m.join(""); \r
+\r
+ container.dom.insertBefore(el, position);\r
+\r
+ this.el = Ext.get(el); \r
+ this.eventEl = Ext.get(el.firstChild);\r
+\r
+ new Ext.util.ClickRepeater(this.el.child("td.x-date-left a"), {\r
+ handler: this.showPrevMonth,\r
+ scope: this,\r
+ preventDefault:true,\r
+ stopDefault:true\r
+ });\r
+\r
+ new Ext.util.ClickRepeater(this.el.child("td.x-date-right a"), {\r
+ handler: this.showNextMonth,\r
+ scope: this,\r
+ preventDefault:true,\r
+ stopDefault:true\r
+ });\r
+\r
+ var kn = new Ext.KeyNav(this.eventEl, {\r
+ "left" : function(e){\r
+ e.ctrlKey ?\r
+ this.showPrevMonth() :\r
+ this.update(this.activeDate.add("d", -1));\r
+ },\r
+\r
+ "right" : function(e){\r
+ e.ctrlKey ?\r
+ this.showNextMonth() :\r
+ this.update(this.activeDate.add("d", 1));\r
+ },\r
+\r
+ "pageUp" : function(e){\r
+ this.showNextMonth();\r
+ },\r
+\r
+ "pageDown" : function(e){\r
+ this.showPrevMonth();\r
+ },\r
+\r
+ "enter" : function(e){\r
+ e.stopPropagation();\r
+ return true;\r
+ }, \r
+ scope : this \r
+ });\r
+ \r
+ this.cellsArray = new Array();\r
+ this.textNodesArray = new Array(); \r
+ for(var x=0; x< this.noOfMonth; x++) {\r
+ var cells = Ext.get('inner-date'+x).select("tbody td");\r
+ var textNodes = Ext.get('inner-date'+x).query("tbody span");\r
+ this.cellsArray[x] = cells;\r
+ this.textNodesArray[x] = textNodes;\r
+ }\r
+\r
+ if(Ext.isIE){\r
+ this.el.repaint();\r
+ }\r
+ this.update(this.value);\r
+ },\r
+\r
+ // private\r
+ showPrevMonth : function(e){\r
+ this.update(this.activeDate.add("mo", -1));\r
+ },\r
+\r
+ // private\r
+ showNextMonth : function(e){\r
+ this.update(this.activeDate.add("mo", 1));\r
+ },\r
+\r
+ // private\r
+ update : function(date){\r
+ this.activeDate = date;\r
+ for(var x=0;x<this.noOfMonth;x++) {\r
+ var days = date.getDaysInMonth();\r
+ var firstOfMonth = date.getFirstDateOfMonth();\r
+ var startingPos = firstOfMonth.getDay()-this.startDay;\r
+ \r
+ if(startingPos <= this.startDay){\r
+ startingPos += 7;\r
+ }\r
+ \r
+ var pm = date.add("mo", -1);\r
+ var prevStart = pm.getDaysInMonth()-startingPos;\r
+ \r
+ var cells = this.cellsArray[x].elements;\r
+ var textEls = this.textNodesArray[x];\r
+ days += startingPos;\r
+ \r
+ // convert everything to numbers so it's fast\r
+ var day = 86400000;\r
+ var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();\r
+ var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;\r
+ var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;\r
+ var ddMatch = this.disabledDatesRE;\r
+ var ddText = this.disabledDatesText;\r
+ var ddays = this.disabledDays ? this.disabledDays.join("") : false;\r
+ var ddaysText = this.disabledDaysText;\r
+ var format = this.format;\r
+ \r
+ var setCellClass = function(cal, cell){\r
+ cell.title = "";\r
+ var t = d.getTime();\r
+ cell.firstChild.dateValue = t;\r
+\r
+ // disabling\r
+ if(t < min) {\r
+ cell.className = " x-date-disabled";\r
+ cell.title = cal.minText;\r
+ return;\r
+ }\r
+ if(t > max) {\r
+ cell.className = " x-date-disabled";\r
+ cell.title = cal.maxText;\r
+ return;\r
+ }\r
+ if(ddays){\r
+ if(ddays.indexOf(d.getDay()) != -1){\r
+ cell.title = ddaysText;\r
+ cell.className = " x-date-disabled";\r
+ }\r
+ }\r
+ if(ddMatch && format){\r
+ var fvalue = d.dateFormat(format);\r
+ if(ddMatch.test(fvalue)){\r
+ cell.title = ddText.replace("%0", fvalue);\r
+ cell.className = " x-date-disabled";\r
+ }\r
+ }\r
+ //Only active days need to be selected\r
+ if(cal.eventDates && (cell.className.indexOf('x-date-active') != -1)) {\r
+ for(var y=0; y < cal.eventDates.length; y++) {\r
+ var evtDate = cal.eventDates[y].clearTime().getTime();\r
+ if(t == evtDate) {\r
+ cell.className += " x-date-selected";\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ };\r
+ \r
+ var i = 0;\r
+ for(; i < startingPos; i++) {\r
+ textEls[i].innerHTML = (++prevStart);\r
+ d.setDate(d.getDate()+1);\r
+ cells[i].className = "x-date-prevday";\r
+ setCellClass(this, cells[i]);\r
+ }\r
+ for(; i < days; i++){\r
+ intDay = i - startingPos + 1;\r
+ textEls[i].innerHTML = (intDay);\r
+ d.setDate(d.getDate()+1);\r
+ cells[i].className = "x-date-active";\r
+ setCellClass(this, cells[i]);\r
+ }\r
+ var extraDays = 0;\r
+ for(; i < 42; i++) {\r
+ textEls[i].innerHTML = (++extraDays);\r
+ d.setDate(d.getDate()+1);\r
+ cells[i].className = "x-date-nextday";\r
+ setCellClass(this, cells[i]);\r
+ }\r
+ var monthLabel = Ext.get('monthLabel' + x); \r
+ monthLabel.update(this.monthNames[date.getMonth()] + " " + date.getFullYear());\r
+\r
+ if(!this.internalRender){\r
+ var main = this.el.dom.firstChild;\r
+ var w = main.offsetWidth;\r
+ this.el.setWidth(w + this.el.getBorderWidth("lr"));\r
+ Ext.fly(main).setWidth(w);\r
+ this.internalRender = true;\r
+ // opera does not respect the auto grow header center column\r
+ // then, after it gets a width opera refuses to recalculate\r
+ // without a second pass\r
+ if(Ext.isOpera && !this.secondPass){\r
+ main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + "px";\r
+ this.secondPass = true;\r
+ this.update.defer(10, this, [date]);\r
+ }\r
+ }\r
+ date = date.add('mo',1);\r
+ } \r
+ }\r
+});\r
+Ext.reg('mmcalendar', Ext.ux.MultiMonthCalendar); \r
+\r