Subversion Repositories cheapmusic

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
103 - 1
/**
2
This is a JavaScript library that will allow you to easily add some basic DHTML
3
drop-down datepicker functionality to your Notes forms. This script is not as
4
full-featured as others you may find on the Internet, but it's free, it's easy to
5
understand, and it's easy to change.
6
 
7
You'll also want to include a stylesheet that makes the datepicker elements
8
look nice. An example one can be found in the database that this script was
9
originally released with, at:
10
 
11
http://www.nsftools.com/tips/NotesTips.htm#datepicker
12
 
13
I've tested this lightly with Internet Explorer 6 and Mozilla Firefox. I have no idea
14
how compatible it is with other browsers.
15
 
16
version 1.5
17
December 4, 2005
18
Julian Robichaux -- http://www.nsftools.com
19
 
20
HISTORY
21
--  version 1.0 (Sept. 4, 2004):
22
Initial release.
23
 
24
--  version 1.1 (Sept. 5, 2004):
25
Added capability to define the date format to be used, either globally (using the
26
defaultDateSeparator and defaultDateFormat variables) or when the displayDatePicker
27
function is called.
28
 
29
--  version 1.2 (Sept. 7, 2004):
30
Fixed problem where datepicker x-y coordinates weren't right inside of a table.
31
Fixed problem where datepicker wouldn't display over selection lists on a page.
32
Added a call to the datePickerClosed function (if one exists) after the datepicker
33
is closed, to allow the developer to add their own custom validation after a date
34
has been chosen. For this to work, you must have a function called datePickerClosed
35
somewhere on the page, that accepts a field object as a parameter. See the
36
example in the comments of the updateDateField function for more details.
37
 
38
--  version 1.3 (Sept. 9, 2004)
39
Fixed problem where adding the <div> and <iFrame> used for displaying the datepicker
40
was causing problems on IE 6 with global variables that had handles to objects on
41
the page (I fixed the problem by adding the elements using document.createElement()
42
and document.body.appendChild() instead of document.body.innerHTML += ...).
43
 
44
--  version 1.4 (Dec. 20, 2004)
45
Added "targetDateField.focus();" to the updateDateField function (as suggested
46
by Alan Lepofsky) to avoid a situation where the cursor focus is at the top of the
47
form after a date has been picked. Added "padding: 0px;" to the dpButton CSS
48
style, to keep the table from being so wide when displayed in Firefox.
49
 
50
-- version 1.5 (Dec 4, 2005)
51
Added display=none when datepicker is hidden, to fix problem where cursor is
52
not visible on input fields that are beneath the date picker. Added additional null
53
date handling for date errors in Safari when the date is empty. Added additional
54
error handling for iFrame creation, to avoid reported errors in Opera. Added
55
onMouseOver event for day cells, to allow color changes when the mouse hovers
56
over a cell (to make it easier to determine what cell you're over). Added comments
57
in the style sheet, to make it more clear what the different style elements are for.
58
*/
59
 
60
var datePickerDivID = "datepicker";
61
var iFrameDivID = "datepickeriframe";
62
 
63
var dayArrayShort = new Array('Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa');
64
var dayArrayMed = new Array('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
65
var dayArrayLong = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
66
var monthArrayShort = new Array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
67
var monthArrayMed = new Array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec');
68
var monthArrayLong = new Array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');
69
 
70
// these variables define the date formatting we're expecting and outputting.
71
// If you want to use a different format by default, change the defaultDateSeparator
72
// and defaultDateFormat variables either here or on your HTML page.
73
var defaultDateSeparator = "/";        // common values would be "/" or "."
74
var defaultDateFormat = "mdy"    // valid values are "mdy", "dmy", and "ymd"
75
var dateSeparator = defaultDateSeparator;
76
var dateFormat = defaultDateFormat;
77
 
78
/**
79
This is the main function you'll call from the onClick event of a button.
80
Normally, you'll have something like this on your HTML page:
81
 
82
Start Date: <input name="StartDate">
83
<input type=button value="select" onclick="displayDatePicker('StartDate');">
84
 
85
That will cause the datepicker to be displayed beneath the StartDate field and
86
any date that is chosen will update the value of that field. If you'd rather have the
87
datepicker display beneath the button that was clicked, you can code the button
88
like this:
89
 
90
<input type=button value="select" onclick="displayDatePicker('StartDate', this);">
91
 
92
So, pretty much, the first argument (dateFieldName) is a string representing the
93
name of the field that will be modified if the user picks a date, and the second
94
argument (displayBelowThisObject) is optional and represents an actual node
95
on the HTML document that the datepicker should be displayed below.
96
 
97
In version 1.1 of this code, the dtFormat and dtSep variables were added, allowing
98
you to use a specific date format or date separator for a given call to this function.
99
Normally, you'll just want to set these defaults globally with the defaultDateSeparator
100
and defaultDateFormat variables, but it doesn't hurt anything to add them as optional
101
parameters here. An example of use is:
102
 
103
<input type=button value="select" onclick="displayDatePicker('StartDate', false, 'dmy', '.');">
104
 
105
This would display the datepicker beneath the StartDate field (because the
106
displayBelowThisObject parameter was false), and update the StartDate field with
107
the chosen value of the datepicker using a date format of dd.mm.yyyy
108
*/
109
function displayDatePicker(dateFieldName, displayBelowThisObject, dtFormat, dtSep)
110
{
111
  var targetDateField = document.getElementsByName (dateFieldName).item(0);
112
 
113
  // if we weren't told what node to display the datepicker beneath, just display it
114
  // beneath the date field we're updating
115
  if (!displayBelowThisObject)
116
    displayBelowThisObject = targetDateField;
117
 
118
  // if a date separator character was given, update the dateSeparator variable
119
  if (dtSep)
120
    dateSeparator = dtSep;
121
  else
122
    dateSeparator = defaultDateSeparator;
123
 
124
  // if a date format was given, update the dateFormat variable
125
  if (dtFormat)
126
    dateFormat = dtFormat;
127
  else
128
    dateFormat = defaultDateFormat;
129
 
130
  var x = displayBelowThisObject.offsetLeft;
131
  var y = displayBelowThisObject.offsetTop + displayBelowThisObject.offsetHeight ;
132
 
133
  // deal with elements inside tables and such
134
  var parent = displayBelowThisObject;
135
  while (parent.offsetParent) {
136
    parent = parent.offsetParent;
137
    x += parent.offsetLeft;
138
    y += parent.offsetTop ;
139
  }
140
 
141
  drawDatePicker(targetDateField, x, y);
142
}
143
 
144
 
145
/**
146
Draw the datepicker object (which is just a table with calendar elements) at the
147
specified x and y coordinates, using the targetDateField object as the input tag
148
that will ultimately be populated with a date.
149
 
150
This function will normally be called by the displayDatePicker function.
151
*/
152
function drawDatePicker(targetDateField, x, y)
153
{
154
  var dt = getFieldDate(targetDateField.value );
155
 
156
  // the datepicker table will be drawn inside of a <div> with an ID defined by the
157
  // global datePickerDivID variable. If such a div doesn't yet exist on the HTML
158
  // document we're working with, add one.
159
  if (!document.getElementById(datePickerDivID)) {
160
    // don't use innerHTML to update the body, because it can cause global variables
161
    // that are currently pointing to objects on the page to have bad references
162
    //document.body.innerHTML += "<div id='" + datePickerDivID + "' class='dpDiv'></div>";
163
    var newNode = document.createElement("div");
164
    newNode.setAttribute("id", datePickerDivID);
165
    newNode.setAttribute("class", "dpDiv");
166
    newNode.setAttribute("style", "visibility: hidden;");
167
    document.body.appendChild(newNode);
168
  }
169
 
170
  // move the datepicker div to the proper x,y coordinate and toggle the visiblity
171
  var pickerDiv = document.getElementById(datePickerDivID);
172
  pickerDiv.style.position = "absolute";
173
  pickerDiv.style.left = x + "px";
174
  pickerDiv.style.top = y + "px";
175
  pickerDiv.style.visibility = (pickerDiv.style.visibility == "visible" ? "hidden" : "visible");
176
  pickerDiv.style.display = (pickerDiv.style.display == "block" ? "none" : "block");
177
  pickerDiv.style.zIndex = 10000;
178
 
179
  // draw the datepicker table
180
  refreshDatePicker(targetDateField.name, dt.getFullYear(), dt.getMonth(), dt.getDate());
181
}
182
 
183
 
184
/**
185
This is the function that actually draws the datepicker calendar.
186
*/
187
function refreshDatePicker(dateFieldName, year, month, day)
188
{
189
  // if no arguments are passed, use today's date; otherwise, month and year
190
  // are required (if a day is passed, it will be highlighted later)
191
  var thisDay = new Date();
192
 
193
  if ((month >= 0) && (year > 0)) {
194
    thisDay = new Date(year, month, 1);
195
  } else {
196
    day = thisDay.getDate();
197
    thisDay.setDate(1);
198
  }
199
 
200
  // the calendar will be drawn as a table
201
  // you can customize the table elements with a global CSS style sheet,
202
  // or by hardcoding style and formatting elements below
203
  var crlf = "\r\n";
204
  var TABLE = "<table cols=7 class='dpTable'>" + crlf;
205
  var xTABLE = "</table>" + crlf;
206
  var TR = "<tr class='dpTR'>";
207
  var TR_title = "<tr class='dpTitleTR'>";
208
  var TR_days = "<tr class='dpDayTR'>";
209
  var TR_todaybutton = "<tr class='dpTodayButtonTR'>";
210
  var xTR = "</tr>" + crlf;
211
  var TD = "<td class='dpTD' onMouseOut='this.className=\"dpTD\";' onMouseOver=' this.className=\"dpTDHover\";' ";    // leave this tag open, because we'll be adding an onClick event
212
  var TD_title = "<td colspan=5 class='dpTitleTD'>";
213
  var TD_buttons = "<td class='dpButtonTD'>";
214
  var TD_todaybutton = "<td colspan=7 class='dpTodayButtonTD'>";
215
  var TD_days = "<td class='dpDayTD'>";
216
  var TD_selected = "<td class='dpDayHighlightTD' onMouseOut='this.className=\"dpDayHighlightTD\";' onMouseOver='this.className=\"dpTDHover\";' ";    // leave this tag open, because we'll be adding an onClick event
217
  var xTD = "</td>" + crlf;
218
  var DIV_title = "<div class='dpTitleText'>";
219
  var DIV_selected = "<div class='dpDayHighlight'>";
220
  var xDIV = "</div>";
221
 
222
  // start generating the code for the calendar table
223
  var html = TABLE;
224
 
225
  // this is the title bar, which displays the month and the buttons to
226
  // go back to a previous month or forward to the next month
227
  html += TR_title;
228
  html += TD_buttons + getButtonCode(dateFieldName, thisDay, -1, "&lt;") + xTD;
229
  html += TD_title + DIV_title + monthArrayLong[ thisDay.getMonth()] + " " + thisDay.getFullYear() + xDIV + xTD;
230
  html += TD_buttons + getButtonCode(dateFieldName, thisDay, 1, "&gt;") + xTD;
231
  html += xTR;
232
 
233
  // this is the row that indicates which day of the week we're on
234
  html += TR_days;
235
  for(i = 0; i < dayArrayShort.length; i++)
236
    html += TD_days + dayArrayShort[i] + xTD;
237
  html += xTR;
238
 
239
  // now we'll start populating the table with days of the month
240
  html += TR;
241
 
242
  // first, the leading blanks
243
  for (i = 0; i < thisDay.getDay(); i++)
244
    html += TD + "&nbsp;" + xTD;
245
 
246
  // now, the days of the month
247
  do {
248
    dayNum = thisDay.getDate();
249
    TD_onclick = " onclick=\"updateDateField('" + dateFieldName + "', '" + getDateString(thisDay) + "');\">";
250
 
251
    if (dayNum == day)
252
      html += TD_selected + TD_onclick + DIV_selected + dayNum + xDIV + xTD;
253
    else
254
      html += TD + TD_onclick + dayNum + xTD;
255
 
256
    // if this is a Saturday, start a new row
257
    if (thisDay.getDay() == 6)
258
      html += xTR + TR;
259
 
260
    // increment the day
261
    thisDay.setDate(thisDay.getDate() + 1);
262
  } while (thisDay.getDate() > 1)
263
 
264
  // fill in any trailing blanks
265
  if (thisDay.getDay() > 0) {
266
    for (i = 6; i > thisDay.getDay(); i--)
267
      html += TD + "&nbsp;" + xTD;
268
  }
269
  html += xTR;
270
 
271
  // add a button to allow the user to easily return to today, or close the calendar
272
  var today = new Date();
273
  var todayString = "Today is " + dayArrayMed[today.getDay()] + ", " + monthArrayMed[ today.getMonth()] + " " + today.getDate();
274
  html += TR_todaybutton + TD_todaybutton;
275
  html += "<button class='dpTodayButton' onClick='refreshDatePicker(\"" + dateFieldName + "\");'>this month</button> ";
276
  html += "<button class='dpTodayButton' onClick='updateDateField(\"" + dateFieldName + "\");'>close</button>";
277
  html += xTD + xTR;
278
 
279
  // and finally, close the table
280
  html += xTABLE;
281
 
282
  document.getElementById(datePickerDivID).innerHTML = html;
283
  // add an "iFrame shim" to allow the datepicker to display above selection lists
284
  adjustiFrame();
285
}
286
 
287
 
288
/**
289
Convenience function for writing the code for the buttons that bring us back or forward
290
a month.
291
*/
292
function getButtonCode(dateFieldName, dateVal, adjust, label)
293
{
294
  var newMonth = (dateVal.getMonth () + adjust) % 12;
295
  var newYear = dateVal.getFullYear() + parseInt((dateVal.getMonth() + adjust) / 12);
296
  if (newMonth < 0) {
297
    newMonth += 12;
298
    newYear += -1;
299
  }
300
 
301
  return "<button class='dpButton' onClick='refreshDatePicker(\"" + dateFieldName + "\", " + newYear + ", " + newMonth + ");'>" + label + "</button>";
302
}
303
 
304
 
305
/**
306
Convert a JavaScript Date object to a string, based on the dateFormat and dateSeparator
307
variables at the beginning of this script library.
308
*/
309
function getDateString(dateVal)
310
{
311
  var dayString = "00" + dateVal.getDate();
312
  var monthString = "00" + (dateVal.getMonth()+1);
313
  dayString = dayString.substring(dayString.length - 2);
314
  monthString = monthString.substring(monthString.length - 2);
315
 
316
  switch (dateFormat) {
317
    case "dmy" :
318
      return dayString + dateSeparator + monthString + dateSeparator + dateVal.getFullYear();
319
    case "ymd" :
320
      return dateVal.getFullYear() + dateSeparator + monthString + dateSeparator + dayString;
321
    case "mdy" :
322
    default :
323
      return monthString + dateSeparator + dayString + dateSeparator + dateVal.getFullYear();
324
  }
325
}
326
 
327
 
328
/**
329
Convert a string to a JavaScript Date object.
330
*/
331
function getFieldDate(dateString)
332
{
333
  var dateVal;
334
  var dArray;
335
  var d, m, y;
336
 
337
  try {
338
    dArray = splitDateString(dateString);
339
    if (dArray) {
340
      switch (dateFormat) {
341
        case "dmy" :
342
          d = parseInt(dArray[0], 10);
343
          m = parseInt(dArray[1], 10) - 1;
344
          y = parseInt(dArray[2], 10);
345
          break;
346
        case "ymd" :
347
          d = parseInt(dArray[2], 10);
348
          m = parseInt(dArray[1], 10) - 1;
349
          y = parseInt(dArray[0], 10);
350
          break;
351
        case "mdy" :
352
        default :
353
          d = parseInt(dArray[1], 10);
354
          m = parseInt(dArray[0], 10) - 1;
355
          y = parseInt(dArray[2], 10);
356
          break;
357
      }
358
      dateVal = new Date(y, m, d);
359
    } else if (dateString) {
360
      dateVal = new Date(dateString);
361
    } else {
362
      dateVal = new Date();
363
    }
364
  } catch(e) {
365
    dateVal = new Date();
366
  }
367
 
368
  return dateVal;
369
}
370
 
371
 
372
/**
373
Try to split a date string into an array of elements, using common date separators.
374
If the date is split, an array is returned; otherwise, we just return false.
375
*/
376
function splitDateString(dateString)
377
{
378
  var dArray;
379
  if (dateString.indexOf("/") >= 0)
380
    dArray = dateString.split("/");
381
  else if (dateString.indexOf(".") >= 0)
382
    dArray = dateString.split(".");
383
  else if (dateString.indexOf("-") >= 0)
384
    dArray = dateString.split("-");
385
  else if (dateString.indexOf("\\") >= 0)
386
    dArray = dateString.split("\\");
387
  else
388
    dArray = false;
389
 
390
  return dArray;
391
}
392
 
393
/**
394
Update the field with the given dateFieldName with the dateString that has been passed,
395
and hide the datepicker. If no dateString is passed, just close the datepicker without
396
changing the field value.
397
 
398
Also, if the page developer has defined a function called datePickerClosed anywhere on
399
the page or in an imported library, we will attempt to run that function with the updated
400
field as a parameter. This can be used for such things as date validation, setting default
401
values for related fields, etc. For example, you might have a function like this to validate
402
a start date field:
403
 
404
function datePickerClosed(dateField)
405
{
406
  var dateObj = getFieldDate(dateField.value);
407
  var today = new Date();
408
  today = new Date(today.getFullYear(), today.getMonth(), today.getDate());
409
 
410
  if (dateField.name == "StartDate") {
411
    if (dateObj < today) {
412
      // if the date is before today, alert the user and display the datepicker again
413
      alert("Please enter a date that is today or later");
414
      dateField.value = "";
415
      document.getElementById(datePickerDivID).style.visibility = "visible";
416
      adjustiFrame();
417
    } else {
418
      // if the date is okay, set the EndDate field to 7 days after the StartDate
419
      dateObj.setTime(dateObj.getTime() + (7 * 24 * 60 * 60 * 1000));
420
      var endDateField = document.getElementsByName ("EndDate").item(0);
421
      endDateField.value = getDateString(dateObj);
422
    }
423
  }
424
}
425
 
426
*/
427
function updateDateField(dateFieldName, dateString)
428
{
429
  var targetDateField = document.getElementsByName (dateFieldName).item(0);
430
  if (dateString)
431
    targetDateField.value = dateString;
432
 
433
  var pickerDiv = document.getElementById(datePickerDivID);
434
  pickerDiv.style.visibility = "hidden";
435
  pickerDiv.style.display = "none";
436
 
437
  adjustiFrame();
438
  targetDateField.focus();
439
 
440
  // after the datepicker has closed, optionally run a user-defined function called
441
  // datePickerClosed, passing the field that was just updated as a parameter
442
  // (note that this will only run if the user actually selected a date from the datepicker)
443
  if ((dateString) && (typeof(datePickerClosed) == "function"))
444
    datePickerClosed(targetDateField);
445
}
446
 
447
 
448
/**
449
Use an "iFrame shim" to deal with problems where the datepicker shows up behind
450
selection list elements, if they're below the datepicker. The problem and solution are
451
described at:
452
 
453
http://dotnetjunkies.com/WebLog/jking/archive/2003/07/21/488.aspx
454
http://dotnetjunkies.com/WebLog/jking/archive/2003/10/30/2975.aspx
455
*/
456
function adjustiFrame(pickerDiv, iFrameDiv)
457
{
458
  // we know that Opera doesn't like something about this, so if we
459
  // think we're using Opera, don't even try
460
  var is_opera = (navigator.userAgent.toLowerCase().indexOf("opera") != -1);
461
  if (is_opera)
462
    return;
463
 
464
  // put a try/catch block around the whole thing, just in case
465
  try {
466
    if (!document.getElementById(iFrameDivID)) {
467
      // don't use innerHTML to update the body, because it can cause global variables
468
      // that are currently pointing to objects on the page to have bad references
469
      //document.body.innerHTML += "<iframe id='" + iFrameDivID + "' src='javascript:false;' scrolling='no' frameborder='0'>";
470
      var newNode = document.createElement("iFrame");
471
      newNode.setAttribute("id", iFrameDivID);
472
      newNode.setAttribute("src", "javascript:false;");
473
      newNode.setAttribute("scrolling", "no");
474
      newNode.setAttribute ("frameborder", "0");
475
      document.body.appendChild(newNode);
476
    }
477
 
478
    if (!pickerDiv)
479
      pickerDiv = document.getElementById(datePickerDivID);
480
    if (!iFrameDiv)
481
      iFrameDiv = document.getElementById(iFrameDivID);
482
 
483
    try {
484
      iFrameDiv.style.position = "absolute";
485
      iFrameDiv.style.width = pickerDiv.offsetWidth;
486
      iFrameDiv.style.height = pickerDiv.offsetHeight ;
487
      iFrameDiv.style.top = pickerDiv.style.top;
488
      iFrameDiv.style.left = pickerDiv.style.left;
489
      iFrameDiv.style.zIndex = pickerDiv.style.zIndex - 1;
490
      iFrameDiv.style.visibility = pickerDiv.style.visibility ;
491
      iFrameDiv.style.display = pickerDiv.style.display;
492
    } catch(e) {
493
    }
494
 
495
  } catch (ee) {
496
  }
497
 
498
}