1 2 // Date Box Control 3 // 4 // based on: 5 // 'Magic' date parsing, by Simon Willison (6th October 2003) 6 // http://simon.incutio.com/archive/2003/10/06/betterDateInput 7 // 8 // Notes 9 // To create a date box control, call the SetupDateBoxControl function. 10 // It should be passed an input element with type of text. 11 // This will first create a div after the input box. 12 // Then it will associate the div with the input element. 13 // The div will use the css classes: DateBoxControlMsg, DateBoxControlErrorMsg. 14 // Then the div is associated with the input element. 15 // Then the contents are validated so the div will get populated initially. 16 // The onchange event of the input element will invoke the validation function. 17 // The validation function populates the div. 18 // If a successfully parsed date, the div gets a nicely formatted date. 19 // If an unsuccessfully parse date, the div gets an error message. 20 // 21 // History 22 // 02/03/2005 - WSR : modified for use as datebox control 23 // 09/09/2005 - WSR : datebox is not required anymore (blank input is valid) 24 // : added style class to datebox itself 25 26 // hooks functionality up to given textbox 27 function SetupDateBoxControl( ctlDateBox ) 28 { 29 30 // if a valid object was given 31 if (ctlDateBox) 32 { 33 34 // add div after control for messages 35 var divMessage = document.createElement('div'); 36 divMessage.className = 'DateBoxControlMsg'; 37 38 // if there is a next sibling 39 if (ctlDateBox.nextSibling) 40 { 41 42 // insert before next sibling 43 ctlDateBox.parentNode.insertBefore( divMessage, ctlDateBox.nextSibling ); 44 45 } 46 // if there is not a next sibling 47 else 48 { 49 50 // append child to parent 51 ctlDateBox.parentNode.appendChild( divMessage ); 52 53 } 54 55 // link message div to textbox for easy script access 56 ctlDateBox.message = divMessage; 57 58 // validate current contents 59 DateBoxControl_Validate( ctlDateBox ); 60 61 // hook up event handlers 62 ctlDateBox.onchange = function () { DateBoxControl_Validate(this); }; 63 64 } 65 66 } 67 68 // add indexOf function to Array type 69 // finds the index of the first occurence of item in the array, or -1 if not found 70 Array.prototype.indexOf = function(item) { 71 for (var i = 0; i < this.length; i++) { 72 if (this[i] == item) { 73 return i; 74 } 75 } 76 return -1; 77 }; 78 79 // add filter function to Array type 80 // returns an array of items judged true by the passed in test function 81 Array.prototype.filter = function(test) { 82 var matches = []; 83 for (var i = 0; i < this.length; i++) { 84 if (test(this[i])) { 85 matches[matches.length] = this[i]; 86 } 87 } 88 return matches; 89 }; 90 91 // add right function to String type 92 // returns the rightmost x characters 93 String.prototype.right = function( intLength ) { 94 if (intLength >= this.length) 95 return this; 96 else 97 return this.substr( this.length - intLength, intLength ); 98 }; 99 100 // add trim function to String type 101 // trims leading and trailing whitespace 102 String.prototype.trim = function() { return this.replace(/^\s+|\s+$/, ''); }; 103 104 // arrays for month and weekday names 105 var monthNames = "January February March April May June July August September October November December".split(" "); 106 var weekdayNames = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "); 107 108 /* Takes a string, returns the index of the month matching that string, throws 109 an error if 0 or more than 1 matches 110 */ 111 function parseMonth(month) { 112 var matches = monthNames.filter(function(item) { 113 return new RegExp("^" + month, "i").test(item); 114 }); 115 if (matches.length == 0) { 116 throw new Error("Invalid month string"); 117 } 118 if (matches.length < 1) { 119 throw new Error("Ambiguous month"); 120 } 121 return monthNames.indexOf(matches[0]); 122 } 123 124 /* Same as parseMonth but for days of the week */ 125 function parseWeekday(weekday) { 126 var matches = weekdayNames.filter(function(item) { 127 return new RegExp("^" + weekday, "i").test(item); 128 }); 129 if (matches.length == 0) { 130 throw new Error("Invalid day string"); 131 } 132 if (matches.length < 1) { 133 throw new Error("Ambiguous weekday"); 134 } 135 return weekdayNames.indexOf(matches[0]); 136 } 137 138 function DateInRange( yyyy, mm, dd ) 139 { 140 141 // if month out of range 142 if ( mm < 0 || mm > 11 ) 143 throw new Error('Invalid month value. Valid months values are 1 to 12'); 144 145 // get last day in month 146 var d = (11 == mm) ? new Date(yyyy + 1, 0, 0) : new Date(yyyy, mm + 1, 0); 147 148 // if date out of range 149 if ( dd < 1 || dd > d.getDate() ) 150 throw new Error('Invalid date value. Valid date values for ' + monthNames[mm] + ' are 1 to ' + d.getDate().toString()); 151 152 return true; 153 154 } 155 156 /* Array of objects, each has 're', a regular expression and 'handler', a 157 function for creating a date from something that matches the regular 158 expression. Handlers may throw errors if string is unparseable. 159 */ 160 var dateParsePatterns = [ 161 // Today 162 { re: /^today/i, 163 handler: function() { 164 return new Date(); 165 } 166 }, 167 // Tomorrow 168 { re: /^tomorrow/i, 169 handler: function() { 170 var d = new Date(); 171 d.setDate(d.getDate() + 1); 172 return d; 173 } 174 }, 175 // Yesterday 176 { re: /^yesterday/i, 177 handler: function() { 178 var d = new Date(); 179 d.setDate(d.getDate() - 1); 180 return d; 181 } 182 }, 183 // mmddyyyy (American style) 184 { re: /(\d{2})(\d{2})(\d{4})/, 185 handler: function(bits) { 186 187 var yyyy = parseInt(bits[3], 10); 188 var dd = parseInt(bits[2], 10); 189 var mm = parseInt(bits[1], 10) - 1; 190 191 if ( DateInRange( yyyy, mm, dd ) ) 192 return new Date(yyyy, mm, dd); 193 194 } 195 }, 196 // mmddyy (American style) short year 197 { re: /(\d{2})(\d{2})(\d{2})/, 198 handler: function(bits) { 199 200 var d = new Date(); 201 var yyyy = d.getFullYear() - (d.getFullYear() % 100) + parseInt(bits[3], 10); 202 var dd = parseInt(bits[2], 10); 203 var mm = parseInt(bits[1], 10) - 1; 204 205 if ( DateInRange(yyyy, mm, dd) ) 206 return new Date(yyyy, mm, dd); 207 208 } 209 }, 210 // 4th 211 { re: /^(\d{1,2})(st|nd|rd|th)?$/i, 212 handler: function(bits) { 213 214 var d = new Date(); 215 var yyyy = d.getFullYear(); 216 var dd = parseInt(bits[1], 10); 217 var mm = d.getMonth(); 218 219 if ( DateInRange( yyyy, mm, dd ) ) 220 return new Date(yyyy, mm, dd); 221 222 } 223 }, 224 // 4th Jan 225 { re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i, 226 handler: function(bits) { 227 228 var d = new Date(); 229 var yyyy = d.getFullYear(); 230 var dd = parseInt(bits[1], 10); 231 var mm = parseMonth(bits[2]); 232 233 if ( DateInRange( yyyy, mm, dd ) ) 234 return new Date(yyyy, mm, dd); 235 236 } 237 }, 238 // 4th Jan 2003 239 { re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i, 240 handler: function(bits) { 241 242 var yyyy = parseInt(bits[3], 10); 243 var dd = parseInt(bits[1], 10); 244 var mm = parseMonth(bits[2]); 245 246 if ( DateInRange( yyyy, mm, dd ) ) 247 return new Date(yyyy, mm, dd); 248 249 } 250 }, 251 // Jan 4th 252 { re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i, 253 handler: function(bits) { 254 255 var d = new Date(); 256 var yyyy = d.getFullYear(); 257 var dd = parseInt(bits[2], 10); 258 var mm = parseMonth(bits[1]); 259 260 if ( DateInRange( yyyy, mm, dd ) ) 261 return new Date(yyyy, mm, dd); 262 263 } 264 }, 265 // Jan 4th 2003 266 { re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i, 267 handler: function(bits) { 268 269 var yyyy = parseInt(bits[3], 10); 270 var dd = parseInt(bits[2], 10); 271 var mm = parseMonth(bits[1]); 272 273 if ( DateInRange( yyyy, mm, dd ) ) 274 return new Date(yyyy, mm, dd); 275 276 } 277 }, 278 // next Tuesday - this is suspect due to weird meaning of "next" 279 { re: /^next (\w+)$/i, 280 handler: function(bits) { 281 282 var d = new Date(); 283 var day = d.getDay(); 284 var newDay = parseWeekday(bits[1]); 285 var addDays = newDay - day; 286 if (newDay <= day) { 287 addDays += 7; 288 } 289 d.setDate(d.getDate() + addDays); 290 return d; 291 292 } 293 }, 294 // last Tuesday 295 { re: /^last (\w+)$/i, 296 handler: function(bits) { 297 298 var d = new Date(); 299 var wd = d.getDay(); 300 var nwd = parseWeekday(bits[1]); 301 302 // determine the number of days to subtract to get last weekday 303 // calculates 0 if weekdays are the same so we have to change this to 7 304 var addDays = (wd == nwd) ? -7 : (-1 * (wd + 7 - nwd)) % 7; 305 306 // adjust date and return 307 d.setDate(d.getDate() + addDays); 308 return d; 309 310 } 311 }, 312 // Tuesday 313 { re: /^(\w+)$/i, 314 handler: function(bits) { 315 316 var d = new Date(); 317 var wd = d.getDay(); 318 var nwd = parseWeekday(bits[1]); 319 320 // if same weekday, return date 321 if (nwd == wd) 322 return d; 323 324 // if new weekday is before current weekday 325 if (nwd < wd ) 326 { 327 328 // calculate last weekday 329 d.setDate(d.getDate() + ((wd == nwd) ? -7 : (-1 * (wd + 7 - nwd)) % 7)); 330 331 } 332 // if new weekday is after current weekday 333 else 334 { 335 336 // calculate next weekday 337 d.setDate(d.getDate() + (nwd - wd)); 338 339 } 340 341 return d; 342 343 } 344 }, 345 // mm/dd/yyyy (American style) 346 { re: /(\d{1,2})\/(\d{1,2})\/(\d{4})/, 347 handler: function(bits) { 348 349 var yyyy = parseInt(bits[3], 10); 350 var dd = parseInt(bits[2], 10); 351 var mm = parseInt(bits[1], 10) - 1; 352 353 if ( DateInRange( yyyy, mm, dd ) ) 354 return new Date(yyyy, mm, dd); 355 356 } 357 }, 358 // mm/dd/yy (American style) short year 359 { re: /(\d{1,2})\/(\d{1,2})\/(\d{1,2})/, 360 handler: function(bits) { 361 362 var d = new Date(); 363 var yyyy = d.getFullYear() - (d.getFullYear() % 100) + parseInt(bits[3], 10); 364 var dd = parseInt(bits[2], 10); 365 var mm = parseInt(bits[1], 10) - 1; 366 367 if ( DateInRange(yyyy, mm, dd) ) 368 return new Date(yyyy, mm, dd); 369 370 } 371 }, 372 // mm/dd (American style) omitted year 373 { re: /(\d{1,2})\/(\d{1,2})/, 374 handler: function(bits) { 375 376 var d = new Date(); 377 var yyyy = d.getFullYear(); 378 var dd = parseInt(bits[2], 10); 379 var mm = parseInt(bits[1], 10) - 1; 380 381 if ( DateInRange(yyyy, mm, dd) ) 382 return new Date(yyyy, mm, dd); 383 384 } 385 }, 386 // yyyy-mm-dd (ISO style) 387 { re: /(\d{4})-(\d{1,2})-(\d{1,2})/, 388 handler: function(bits) { 389 390 var yyyy = parseInt(bits[1], 10); 391 var dd = parseInt(bits[3], 10); 392 var mm = parseInt(bits[2], 10) - 1; 393 394 if ( DateInRange( yyyy, mm, dd ) ) 395 return new Date(yyyy, mm, dd); 396 397 } 398 }, 399 // yy-mm-dd (ISO style) short year 400 { re: /(\d{1,2})-(\d{1,2})-(\d{1,2})/, 401 handler: function(bits) { 402 403 var d = new Date(); 404 var yyyy = d.getFullYear() - (d.getFullYear() % 100) + parseInt(bits[1], 10); 405 var dd = parseInt(bits[3], 10); 406 var mm = parseInt(bits[2], 10) - 1; 407 408 if ( DateInRange( yyyy, mm, dd ) ) 409 return new Date(yyyy, mm, dd); 410 411 } 412 }, 413 // mm-dd (ISO style) omitted year 414 { re: /(\d{1,2})-(\d{1,2})/, 415 handler: function(bits) { 416 417 var d = new Date(); 418 var yyyy = d.getFullYear(); 419 var dd = parseInt(bits[2], 10); 420 var mm = parseInt(bits[1], 10) - 1; 421 422 if ( DateInRange( yyyy, mm, dd ) ) 423 return new Date(yyyy, mm, dd); 424 425 } 426 } 427 ]; 428 429 // parses date string input 430 function parseDateString( strDateInput ) 431 { 432 433 // cycle through date parse patterns 434 for (var i = 0; i < dateParsePatterns.length; i++) 435 { 436 437 // get regular expression for this pattern 438 var re = dateParsePatterns[i].re; 439 440 // get handler function for this pattern 441 var handler = dateParsePatterns[i].handler; 442 443 // parse input using regular expression 444 var bits = re.exec(strDateInput); 445 446 // if there was a match 447 if (bits) 448 { 449 450 alert( re ); 451 452 // return the result of the handler function (which constitutes bits into a date) 453 return handler(bits); 454 455 } 456 457 } 458 459 // if no pattern matched - throw exception 460 throw new Error("Invalid date string"); 461 462 } 463 464 // validates the input from datebox as a date 465 function DateBoxControl_Validate( ctlDateBox ) 466 { 467 468 ctlDateBox.value = ctlDateBox.value.trim(); 469 470 if ( ctlDateBox.value.length > 0 ) 471 { 472 473 try 474 { 475 476 // parse input to get date (error is raised if it can't be parsed) 477 var dtValue = parseDateString(ctlDateBox.value.trim()); 478 479 // assign date in mm/dd/yyyy format to textbox 480 ctlDateBox.value = ('0' + (dtValue.getMonth() + 1).toString()).right(2) + '/' + ('0' + dtValue.getDate().toString()).right(2) + '/' + dtValue.getFullYear().toString(); 481 482 // add more formal date to message div associated with textbox 483 if (!ctlDateBox.message.firstChild) 484 ctlDateBox.message.appendChild(document.createTextNode(dtValue.toDateString())); 485 else 486 ctlDateBox.message.firstChild.nodeValue = dtValue.toDateString(); 487 488 // swith class name back to default so styling is changed 489 ctlDateBox.message.className = 'DateBoxControlMsg'; 490 ctlDateBox.className = 'DateBoxControl'; 491 492 } 493 catch (e) 494 { 495 496 // use error message from exception 497 var strMessage = e.message; 498 499 // give a nicer message to built-in javascript exception message 500 if (strMessage.indexOf('is null or not an object') < -1) 501 strMessage = 'Invalid date string'; 502 503 // add error message to message div associated with textbox 504 if (!ctlDateBox.message.firstChild) 505 ctlDateBox.message.appendChild(document.createTextNode(strMessage)); 506 else 507 ctlDateBox.message.firstChild.nodeValue = strMessage; 508 509 // switch class name to error so styling is changed 510 ctlDateBox.message.className = 'DateBoxControlErrorMsg'; 511 ctlDateBox.className = 'DateBoxControlError'; 512 513 } 514 515 } 516 else 517 { 518 519 // clear message div associated with textbox 520 if (!ctlDateBox.message.firstChild) 521 ctlDateBox.message.appendChild(document.createTextNode('')); 522 else 523 ctlDateBox.message.firstChild.nodeValue = ''; 524 525 // swith class name back to default so styling is changed 526 ctlDateBox.message.className = 'DateBoxControlMsg'; 527 ctlDateBox.className = 'DateBoxControl'; 528 529 } 530 531 }
You need to create an account or log in to post comments to this site.