1/** 2 * The client-side representation of the DateSelectorControl class. 3 * Implemented as a JS object to encapsulate all client-side functionality required 4 * by the custom control. 5 * A new parameter exists now that associates a departure date custom control to an arrival 6 * date custom control, which is updated on arrival change to arrival + 3 days. 7 * validateDateFormat is a function type parameter to do validation on the 'FreeFormat' selection Mode 8 */ 9function DateSelectorControl(controlID, dateFormat, selectionMode, validateDateFormat) 10{ 11 12 // ======================================================================= 13 // Attributes 14 // ======================================================================= 15 var _ddlDay = ((selectionMode=="FreeFormat") ? null : document.getElementById(controlID + "_ddlDay")); 16 var _ddlMonthYear = ((selectionMode=="FreeFormat") ? null : document.getElementById(controlID + "_ddlMonthYear")); 17 var _ddlDayParent = ((selectionMode=="FreeFormat") ? null : document.getElementById(controlID + "lblDayContainer")); 18 var _txtCurrentDate = document.getElementById(controlID + "_txtCurrentDate"); 19 var _txtPeriodStart = document.getElementById(controlID + "_txtPeriodStart"); 20 var _txtPeriodEnd = document.getElementById(controlID + "_txtPeriodEnd"); 21 var _hdnStayLength = document.getElementById(controlID + "_hdnStayLength"); 22 var _localInstance = this; 23 var _dateFormat = dateFormat; 24 var oneDay = 86400000;//24*60*60*1000; hours*minutes*seconds*milliseconds 25 26 var _associatedDepartureControlInstance; 27 28 // ======================================================================= 29 // Methods 30 // ======================================================================= 31 /** 32 * Used to help ensure that the drop-down lists work as expected. If, for example, 33 * the user has a current selection of 31 August, and changes the month to June, 34 * the display will revert to 01 July, since this is, effectively, 31 June. This 35 * method, then, will make sure that any such dates are properly rounded down to 36 * the maximum day for the new month. 37 * @param newDay the new date to be tested 38 * @param newMonth the new month to be tested 39 * @param newYear the new year to be tested 40 * @return the newDate, truncated if required 41 */ 42 var createDate = function(newDay, newMonth, newYear) { 43 var maxDay = new Date(newYear, newMonth + 1, 0).getDate(); 44 return new Date(newYear, newMonth, Math.min(newDay, maxDay)); 45 }; 46 47 /** 48 * Basic event handler that is called when the day drop-down changes. 49 */ 50 var ddlDayOnChange = function () { 51 var originalDate = _localInstance.getDate(); //This variable was renamed because it actually held the original date value, not what was selected in the DDL 52 if (originalDate != null) { 53 dateSelection(originalDate); // 54 setDateSelectorAttributes(originalDate); 55 56 var selectedDate = _localInstance.getDate(); 57 58 //Sync associated departure control if defined 59 if (_associatedDepartureControlInstance != null) { 60 syncDeparture(selectedDate); 61 } 62 } 63 }; 64 65 /** 66 * Basic event handler that is called when the month/year drop-down changes. 67 */ 68 var ddlMonthYearOnChange = function () { 69 var originalDate = _localInstance.getDate(); 70 if (originalDate != null) { 71 monthYearSelection(originalDate, _localInstance.getPeriodStart(), _localInstance.getPeriodEnd()); 72 setDateSelectorAttributes(originalDate); 73 74 var selectedDate = _localInstance.getDate(); 75 76 //Sync associated departure control if defined 77 if (_associatedDepartureControlInstance != null) { 78 syncDeparture(selectedDate); 79 } 80 } 81 }; 82 83 84 /** 85 * When a value is chosen from the date drop-down list, create a new date with 86 * the old month/year and the new day, and update the date field. 87 * The function will bypass if the new date selection equals the old. 88 * @param selectedDate the last selected date - taken from the calendar 89 */ 90 var dateSelection = function (selectedDate) { 91 if (_ddlDay != null) { 92 var newSelectionValue = parseInt(_ddlDay.value, 10); 93 94 if (newSelectionValue != selectedDate.getDate()) { 95 var newSelectionDate = createDate(newSelectionValue, selectedDate.getMonth(), selectedDate.getFullYear()); 96 $('#' + controlID + '_txtCurrentDate').val(newSelectionDate.format(_dateFormat)); 97 } 98 } 99 }; 100 101 /** 102 * This method syncs all associated controls with the calendar - dropdownlists and, if defined, 103 * the departure date control. 104 */ 105 this.syncAll = function () { 106 var selectedDate = this.getDate(); 107 if (selectedDate != null) { 108 setSelectedMonthYear(selectedDate); 109 setSelectedDate(selectedDate, _localInstance.getPeriodStart(), _localInstance.getPeriodEnd()); 110 111 //Sync associated departure control if defined - the rule is current date plus 1 day (in milliseconds)
112 if (_associatedDepartureControlInstance != null) { 113 syncDeparture(this.getDate()); 114 setTimeout(function () { _associatedDepartureControlInstance.showCalander(); }, 200); 115 } 116 } 117 }; 118 119 /** 120 * This method shows the datepicker control associated with the date selector control. 121 */ 122 this.showCalander = function () { 123 $('#' + controlID + '_txtCurrentDate').datepicker("refresh"); 124 $('#' + controlID + '_txtCurrentDate').datepicker("show"); 125 }; 126 127 128 /** 129 * this to maintain the difference between the arrive/depart dates 130 * @param numberOfNights the number of nights 131 */ 132 this.setStayLength = function (numberOfNights) { 133 $(_hdnStayLength).val(numberOfNights); 134 onChangeHandler(); 135 }; 136 137 this.getStayLength = function() { 138 return $(_hdnStayLength).val(); 139 }; 140 141 var syncDeparture = function (selectedDate) { 142 //Calculate 3 days after the arrival date (this instance), 143 //set the Departure control's new date and invoke its syncAll() 144 var newDepartureTime = selectedDate.getTime() + parseInt($('#' + controlID + '_hdnStayLength').val()) * oneDay; 145 var newDepartureDate = new Date(newDepartureTime); 146 _associatedDepartureControlInstance.setDate(newDepartureDate); 147 _associatedDepartureControlInstance.syncAll(); 148 }; 149 150 /** 151 * Allows clients to retrieve the curent date in this date selector. 152 * Null is returned if the calendar can't be retrieved, or doesn't 153 * have a value and finally, if the value is matched the place holder. 154 * @return the currently selected date 155 */ 156 this.getDate = function() { 157 var result = null; 158 if ((_txtCurrentDate.value != null) && (_txtCurrentDate.value != "") && (_txtCurrentDate.value !== _txtCurrentDate.getAttribute("placeholder"))) { 159 result = parseDate(_txtCurrentDate.value, dateFormat); 160 } 161 return result; 162 }; 163 164 /** 165 * Allows clients to retrieve the period end of this date selector, 166 * Null is returned if the calendar can't be retrieved, or doesn't have a value. 167 * @return the current end date of the valid date range 168 */ 169 this.getPeriodEnd = function () { 170 var result = null; 171 if (_txtPeriodEnd != null) { 172 result = parseDate(_txtPeriodEnd.value, dateFormat); 173 } 174 return result; 175 }; 176 177 /** 178 * Allows clients to retrieve the period start of this date selector, 179 * Null is returned if the calendar can't be retrieved, or doesn't have a value. 180 * @return the current start date of the valid date range 181 */ 182 this.getPeriodStart = function () { 183 var result = null; 184 if (_txtPeriodStart != null) { 185 result = parseDate(_txtPeriodStart.value, dateFormat); 186 } 187 return result; 188 }; 189 190 /** 191 * This function should be called immediately after creation to ensure that 192 * all appropriate listeners, etc., are properly initialised. 193 */ 194 this.initialise = function () { 195 // Do all of this the long-winded way, otherwise ASP.Net's validators 196 // can come along and screw up our scoping. 197 if (typeof _txtCurrentDate.attachEvent != 'undefined') { 198 _txtCurrentDate.detachEvent('onchange', onCalendarDateChange); 199 _txtCurrentDate.attachEvent('onchange', onCalendarDateChange); 200 } 201 else if (typeof _txtCurrentDate.addEventListener != 'undefined') { 202 _txtCurrentDate.removeEventListener('change', onCalendarDateChange, false); 203 _txtCurrentDate.addEventListener('change', onCalendarDateChange, false); 204 } 205 //Use JQuery change handler for hidden Calendar Date field. 206 //Update : was not firing when external source changed date, so also added to setDate() function 207 $('#' + controlID + '_txtCurrentDate').change(onChangeHandler); 208 209 if (_ddlMonthYear != null) { 210 if (typeof _ddlMonthYear.attachEvent != 'undefined') { 211 _ddlMonthYear.detachEvent('onchange', ddlMonthYearOnChange); 212 _ddlMonthYear.attachEvent('onchange', ddlMonthYearOnChange); 213 } 214 else if (typeof _ddlMonthYear.addEventListener != 'undefined') { 215 _ddlMonthYear.removeEventListener('change', ddlMonthYearOnChange, false); 216 _ddlMonthYear.addEventListener('change', ddlMonthYearOnChange, false); 217 } 218 } 219 initialiseDateListeners(); 220 221 // Initialisation has to happen after everything loads, but ONLY for IE. 222 // All other browsers work fine otherwise; IE has issues with DOM loading 223 // and the order in which things are initialised. 224 Sys.Application.add_load(onLoadHandler); 225 226 // Make the text box to be read only when the device is a touch device and the format is Free Format 227 if (selectionMode === "FreeFormat" && typeof Modernizr === 'object') { 228 if (Modernizr.touch) { 229 _txtCurrentDate.setAttribute("readonly","true"); 230 } 231 } 232 233 234 }; 235 236 /** 237 * Used just after control initialisation, on the body loaded event to associate the instance of the departure control. 238 */
239 this.setAssociatedDepartureControl = function (associatedDepartureControlInstance) { 240 _associatedDepartureControlInstance = associatedDepartureControlInstance; 241 }; 242 243 var onChangeHandler = function () { 244 if (selectionMode !== "FreeFormat") { 245 //The following syncs DDL's with the hidden text box. 246 var selectedDate = parseDate(_txtCurrentDate.value, dateFormat); 247 248 setSelectedMonthYear(selectedDate); 249 setSelectedDate(selectedDate, _localInstance.getPeriodStart(), _localInstance.getPeriodEnd()); 250 } 251 }; 252 253 var onLoadHandler = function () { 254 // The following will force the various parts of this control (dropdowns, 255 // calendar extenders, text fields, etc., to align. It comes into play on 256 // back-button navigation. 257 if (selectionMode !== "FreeFormat" && _txtCurrentDate.value !== "") { 258 var selectedDate = parseDate(_txtCurrentDate.value, dateFormat); 259 260 setSelectedMonthYear(selectedDate); 261 setSelectedDate(selectedDate, _localInstance.getPeriodStart(), _localInstance.getPeriodEnd()); 262 } 263 264 // Make sure that the event isn't re-fired on async postback. 265 Sys.Application.remove_load(onLoadHandler); 266 }; 267 268 /** 269 * Due to bugs in safari, the date drop-down list must be re-created 270 * whenever it is updated dynamically. This includes the listeners 271 * applied to it by this class, and so this method is called from two 272 * places: initialise() and setSelectedDate(). 273 */ 274 var initialiseDateListeners = function () { 275 if (_ddlDay != null) { 276 if (typeof _ddlDay.attachEvent != 'undefined') { 277 _ddlDay.detachEvent('onchange', ddlDayOnChange); 278 _ddlDay.attachEvent('onchange', ddlDayOnChange); 279 } 280 else if (typeof _ddlDay.addEventListener != 'undefined') { 281 _ddlDay.removeEventListener('change', ddlDayOnChange, false); 282 _ddlDay.addEventListener('change', ddlDayOnChange, false); 283 } 284 } 285 }; 286 287 /** 288 * When a value is chosen from the month/year drop-down list, create a new 289 * date that combines this new month/year with the current date selection. 290 * Ensure that this new date is properly rounded to within the current 291 * period start and end dates, set the date text field & trigger update events. 292 * The function will bypass if the new date selection equals the old. The values of all items 293 * in the month/year drop-down are in the form MMyyyy, and are used to extract 294 * the appropriate values for the new date. 295 * @param selectedDate the last selected date - taken from the calendar 296 * @param periodStart the period start date set against this control 297 * @param periodEnd the period end date set against this control 298 */ 299 var monthYearSelection = function (selectedDate, periodStart, periodEnd) { 300 if (_ddlMonthYear != null) { 301 var newSelectionValue = _ddlMonthYear.value; 302 var newSelectionDate = createDate(selectedDate.getDate(), parseInt(newSelectionValue.substring(0, 2), 10) - 1, parseInt(newSelectionValue.substring(2), 10)); 303 if (newSelectionDate != selectedDate) { 304 newSelectionDate = validateDateAgainstRange(newSelectionDate, periodStart, periodEnd); 305 $('#' + controlID + '_txtCurrentDate').val(newSelectionDate.format(_dateFormat)); 306 setSelectedDate(newSelectionDate, periodStart, periodEnd); 307 } 308 } 309 }; 310 311 /** 312 * An event handler that is tied to the onchange event of the hidden text 313 * field in this control that contains the current date selection. The value 314 * may be changed from a variety of sources, both inside and outside this 315 * object, and any change will cause the related controls here to change. 316 */ 317 var onCalendarDateChange = function () { 318 var selectedDate = _localInstance.getDate(); 319 if (selectedDate != null) { 320 setSelectedMonthYear(selectedDate); 321 setSelectedDate(selectedDate, _localInstance.getPeriodStart(), _localInstance.getPeriodEnd()); 322 323 // The calendar date should have been updated by the lines above. 324 setDateSelectorAttributes(_localInstance.getDate()); 325 326 if (selectionMode == "FreeFormat" && selectedDate != "Invalid Date" && _txtCurrentDate.value !== selectedDate.format(_dateFormat) && typeof (validateDateFormat) !== "undefined" && validateDateFormat(this)) { 327 _txtCurrentDate.value = selectedDate.format(_dateFormat); 328 } 329 330 // The custom date change handler used to be added to the corresponding 331 // field during initialise, AFTER the local listener. However, IE has a 332 // problem with guaranteeing the order of event listeners, so the handler 333 // is invoked here, AFTER all local changes have been made. 334 if ((typeof (customDateChangeHandler) !== "undefined") && (typeof (customDateChangeHandler) == "function")) { 335 customDateChangeHandler(); 336 } 337 } 338 }; 339 340 /** 341 * Used to get around various cross-browser date-parsing issues. The format 342 * provided here is generally supplied as the dateFormat attribute of this 343 * class. If no format is provided, a default of "yyyy-MM-dd" is assumed. 344 * In any case, it depends on 4-digits years (yyyy), 2-digit months (dd), 345 * and 2-digit days (dd). 346 * @param stringValue the value to be parsed 347 * @param format the date format to be applied 348 */ 349 var parseDate = function (stringValue, format) { 350 351 format = format.toLowerCase() || 'yyyy-mm-dd'; 352 //if the date string includes mmm or mmmm, it's ok to convert from string to Date 353 if (format.search("mmm") > 0 || format.search("mmmm") > 0) { 354 return new Date(stringValue); 355 } 356 357 var parts = stringValue.match(/(\d+)/g), 358 i = 0, 359 fmt = {}; 360 361 format.replace(/(yyyy|dd|mm)/g, function (part) { fmt[part] = i++; }); 362 return new Date(parts[fmt['yyyy']], parts[fmt['mm']] - 1, parts[fmt['dd']]); 363 364 }; 365 366 /** 367 * Allows clients to set the date of this date selector. The calendar 368 * and all associated controls within this selector has its date set 369 * to the given value. 370 * @param selectedDate the new date value for this control 371 */ 372 this.setDate = function (selectedDate) { 373 $('#' + controlID + '_txtCurrentDate').val(selectedDate.format(_dateFormat)); 374 onChangeHandler(); 375 }; 376 377 /** 378 * A function that updates the date-specific attributes of this date 379 * selector with the values of the given date. The value may be stored 380 * in more than one format against this control. 381 * @param selectedDate the new date to update local attributes with 382 */ 383 var setDateSelectorAttributes = function(selectedDate) 384 { 385 document.getElementById(controlID).setAttribute("selecteddate", selectedDate.format("yyyy-MM-dd")); 386 }; 387 388 /** 389 * Forces an update on the date drop-down list, typically as the result of a 390 * selection made in the associated calendar. The start and end dates for the 391 * period defined against this control are also passed in, since the new date 392 * may require som truncation to fit within this period. The current contents 393 * of the drop-down are cleared, and replaced with all dates in the month of 394 * the given selectedDate. 395 * @param selectedDate the date that has been selected, typically in the calendar
396 * @param periodStart the start date of the period defined in this control 397 * @param periodEnd the end date of the period defined in this control 398 */ 399 var setSelectedDate = function (selectedDate, periodStart, periodEnd) { 400 if (_ddlDay != null) { 401 var minDay = 1; 402 var maxDay = new Date(selectedDate.getFullYear(), selectedDate.getMonth() + 1, 0).getDate(); 403 if ((periodStart != null) && (selectedDate.getMonth() == periodStart.getMonth()) && (selectedDate.getFullYear() == periodStart.getFullYear())) { 404 minDay = Math.max(minDay, periodStart.getDate()); 405 } 406 if ((periodEnd != null) && (selectedDate.getMonth() == periodEnd.getMonth()) && (selectedDate.getFullYear() == periodEnd.getFullYear())) { 407 maxDay = Math.min(maxDay, periodEnd.getDate()); 408 } 409 410 var updateSelector = _ddlDay; 411 var sBrowser = navigator.userAgent; 412 413 // The following workaround addresses a Safari bug, in which dynamically 414 // updates SELECTs are not repainted properly. The select is basically removed 415 // from memory and recreated before being filled. 416 if ((sBrowser.toLowerCase().indexOf('webkit') > -1) || (sBrowser.toLowerCase().indexOf('safari') > -1)) { 417 var dateSelectorID = updateSelector.id; 418 var dateSelectorName = updateSelector.name; 419 var dateSelectorOnChange = updateSelector.onchange; 420 421 if (_ddlDayParent != null) { 422 _ddlDayParent.innerHTML = ""; 423 424 updateSelector = document.createElement("SELECT"); 425 updateSelector.id = dateSelectorID; 426 updateSelector.name = dateSelectorName; 427 updateSelector.onchange = dateSelectorOnChange; 428 _ddlDayParent.appendChild(updateSelector); 429 430 _ddlDay = updateSelector; 431 initialiseDateListeners(); 432 } 433 } 434 var selectedOption = null; 435 updateSelector.options.length = 0; 436 var baseDate = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1); 437 for (var i = minDay; i <= maxDay; i++) { 438 baseDate.setDate(i); 439 var dateValue = ((i < 10) ? "0" : "") + i; 440 var dateString = baseDate.format("d (ddd)"); 441 442 var option = document.createElement("OPTION"); 443 option.text = dateString; 444 option.value = dateValue; 445 updateSelector.options.add(option); 446 447 if (i == selectedDate.getDate()) { 448 option.setAttribute('selected', true); 449 selectedOption = option; 450 } 451 } 452 if (selectedOption != null) { 453 updateSelector.selectedIndex = selectedOption.index; 454 } 455 } 456 }; 457 458 /** 459 * Forces an update to the selected month and year drop-down list. The given 460 * date is generally taken from the associated calendar after a selection. The 461 * format of all values in the month/year drop-down - MMyyyy - is used to specify 462 * the new selection. 463 * @param selectedDate the date that has been selected, typically in the calendar 464 */ 465 var setSelectedMonthYear = function (selectedDate) { 466 if (_ddlMonthYear != null) { 467 _ddlMonthYear.value = selectedDate.format("MMyyyy"); 468 } 469 }; 470 471 /** 472 * A utility function that will round the given date to within the boundaries 473 * imposed by the given period start and end dates. Either of these range values 474 * can be null, indicating an "open" set of dates. 475 * @param selectedDate the date to be converted 476 * @param periodStart the start date of the period 477 * @param periodEnd the end date of the period 478 * @return the given selectedDate, possibly altered to fit within the range 479 */
480 var validateDateAgainstRange = function (selectedDate, periodStart, periodEnd) { 481 var result = selectedDate; 482 483 if ((periodStart != null) && (result < periodStart)) { 484 result = new Date(periodStart.getTime()); 485 } 486 if ((periodEnd != null) && (result > periodEnd)) { 487 result = new Date(periodEnd.getTime()); 488 } 489 return result; 490 }; 491}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.