Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

JavaScript DateBoxControl (See related posts)

In input forms on web pages you often have to validate dates and users are always entering something off the wall. Simon Incutio came up with a script to parse the dates. I added some stuff and turned it into a control of sorts. It is sort of US centric with date entering but it does accept iso style entries.

   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.


Click here to browse all 5311 code snippets

Related Posts