1window.eviivo = window.eviivo ? window.eviivo : {}; 2window.eviivo.availabilitySearch = window.eviivo.availabilitySearch ? window.eviivo.availabilitySearch : {}; 3 4window.eviivo.availabilitySearch = function ($) { 5 6 var defaultOptions = { 7 isDefaultReferrer: true, 8 defaultNightsServiced: 0, 9 roomsLimit: 3, //default room limit is 3 if not overriden by external settings 10 eviivoSearchBoxId: "#eviivo-availability-search", //default serach box id is "eviivo-availability-search" if not overriden by external settings 11 cultureLanguageCode: "en-GB", 12 baseSearchUrl: "", 13 resources: { 14 adults: "Adults", 15 children: "Children", 16 rooms: "Room(s)", 17 room: "Room" 18 }, 19 //have this search criteria as a default for easier way to parse the query string and easier way to customize the search if properties want to 20 criteria: 21 { 22 startDate: null, 23 endDate: null, 24 occupancy: [{ adults: 2, children: 0 }] 25 } 26 }; 27 var options; 28 var occupancySelectionHtmlClone; 29 var roomsRow; 30 var currentDate; 31 var simpleVersion; 32 33 var $tooltip; 34 var $addButton; 35 var $removeButton; 36 var $searchButton; 37 var $startDate; 38 var $endDate; 39 var $doneButton; 40 var $occupancySelectors; 41 var $firstAdultsOccupancy; 42 var $showPricesButtons; 43 var $searchBar; 44 var $datePicker; 45 var $viewportWidth; 46 var $safariBrowser; 47 var $isChannelSearch; 48 var querystringHelper; 49 var $formFieldSet; 50 var $datePickerSelectionToolTip; 51 var $infoTooltip; 52 53 ///<summary>extend Date javascript object and add extra function "addHours"</summary> 54 Date.prototype.addHours = function (hours) { 55 this.setHours(this.getHours() + hours); 56 return this; 57 }; 58 59 ///<summary>Main init method of the eviivo.availabilitySearch object</summary> 60 ///<param name="settings">external settings</param> 61 function init(settings) { 62 //extend internal settings with external ones 63 options = $.extend(defaultOptions, settings); 64 65 //cache jquerySelectors either as jQuery objects or just as simple text selectors 66 occupancySelectionHtmlClone = $(options.eviivoSearchBoxId + " div.column-outer").html(); 67 roomsRow = options.eviivoSearchBoxId + " div.column-inner"; 68 69 //cache jquery objects 70 $tooltip = $(options.eviivoSearchBoxId + " div.cp-tooltip"); 71 $addButton = $(options.eviivoSearchBoxId + " #btn-addRoom"); 72 $removeButton = $(options.eviivoSearchBoxId + " #btn-removeRoom"); 73 $searchButton = $(options.eviivoSearchBoxId + " button.cp-button-price"); 74 $showPricesButtons = $("#mod-results div.results-price button.cp-button-showprices"); 75 $startDate = $(options.eviivoSearchBoxId + " input#eviivo-start-date"); 76 $endDate = $(options.eviivoSearchBoxId + " input#eviivo-end-date"); 77 $doneButton = $(options.eviivoSearchBoxId + " .actions button"); 78 $firstAdultsOccupancy = $(options.eviivoSearchBoxId + " div.column-inner:first-child span.column-adult span.ui-selectmenu-button"); 79 $searchBar = $("#eviivo-availability-search"); 80 $datePicker = $("#ui-datepicker-div"); 81 $isChannelSearch = $("#submit-reservation #cbe"); 82 $formFieldSet = $('.column-outer'); 83 $viewportWidth = $(window).width(); 84 //use this array to cache the occupancy selector menus for easier manipulation 85 $occupancySelectors = []; 86 $safariBrowser = false; 87 $datePickerSelectionToolTip = $('#datepicker-tooltip'); 88 $infoTooltip = $(".cp-tooltip-info"); 89 querystringHelper = eviivo.utils.queryStringHelper; 90 if (typeof querystringHelper === "undefined") { 91 console.log("eviivo.utils.queryStringHelper was not loaded therefore this page might not work properly"); 92 } 93 94 //init datepicker (set culture and min date - not in the past) 95 var minDate = new Date(); 96 if (options.defaultNightsServiced == null || options.isDefaultReferrer === false) { 97 options.defaultNightsServiced = 0; 98 } 99 minDate.setDate(minDate.getDate() + options.defaultNightsServiced); 100 101 var dtFormat = eviivo.webcore.formatting.datetime.evGetDateDisplayFormat(options.cultureLanguageCode, "medium"); 102 103 //setup culture and get users current culture 104 var culture = "en-gb"; 105 if (options.selectedCultureLanguageCode != null) { 106 culture = options.selectedCultureLanguageCode; 107 } 108 109 //Date has been extended via eviivo.webcore.formatting.js, this function takes the current users culture as a parameter and then determines what first day of the week should be, returning it as an offset used with date pickers 110 var d = new Date(); 111 var firstDayOffset = d.evStandardFirstDayCalendarOffset(culture); 112 113 $.datepicker.setDefaults( 114 $.extend( 115 { 116 'dateFormat': dtFormat, 117 'altFormat': dtFormat, 118 'showOtherMonths': true, 119 'selectOtherMonths': true, 120 'minDate': minDate, 121 'firstDay': firstDayOffset 122 } 123 ) 124 ); 125 126 $startDate.datepicker({ 127 onSelect: startDateSelectHandler 128 }); 129 130 $endDate.datepicker({ 131 onSelect: endDateSelectHandler 132 }); 133 134 //init end datepicker with culture specifics, min date as current date and onSelect custom handler 135 $endDate.datepicker(); 136 137 //attach click handler for add room button 138 $addButton.on("click", addRoomClickHandler); 139 //attach click handler for remove room button 140 $removeButton.on("click", removeRoomClickHandler); 141 $datePickerSelectionToolTip.on("click", clearDatePickerTooltip); 142 //attach click handler for hiding the occupancy tooltip (to "done" button and to DOM document) 143 $doneButton.on("click", tooltipHide).on("blur", tooltipHide); 144 $(document).on('keydown', tooltipHideEscape); 145 146 //attach click handler for showing the occupancy tooltip 147 $(options.eviivoSearchBoxId + " .icon-guest").on("click", tooltipShow); 148 $(options.eviivoSearchBoxId + " .icon-guest a").on("click", tooltipShow); 149 150 //initialize the first row of occupancy selectors in the occupancy tooltip 151 initSelectMenu($(options.eviivoSearchBoxId + " div.column-inner:first-child select.cp-select"));
152 153 //handle the click outside the occupancy tooltip (close the tooltip and focus on the search price button) 154 $(document).on("click", tooltipOutsideClick); 155 156 //action for search button 157 $searchButton.on("click", triggerSearch); 158 159 //action for show prices button 160 $showPricesButtons.on("click", showStartDateCalendar); 161 162 //first try to extract search criteria from query string 163 extractCriteria(); 164 //init search criteria (either from extracted query string keys or from default values) 165 initSearchForm(); 166 167 $infoTooltip.on("click", hideInfoToolTip); 168 169 170 validate(true); 171 172 173 } 174 175 176 ///<summary>Initialize occupancy selectors in the occupancy tooltip</summary> 177 ///<param name="selector">the dropdown jquery selector</param> 178 function initSelectMenu(selector) { 179 var selectors = selector.selectmenu({ 180 change: highlightOccupancy 181 }); 182 183 $occupancySelectors.push({ adults: selectors[0], children: selectors[1] }); 184 } 185 186 ///<summary>Occupancy selector onChange handler</summary> 187 function occupancySelectHandler() { 188 var totalAdults = 0; 189 $(options.eviivoSearchBoxId + " div.column-inner span.column-adult .ui-selectmenu-text").each(function (i, e) { 190 totalAdults += extractNumber($(e)); 191 }); 192 193 var totalChildren = 0; 194 $(options.eviivoSearchBoxId + " div.column-inner span.column-child .ui-selectmenu-text").each(function (i, e) { 195 totalChildren += extractNumber($(e)); 196 }); 197 198 var numberOfRooms = $(roomsRow).length; 199 var searchBoxText = $(options.eviivoSearchBoxId + " .cp-inputValues a span"); 200 var roomText = ""; 201 if (numberOfRooms > 1) { 202 roomText = options.resources.rooms; 203 } else { 204 roomText = options.resources.room; 205 } 206 searchBoxText.html(totalAdults + " " + options.resources.adults + " " + totalChildren + " " + options.resources.children + " " + numberOfRooms + " " + roomText); 207 } 208 209 ///<summary>highlight the occupancy text when switching values</summary> 210 function highlightOccupancy() { 211 occupancySelectHandler(); 212 var searchBoxText = $(options.eviivoSearchBoxId + " .cp-inputValues a"); 213 searchBoxText.fadeIn("slow", function () { 214 searchBoxText.css("color", "#000"); 215 }); 216 } 217 218 ///<summary>Give each room added a number</summary> 219 function numberRoomTitle() { 220 var count = 1; 221 $(".column-outer .column-inner").each(function () { 222 var countTitle = options.resources.room + " " + count; 223 224 $(this).find(".room").html(countTitle); 225 226 count++; 227 }); 228 } 229 230 //<summary>Change number of rooms selected in helper text</summary> 231 function numberRoomHelper() { 232 var roomTotal = $(".column-outer .column-inner").length; 233 var roomRow = $(".room-counter"); 234 var resource; 235 if (roomTotal > 1) { 236 resource = options.resources.roomsAdded; 237 } else { 238 resource = options.resources.roomAdded; 239 } 240 var roomHtml = resource.replace(new RegExp('\\{' + 0 + '\\}', 'gm'), roomTotal); 241 roomRow.html(roomHtml); 242 roomRow.addClass('pulse'); 243 244 var newRow = roomRow.clone(true); 245 roomRow.before(newRow); 246 $(".room-counter:last").remove(); 247 } 248 249 ///<summary>Add room onClick handler</summary> 250 function addRoomClickHandler() { 251 var numberOfRooms = $(roomsRow).length; 252 if (numberOfRooms < options.roomsLimit) { 253 $(options.eviivoSearchBoxId + " div.column-outer").append(occupancySelectionHtmlClone).addClass("cloned"); 254 initSelectMenu($(options.eviivoSearchBoxId + " div.column-inner:last-child select.cp-select")); 255 occupancySelectHandler(); 256 numberRoomTitle(); 257 numberRoomHelper(); 258 } 259 setTooltipButtonsStatus(); 260 } 261 262 ///<summary>Remove room onClick handler</summary> 263 function removeRoomClickHandler() { 264 var numberOfRooms = $(roomsRow).length; 265 if (numberOfRooms > 1) { 266 $(options.eviivoSearchBoxId + " div.column-inner:last").remove(); 267 occupancySelectHandler(); 268 numberRoomHelper(); 269 } 270 setTooltipButtonsStatus();
271 } 272 273 ///<summary>Set the disabled status of the add and remove room buttons in the occupancy tooltip</summary> 274 function setTooltipButtonsStatus() { 275 var numberOfRooms = $(roomsRow).length; 276 if (numberOfRooms > 1) { 277 $removeButton.removeAttr("disabled"); 278 } else { 279 $removeButton.attr("disabled", "disabled"); 280 //once disabled the "remove button" we focus on the "add button" for better interaction 281 $addButton.focus(); 282 } 283 if (numberOfRooms < options.roomsLimit) { 284 $addButton.removeAttr("disabled"); 285 } else { 286 $addButton.attr("disabled", "disabled"); 287 //once disabled the "add button" we focus on the "remove button" for better interaction 288 $removeButton.focus(); 289 } 290 $(options.eviivoSearchBoxId + " div.column-outer").removeClass("overflow"); 291 if (numberOfRooms > 2) { 292 $(options.eviivoSearchBoxId + " div.column-outer").addClass("overflow"); 293 } 294 } 295 296 ///<summary>Hide tooltip when clicking outside the tooltip area</summary> 297 function tooltipOutsideClick(e) { 298 if (!$(e.target).hasClass("ui-menu-item-wrapper") && !$(e.target).hasClass("ui-menu-item") && !$(e.target).hasClass("cp-tooltip") && $(e.target).parents("div.cp-tooltip").length === 0) { 299 tooltipHide(e, true); 300 } 301 } 302 303 ///<summary>Hide tooltip handler on escape key</summary> 304 function tooltipHideEscape(e) { 305 if (e.keyCode === 27) { 306 tooltipHide(e, true); 307 } 308 } 309 310 ///<summary>Hide tooltip handler</summary> 311 function tooltipHide(e, bypassFocus) { 312 if ($tooltip.is(":visible")) { 313 $tooltip.hide(); 314 315 //we check if we bypass focus for search button 316 if (!bypassFocus) { 317 $searchButton.focus(); 318 } 319 } 320 } 321 322 ///<summary>Show tooltip handler</summary> 323 function tooltipShow(e) { 324 if (e !== null) { 325 e.stopPropagation(); 326 } 327 if (($tooltip).is(":hidden")) { 328 $tooltip.show('fast'); 329 //when showing tooltip we focus on first select input element 330 $firstAdultsOccupancy.focus(); 331 } 332 } 333 334 function hideInfoToolTip() { 335 //hide info tooltip if visible 336 if ($infoTooltip.is(":visible")) { 337 $infoTooltip.hide(); 338 } 339 } 340 341 ///<summary>Performs a validation on all the fields necessary to make the autocomplete / location result valid</summary> 342 function validate(isOnLoadValidation) { 343 // if both dates fields are empty 344 if ($startDate.val() === "" && $endDate.val() === "") { 345 if (isOnLoadValidation) { 346 $datePickerSelectionToolTip.addClass("infotip"); 347 } else { 348 $startDate.addClass("invalid"); 349 $endDate.addClass("invalid"); 350 } 351 $datePickerSelectionToolTip.show(); 352 return false; 353 } 354 // if start date field is empty 355 if ($startDate.val() === "") { 356 if (isOnLoadValidation) { 357 $datePickerSelectionToolTip.addClass("infotip"); 358 } else { 359 $startDate.addClass("invalid"); 360 } 361 $datePickerSelectionToolTip.show(); 362 return false; 363 } 364 // if end date field is empty 365 if ($endDate.val() === "") { 366 if (isOnLoadValidation) { 367 $datePickerSelectionToolTip.addClass("infotip"); 368 } else { 369 $endDate.addClass("invalid"); 370 } 371 $datePickerSelectionToolTip.show(); 372 return false; 373 } 374 clearDatePickerTooltip(); 375 return true; 376 377 } 378 379 380 function clearDatePickerTooltip() { 381 $startDate.removeClass("invalid"); 382 $endDate.removeClass("invalid"); 383 $datePickerSelectionToolTip.removeClass("infotip"); 384 $datePickerSelectionToolTip.hide(); 385 } 386 387 388 ///<summary>Extract search criteria from querystring and populate search form</summary> 389 function extractCriteria() { 390 var criteria = options.criteria; 391 var src = window.location.href;
392 var isRoomCount = false; 393 394 //extract all query string keys by using reg exp 395 var qsJson = querystringHelper.getAllKeys(src); 396 397 //set start and end dates 398 if (qsJson["startdate"] !== null) { 399 criteria.startDate = new Date(qsJson["startdate"]); 400 } 401 if (qsJson["enddate"] !== null) { 402 criteria.endDate = new Date(qsJson["enddate"]); 403 } 404 405 //in case we had number of nights from legacy URL formats 406 if (qsJson["nights"] !== null) { 407 var nights = parseInt(qsJson["nights"]); 408 //in case we had number of nights from legacy URL formats 409 if (!isNaN(nights)) { 410 //we have to re-construct the date object due to fact it will change the original is we only assign 411 criteria.endDate = new Date(criteria.startDate.getFullYear(), criteria.startDate.getMonth(), criteria.startDate.getDate(), 0, 0, 0); 412 criteria.endDate.addHours(24 * nights); 413 } 414 } 415 416 //make sure to persist the display format 417 if (qsJson["simpleversion"] !== null) { 418 simpleVersion = qsJson["simpleversion"]; 419 } 420 421 if (qsJson["noofrooms"] !== null) { 422 isRoomCount = qsJson["noofrooms"] >= 0; 423 } 424 425 criteria.occupancy = []; 426 427 //pass through all query string keys and construct the occupancy selectors 428 $.each(qsJson, function (key, value) { 429 //check for existance of adults and/or children params 430 var isAdults = key.indexOf("adults") >= 0; 431 var isChildren = key.indexOf("children") >= 0; 432 433 if (isAdults || isChildren > 0) { 434 //check to see which is the occupancy group the query string key refers to 435 var occupancyRegEx = new RegExp("[a-zA-Z]+([0-9]+)"); 436 // extract the "index" of the adults or children (eg. adults1) - we need to consider them as being 0 base even though they come as 1 based in the URL 437 var index = parseInt(occupancyRegEx.exec(key)[1]) - 1; 438 439 // if room count is used exit loop if the "index" of the adults or children is greater than the noofrooms query string value 440 if (isRoomCount && (index + 1) > parseInt(qsJson["noofrooms"])) 441 return false; 442 443 //construct extra occupancy groups (in case it's needed) 444 while (index > options.criteria.occupancy.length - 1) { 445 criteria.occupancy.push({ adults: 0, children: 0 }); 446 } 447 448 //based on key, set the appropriate field for occupancy 449 if (isAdults) { 450 criteria.occupancy[index].adults = value; 451 452 } else if (isChildren) { 453 criteria.occupancy[index].children = value; 454 } 455 } 456 }); 457 458 return criteria; 459 } 460 461 ///<summary>Construct the search URL base on the base URL and the selected parameters</summary> 462 function triggerSearch() { 463 464 if (!validate()) { 465 return; 466 } 467 468 var searchUrl = options.baseSearchUrl; 469 var src = window.location.href; 470 var originalQueryString = querystringHelper.getAllKeys(src); 471 var queryStrings = jQuery.extend({}, originalQueryString); 472 473 var startDate = $startDate.datepicker("getDate"); 474 var endDate = $endDate.datepicker("getDate"); 475 476 if (startDate !== null) { 477 querystringHelper.addKey(queryStrings, "startdate", startDate.evStandardIso8601DateExtended()); 478 } 479 if (endDate !== null) { 480 querystringHelper.addKey(queryStrings, "enddate", endDate.evStandardIso8601DateExtended()); 481 } 482 483 querystringHelper.deleteKeyWithRegex(queryStrings, "adults"); 484 querystringHelper.deleteKeyWithRegex(queryStrings, "children"); 485 querystringHelper.deleteKeyWithRegex(queryStrings, "noofrooms"); 486 $(options.eviivoSearchBoxId + " div.column-inner").each(function (i, e) { 487 querystringHelper.addKey(queryStrings, "adults" + (i + 1), extractNumber($(e).find("span.column-adult .ui-selectmenu-text"))); 488 querystringHelper.addKey(queryStrings, "children" + (i + 1), extractNumber($(e).find("span.column-child .ui-selectmenu-text"))); 489 }); 490 491 if (simpleVersion !== null && simpleVersion !== "" && simpleVersion !== undefined) { 492 querystringHelper.addKey(queryStrings, "simpleversion", simpleVersion); 493 } 494 495 //This is necessary in order to cater for situations where the booking pages are being served via the SearchSystem 496 if ($("#ref").length > 0 && $("#ref").val().length > 0) { 497 querystringHelper.addKey(queryStrings, "ref", $("#ref").val()); 498 } 499 500 handlePromotionsRequestParameters(originalQueryString, queryStrings); 501 502 querystringHelper.addRandomizer(queryStrings); 503 504 searchUrl += querystringHelper.toString(queryStrings); 505 506 //reload browser to the search url 507 window.location.href = searchUrl; 508 } 509 510 function handlePromotionsRequestParameters(originalQueryString, queryStrings) { 511 //clear the promo code parameter 512 querystringHelper.deleteKey(queryStrings, "pce"); 513 //see if promo code has to be submited fresh 514 var promotionCodeElement = $("#pce"); 515 if (promotionCodeElement.length > 0) { 516 querystringHelper.addKey(queryStrings, "pce", promotionCodeElement.val()); 517 } 518 519 //clear the promo instant deal parameter 520 querystringHelper.deleteKey(queryStrings, "peid"); 521 //see if promo instant deal has to be submited fresh 522 var promotionEnableDealsElement = $("#peid"); 523 if (promotionEnableDealsElement.length > 0 && promotionEnableDealsElement.val() != null && promotionEnableDealsElement.val() !== "") { 524 querystringHelper.addKey(queryStrings, "peid", promotionEnableDealsElement.val()); 525 } 526 } 527 528 ///<summary>
528Construct the search URL base on the base URL and the selected parameters</summary> 529 function showStartDateCalendar(e) { 530 if (!$viewportWidth < 768) { 531 //smaller than ipad 532 eviivo.smoothScrolling.smoothScrollHandler(e, function () { 533 $startDate.datepicker("show"); 534 }, this); 535 } 536 } 537 538 ///<summary>initialize the serach form based on criteria options we have (either from query string, from property setup or from our own defaults)</summary> 539 function initSearchForm() { 540 541 //pass through all occupancy groups 542 $.each(options.criteria.occupancy, function (index, value) { 543 //construct extra occupancy group selectors in case it's needed 544 while (index > 0 && index > $occupancySelectors.length - 1) { 545 addRoomClickHandler(); 546 } 547 $($occupancySelectors[index].adults).val(value.adults); 548 $($occupancySelectors[index].adults).selectmenu("refresh"); 549 $($occupancySelectors[index].children).val(value.children); 550 $($occupancySelectors[index].children).selectmenu("refresh"); 551 }); 552 553 occupancySelectHandler(); 554 } 555 556 ///<summary>Datepicker select handler for start date</summary> 557 function startDateSelectHandler(date, instance) { 558 //grab selected date and add extra 24 hours 559 var selectedDate = new Date(instance.selectedYear, instance.selectedMonth, parseInt(instance.selectedDay), 0, 0, 0, 0); 560 var minEndDate = new Date(selectedDate).addHours(24); 561 562 //set the minDate for the endDate calendar 563 $endDate.datepicker("option", { "minDate": minEndDate }); 564 565 //auto show the end date calendar (delay of 100ms because of jquery show effects on datepicker) 566 setTimeout(function () { 567 $endDate.datepicker("show"); 568 $endDate.datepicker("setDate", minEndDate); 569 }, 100); 570 571 // If yellow tooltip exists & it's visible 572 if ($datePickerSelectionToolTip.length > 0 && $datePickerSelectionToolTip.is(':visible')) { 573 clearDatePickerTooltip(); 574 } 575 } 576 577 ///<summary>Datepicker select handler for end date</summary> 578 function endDateSelectHandler(date, instance) { 579 if ($startDate.val() === "") { 580 //auto show the end date calendar (delay of 100ms because of jquery show effects on datepicker) 581 setTimeout(function () { 582 $startDate.datepicker("show"); 583 }, 100); 584 } else { 585 //focus on done button 586 $firstAdultsOccupancy.focus(); 587 } 588 // If yellow tooltip exists & it's visible 589 if ($datePickerSelectionToolTip.length > 0 && $datePickerSelectionToolTip.is(':visible')) { 590 clearDatePickerTooltip(); 591 } 592 593 } 594 595 ///<summary>helper function to extract number from occupancy selector text (adult or child)</summary> 596 function extractNumber(selector) { 597 return parseInt(selector.text().slice(0, 2).trim()); 598 } 599 600 ///<summary>extend Date javascript object and add extra function "addHours"</summary> 601 Date.prototype.addHours = function (hours) { 602 this.setHours(this.getHours() + hours); 603 return this; 604 }; 605 606 ///<summary>Expose the internal "private" methods for external use (make them public)</summary> 607 return { 608 init: init, 609 triggerSearch: triggerSearch 610 }; 611}(jQuery);
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.