var ADate = Class.create({ year: 2000, month: 1, day: 1, // padded values (their values are maintained synchronously with numeric values), // this is made to avoid permanent .toPaddedString calls yearPadded: null, monthPadded: null, dayPadded: null, initialize: function(y, m, d) { this.year = parseInt(y, 10); this.month = parseInt(m, 10); this.day = parseInt(d, 10); if (!this.IsIncorrect()) { this.yearPadded=this.year.toString(); this.monthPadded=this.month.toPaddedString(2); this.dayPadded=this.day.toPaddedString(2); } // otherwise left them null }, toString: function() { var date = this.ToDate(); return date ? date.toLocaleDateString() : ""; }, ToDate: function(){ return this.IsIncorrect() ? "" : new Date(this.year, this.month - 1, this.day); }, IsIncorrect: function() { return isNaN(this.day) || isNaN(this.month) || isNaN(this.year); }, ToISOpart: function(){ if (this.IsIncorrect()) return ADate.notADate; return this.yearPadded+this.monthPadded+this.dayPadded; }, fromISOpart: function(str){ str=str.substr(0, 8); this.yearPadded=str.substr(0,4); this.monthPadded=str.substr(4,2); this.dayPadded=str.substr(6); this.year=parseInt(this.yearPadded, 10); this.month=parseInt(this.monthPadded, 10); this.day=parseInt(this.dayPadded, 10); }, FromISO: function(str) { this.fromISOpart(str); }, Serialize: function(serializer){ serializer.BeginType("ADate"); serializer.Serialize(this.ToISOpart()); serializer.EndType("ADate"); }, Restore: function(restorer) { restorer.BeginType("ADate"); var str=restorer.Restore(); restorer.EndType("ADate"); if (str==ADate.notADate){ this.year=NaN; this.month=NaN; this.day=NaN; this.yearPadded=null; this.monthPadded=null; this.dayPadded=null; } else this.fromISOpart(str); }, GetYear: function(){ return this.year; }, GetMonth: function(){ return this.month; }, GetDay: function(){ return this.day; } }); ADate.notADate = "not-a-date-time"; ADate.Current = function(){ var today = new Date(); return new ADate(today.getFullYear(), today.getMonth() + 1, today.getDate()); } var DateInput = Class.create(SuperclassInput, { initialize: function($super, element, params){ params = params || {}; $super(element, params.button); if (params["attrs"]){ this.opener = new SIPopupOpener(params.button, { "html": function(){ return JSControl.TransformElementAndUnwrap(params["attrs"], params.types || {}); }, "tablimit": 4, "align-id": this.ID() }); SubscribeOnEvent("CHANGE", "document", this.onCHANGE, this); SubscribeOnEvent("FINALIZE", "document", this.onFINALIZE, this); Event.observe(params.button, "SI:SHOW", function(event){ if (event.memo != this.opener) return; var calendar = $(params["attrs"].htmlid); if (!calendar || !calendar.JSControl) return; calendar.JSControl.SetValue(this.Value()); }.bindAsEventListener(this)); } this.params = params; this.defaultCentury=parseInt(this.element.readAttribute("default-century"), 10); this.delimiter = this.element.readAttribute("date-delimiter"); this.dateFormat = String(this.element.readAttribute("date-format")).toLowerCase(); if (this.dateFormat.length != 3 || // ["d", "m", "y"].collect(function(char){ return this.dateFormat.indexOf(char) == -1; }, this).any()) return alert("Incorrect date format: \"" + this.dateFormat + "\""); var delimStr = { "complete": SuperclassInput.addBackslashForRegexp(this.delimiter), "incomplete": SuperclassInput.constructPartialMatch(this.delimiter), "nonEmpty": SuperclassInput.constructPartialMatch(this.delimiter, false) }; var calendarType = this.element.readAttribute("calendar-type"); switch (calendarType){ case "gregorian": if (/\d/.test(this.delimiter)) LogE("Date delimiter MUST NOT contain digits"); this.limits = { "d": {"min": 1, "max": 31, "errMin": DateInput.dayless1, "errMax": DateInput.daymore31}, "m": {"min": 1, "max": 12, "errMin": DateInput.monthless1, "errMax": DateInput.monthmore12}, "y": {"min": 1400, "max": NaN, "errMin": DateInput.yearless + " 1400"} }; var dateFormatArray = this.dateFormat.split(""); this.calendarFormat = dateFormatArray.collect(function(char){ return "%" + (char == "y" ? char.toUpperCase() : char.toLowerCase()); }).join(this.delimiter); this.formatToDisplay = dateFormatArray.collect(function(char){ return DateInput.formatNames[char]; }).join(this.delimiter); this.incorrectFormatMessage = DateInput.incorrectformat + " (" + this.formatToDisplay + ")"; var dateComponents = dateFormatArray.collect(function(char, iii){ switch (char){ case "d": case "m": return {"type": char, "complete": "\\d{2}", "incomplete": "\\d{1,2}", "index": iii, "length": 2}; case "y": return {"type": char, "complete": "\\d{4}", "incomplete": "\\d{1,4}", "index": iii, "length": 4}; } }); dateComponents.each(function(dateComponent, iii){ dateComponents[dateComponent.type] = dateComponents[iii]; }); this.dateComponents = dateComponents; var completeMatch = ""; var dateComponentsLength = dateComponents.length; this.syntax = new RegExp("^$|" + dateComponents.collect(function(dateComponent, iii){ var result = completeMatch + dateComponent.incomplete; if (iii < dateComponentsLength - 1){ result = completeMatch + dateComponent.complete + delimStr.incomplete + "$|^" + result; completeMatch += dateComponent.complete + delimStr.complete; } return "^" + result + "$"; }).join("|")); this.syntaxFull = new RegExp("^" + dateComponents.pluck("complete").join(delimStr.complete) + "$"); this.semantics = new RegExp("^" + SuperclassInput.constructPartialMatchFromArray( dateComponents.collect(function(dateComponent, iii){ var result = ["(" + dateComponent.incomplete + ")"]; if (iii > 0) result.unshift(delimStr.nonEmpty); return result; }).flatten()) + "$"); this.checkSemantics = this.checkSemanticsGregorian; this.preModify = this.preModifyGregorian; this.autoModify = this.autoModifyGregorian; this.getValue = this.getValueGregorian; break; default: LogE("Unsupported calendar type " + calendarType); }; this.completeInitialization(); Event.observe(this.element, "click", this.setselectionclick.bind(this)); Event.observe(this.element, "keypress", this.setselectionkey.bind(this)); }, getModifiedStr: function(str, newch, ss, se){ if (typeof se == "undefined") se = ss; if(this.defaultCentury>=14 && this.defaultCentury<=99 && ss==10 && se==10) return str.substr(0, 6)+str.substr(8)+newch; else return str.substr(0, ss) + newch + str.substr(se); }, setselectionkey: function( event ) { var keycode = event.keyCode; if( keycode == Event.KEY_LEFT ) { var pos = getSelectionStart( document.getElementById(this.ID()) ); if( pos == 2 || pos == 5 ) document.getElementById(this.ID()).setSelectionRange( pos-1, pos ); else document.getElementById(this.ID()).setSelectionRange( pos, pos+1 ); } else { var pos = getSelectionStart( document.getElementById(this.ID()) ); if( pos == 2 || pos == 5 ) document.getElementById(this.ID()).setSelectionRange( pos+1, pos+2 ); else document.getElementById(this.ID()).setSelectionRange( pos, pos+1 ); } }, setselectionclick: function( ) { var pos = getSelectionStart( document.getElementById(this.ID()) ); if( pos == 2 || pos == 5 ) document.getElementById(this.ID()).setSelectionRange( pos+1, pos+2 ); else document.getElementById(this.ID()).setSelectionRange( pos, pos+1 ); }, getValue: function(){ return {"d": NaN, "m": NaN, "y": NaN}; }, checkSemanticsGregorian: function(str, silent, fullCheck){ var matchedArray = this.semantics.exec(str); if (!matchedArray){ this.setErrorMessage(this.incorrectFormatMessage, silent); return false; } matchedArray.shift(); while (matchedArray.length > 0 && typeof matchedArray[matchedArray.length - 1] == "undefined") --matchedArray.length; var thisobj = this; var matchedValues = {}; if (!(matchedArray.collect(function(unit, iii){ var number = Number(unit); var isComplete = fullCheck || RegExp("^" + thisobj.dateComponents[iii].complete + "$").test(unit); var dateComponentType = thisobj.dateComponents[iii].type; matchedValues[dateComponentType] = {"value": number, "complete": isComplete}; var min = thisobj.limits[dateComponentType].min; if (isComplete && !isNaN(min) && number < min){ thisobj.setErrorMessage(thisobj.limits[dateComponentType].errMin, silent); return false; } var max = thisobj.limits[dateComponentType].max; if (!isNaN(max) && number > max){ thisobj.setErrorMessage(thisobj.limits[dateComponentType].errMax, silent); return false; } return true; }).all())) return false; if (matchedArray.length <= 1) return true; if (["d", "m"].collect(function(field){ return typeof matchedValues[field] != "undefined" && matchedValues[field].complete; }).all()){ var day = matchedValues["d"].value; var month = matchedValues["m"].value; if (day > DateInput.maxDays[month]){ thisobj.setErrorMessage(DateInput.lessthandaysin(day, month), silent); return false; } if (day == 29 && month == 2 && typeof matchedValues["y"] != "undefined" && matchedValues["y"].complete){ var year = matchedValues["y"].value; if (!DateInput.isLeapYear(year)){ thisobj.setErrorMessage(DateInput.notleapyear(year), silent); return false; } } } return true; }, getDateComponentGregorian: function(str, pos){ var currentPos = 0; var startPos = 0; var delimiterLength = this.delimiter.length; var isIn = new Array(this.dateComponents.length); this.dateComponents.each(function(dateComponent, iii){ isIn[iii] = pos >= currentPos && pos < currentPos + dateComponent.length; currentPos += dateComponent.length + delimiterLength; }); var iii = isIn.indexOf(true); return iii == -1 ? "" : this.dateComponents[iii].type; }, isFirstCharInComponent: function(str, pos){ return pos == 0 || str.charAt(pos - 1) == this.delimiter.charAt(this.delimiter.length - 1); }, preModifyGregorian: function(str, newch, ss, se){ var result = {"str": str, "ss": ss, "se": se}; if (str.length !== se){ var comp = this.getDateComponentGregorian(str, ss); if (comp === this.getDateComponentGregorian(str, se - 1) && ["d", "m"].indexOf(comp) !== -1){ var comp_s = str.lastIndexOf(this.delimiter, ss - this.delimiter.length); if (comp_s === -1) comp_s = 0; else comp_s += this.delimiter.length; var comp_e = str.indexOf(this.delimiter, se); if (se === -1) comp_e = str.length; var pad_l = this.dateComponents[comp].length - (ss - comp_s + newch.length); if (se === comp_e && pad_l >= 0){ var mod_s = str.substr(0, comp_s) + "0".times(pad_l) + str.substring(comp_s, ss); var mod_e = str.substr(se); if (this.check(mod_s + newch + mod_e, true)){ result.str = mod_s + mod_e; result.ss = result.se = mod_s.length; return result; } } } } if (newch.length == 0 || this.delimiter.length == 0 || !this.check(str, true) || // this.check(this.getModifiedStr(str, newch, ss, se), true)) return result; var lastDelimiterChar = this.delimiter.charAt(this.delimiter.length - 1); switch (newch){ case this.delimiter.charAt(0): if (ss == 0 || se < str.length || !str.charAt(ss - 1).isDigit()) break; str = str.substr(0, ss); var componentStart = str.lastIndexOf(lastDelimiterChar) + 1; var strModified = this.getModifiedStr(str, "0", componentStart); if (!this.check(strModified, true)) break; result.str = strModified; ++result.ss; ++result.se; break; case "0": if (se >= str.length || se != ss + 1 || str.charAt(se) != "0" || // !this.isFirstCharInComponent(str, ss)) break; var currentComponent = this.getDateComponentGregorian(str, ss); if (currentComponent != "d" && currentComponent != "m") break; var strModified = this.getModifiedStr(str, "01", ss, se + 1); if (this.check(strModified, true)) result.str = strModified; break; default: if (!newch.isDigit() || se >= str.length || se != ss + 1) break; switch (this.getDateComponentGregorian(str, ss)){ case "d": case "m": if (this.isFirstCharInComponent(str, ss)){ var strmod = this.getModifiedStr(str, newch + "0", ss, se + 1); if (this.check(strmod, true)) result.str = strmod; } break; case "y": if (newch != "1" || !this.isFirstCharInComponent(str, ss)) break; var strModified = this.getModifiedStr(str, "14", ss, se + 1); if (this.check(strModified, true)) result.str = strModified; break; } break; } return result; }, autoModifyGregorian: function(str, ss, se){ var val = this.getValue(str); var delimiterIndices = str.indicesOf(this.delimiter); var thisobj = this; var limitsForAutoComplete = {"d": 3, "m": 1}; ["d", "m"].each(function(char){ if (typeof val[char] == "undefined" || String(val[char]).length != 1 || // Number(val[char]) <= limitsForAutoComplete[char]) return; var index = thisobj.dateComponents[char].index; var insertPoint = index == 0 ? 0 : delimiterIndices[index - 1] + thisobj.delimiter.length; var strModified = thisobj.getModifiedStr(str, "0", insertPoint); if (thisobj.check(strModified, true)){ str = strModified; ss += insertPoint <= ss; se += insertPoint <= se; for (var iii = index; iii < delimiterIndices.length; ++iii) ++delimiterIndices[iii]; } }); if (ss == str.length && this.check(str + this.delimiter, true)){ str += this.delimiter; ss += this.delimiter.length; se = ss; if(ss==6 && this.defaultCentury>=14 && this.defaultCentury<=99) { str+=this.defaultCentury.toString(10); se=ss=8; } } return {"str": str, "ss": ss, "se": se}; }, getValueGregorian: function(str){ if (typeof str == "undefined") str = this.element.value; var matchedArray = this.semantics.exec(str); var result = {}; this.dateComponents.each(function(dateComponent, iii){ result[dateComponent.type] = parseInt(matchedArray[iii + 1], 10); }); return result; }, ultimateTest: function(){ var silent = false; var val = String(this.element.value); return this.hintIsVisible() || val.length == 0 || this.check(val, silent, true); }, Value: function(){ if (this.disabled()){ if (this.value instanceof ADate) return this.value; if (this.value && ["day", "month", "year"].collect(function(key){ return !Object.isUndefined(this.value[key]); }, this).all()) return new ADate(this.value["year"], this.value["month"], this.value["day"]); }else if (!this.hintIsVisible() && this.check(this.element.value, true, true)){ var val = this.getValue(); return new ADate(val.y, val.m, val.d); } return new ADate(); }, SetValue: function($super, date){ this.value = date; if (typeof(date) == 'undefined' || date === null) return void this.clear(); if(date=='today') { var today=new Date(); date=new ADate(today.getFullYear(), today.getMonth()+1, today.getDate()); this.value = date; new AEvent("WM_INIT", {}, this); } if(["day", "month", "year"].collect(function(fieldName){ if (typeof(date[fieldName]) == "undefined" || isNaN(date[fieldName])){ LogL("DateInput.SetValue(): date." + fieldName + " is undefined or NaN"); return true; } return false; }).any()) return void this.clear(); var humanreadable = this.disabled() && ("ro" != this.visibility_); this.hideHint(); this.element.value = this.dateComponents.collect(function(dateComponent){ switch (dateComponent.type){ case "d": return date.day.toPaddedString(2); case "m": return humanreadable ? DateInput.of_monthname[date.month] : date.month.toPaddedString(2); case "y": return date.year.toPaddedString(4); } }).join(humanreadable ? " " : this.delimiter); $super(date); }, SetAttribute: function($super, attrName, attrValue){ var disabledPre = this.disabled(); if (disabledPre) this.value = this.Value(); $super(attrName, attrValue); if (this.disabled() != disabledPre) this.SetValue(this.value); }, SetTabOrder: function($super, tabbase){ tabbase = $super(tabbase); if (this.opener) return this.opener.SetTabOrder(tabbase); else return tabbase; }, widgetID: function(){ return this.params["attrs"].htmlid; }, onCHANGE: function(aevent){ var emitter = aevent.Emitter(); if (emitter.ID() != this.widgetID()) return; this.SetValue(emitter.Value()); this.onchange(); }, onFINALIZE: function(aevent){ var emitter = aevent.Emitter(); if (emitter.ID() != this.widgetID()) return; this.SetValue(emitter.Value()); if (this.opener) this.opener.Hide(); this.onchange(); }, setCurrent: function(event){ var cur = ADate.Current(); if (cur.toString() != this.Value().toString()){ this.SetValue(cur); var widget = $(this.widgetID()); if (widget && widget.JSControl) widget.JSControl.SetValue(cur); this.onchange(); } } }); DateInput.formatNames = {"d": "`DD`", "m": "`MM`", "y": "`YYYY`"}; DateInput.maxDays = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; DateInput.isLeapYear = function(year){ return year % 4 == 0 && year % 100 != 0 || year % 400 == 0; }; DateInput.daynames = new Array("`Sunday`", "`Monday`", "`Tuesday`", "`Wednesday`", "`Thursday`", "`Friday`", "`Saturday`"); DateInput.monthnames = new Array("", "`January`", "`February`", "`March`", "`April`", "`May`", "`June`", "`July`", "`August`", "`September`", "`October`", "`November`", "`December`"); DateInput.shortMonthNames = new Array("", "`SHORT_MONTH_Jan`", "`SHORT_MONTH_Feb`", "`SHORT_MONTH_Mar`", "`SHORT_MONTH_Apr`", "`SHORT_MONTH_May`", "`SHORT_MONTH_Jun`", "`SHORT_MONTH_Jul`", "`SHORT_MONTH_Aug`", "`SHORT_MONTH_Sep`", "`SHORT_MONTH_Oct`", "`SHORT_MONTH_Nov`", "`SHORT_MONTH_Dec`"); DateInput.of_monthname = new Array("", "`of January`", "`of February`", "`of March`", "`of April`", "`of May`", "`of June`", "`of July`", "`of August`", "`of September`", "`of October`", "`of November`", "`of December`"); DateInput.in_monthname = new Array("", "`In January`", "`In February`", "`In March`", "`In April`", "`In May`", "`In June`", "`In July`", "`In August`", "`In September`", "`In October`", "`In November`", "`In December`"); DateInput.dayless1 = "`DAY < 1`"; DateInput.daymore31 = "`DAY > 31`"; DateInput.monthless1 = "`MONTH < 1`"; DateInput.monthmore12 = "`MONTH > 12`"; DateInput.yearless = "`YEAR <`"; DateInput.incorrectformat = "`Incorrect date format`"; DateInput.lessthandaysin = function(day, month){ return "`$1 less than $2 days`".replace("$1", DateInput.in_monthname[month]).replace("$2", day); }; DateInput.notleapyear = function(year){ return "`Year $1 is not a leap year`".replace("$1", year); };