1(function ($, settings) { 2 $.fn.homelocalRangeCalendar = function (options) { 3 const app = this; 4 const $container = $(app); 5 6 options = $.extend({}, options); 7 options.min_stay = parseInt(options.min_stay, 10) || 1; 8 options.max_stay = parseInt(options.max_stay, 10) || 365; 9 options.setDates = options.setDates || function (checkin, checkout) {}; 10 options.max_num_days = parseInt(options.max_num_days, 10); 11 settings.start_of_week = parseInt(settings.start_of_week, 10) || 0; 12 13 let calendarSlider = null; 14 let moveCalendarToDate = null; 15 16 const calendarUtils = new HomeLocalCalendarUtils( 17 homerunner_calendar_utils_settings, 18 { 19 min_stay: options.min_stay, 20 max_stay: options.max_stay, 21 max_num_days: options.max_num_days, 22 timezone: settings.timezone, 23 }, 24 ); 25 26 const selector = { 27 main: ".homelocal-range-calendar", 28 }; 29 30 let cellHeight = null; 31 32 const debounce = (func, timeout = 300) => { 33 let timer; 34 return (...args) => { 35 clearTimeout(timer); 36 timer = setTimeout(() => { 37 func.apply(this, args); 38 }, timeout); 39 }; 40 }; 41 42 app.isBooked = (day) => { 43 const opts = calendarUtils.dateOptions(day); 44 return opts && opts.booked; 45 }; 46 47 app.isUnavailable = (day) => { 48 const date = calendarUtils.getDateZero(day); 49 const today = calendarUtils.getDateZero(); 50 51 // check if date is in the past, today or too far in the future. 52 if (date < today) { 53 return true; 54 } 55 56 if (options.max_num_days !== -1) { 57 const maxDate = luxon.DateTime.now().setZone(settings.timezone).plus({ 58 days: options.max_num_days, 59 }); 60 61 if (date > maxDate) { 62 return true; 63 } 64 } 65 66 return false; 67 }; 68 69 app.canCheckin = (day) => { 70 const givenDay = calendarUtils.getDateZero(day); 71 72 const lastCheckin = calendarUtils.maxDate.plus({ 73 days: -options.min_stay, 74 }); 75 76 return givenDay >= calendarUtils.minDate && givenDay <= lastCheckin; 77 }; 78 79 app.canCheckout = (day) => { 80 const givenDay = calendarUtils.getDateZero(day); 81 82 const firstCheckout = givenDay.plus({ 83 days: options.min_stay, 84 }); 85 86 return givenDay >= firstCheckout && givenDay <= calendarUtils.maxDate; 87 }; 88 89 // check if checkout date is valid. 90 // if checkin date is provided, check if min stay is met 91 app.canBook = (checkin, checkout) => { 92 const today = calendarUtils.getDateZero(); 93 const checkinDate = calendarUtils.getDateZero(checkin); 94 const checkoutDate = calendarUtils.getDateZero(checkout); 95 96 if (checkoutDate < today || checkoutDate < checkinDate) { 97 return false; 98 } 99 100 // when checkin date is provided, check if min stay is met 101 const nights = calendarUtils.calcNights(checkin, checkout); 102 103 if (nights < options.min_stay || nights > options.max_stay) { 104 return false; 105 } 106 107 return true; 108 }; 109 110 app.updateCellSize = () => { 111 if ($container.find(".calendar:visible .calendar-table").length > 0) { 112 const width = $container 113 .find(".calendar:visible .calendar-table") 114 .width(); 115 116 cellHeight = width / 7; 117 $container.find(".calendar .calendar-table").each(function (i, table) { 118 $(table) 119 .find(".day") 120 .css({ 121 height: cellHeight + "px", 122 lineHeight: cellHeight + "px", 123 }); 124 125 $(table) 126 .find(".day .date") 127 .css({ 128 height: cellHeight - 4 + "px", 129 width: cellHeight - 4 + "px", 130 lineHeight: cellHeight - 4 + "px", 131 }); 132 }); 133 } 134 }; 135 136 app.isBelowMax = ({ year, month }) => { 137 return ( 138 year * 12 + month < 139 calendarUtils.maxDate.year * 12 + calendarUtils.maxDate.month + 1 140 ); 141 }; 142 143 app.isAboveMin = ({ year, month }) => { 144 return ( 145 year * 12 + month > 146 calendarUtils.minDate.year * 12 + calendarUtils.minDate.month - 1 147 ); 148 }; 149 150 app.addCalendar = ({ year, month }, show = false) => { 151 if ( 152 $container.find( 153 '.calendar[data-y="' + year + '"][data-m="' + month + '"]', 154 ).length < 1 155 ) { 156 const $el = $("<div/>", { 157 class: "calendar", 158 "data-y": year, 159 "data-m": month, 160 }).html(app.getCalendarHtml({ year, month })); 161 162 $el.appendTo($container.find(".calendars")); 163 164 setTimeout(app.updateCellSize, 1); 165 166 if (!show) { 167 $container 168 .find('.calendar[data-y="' + year + '"][data-m="' + month + '"]') 169 .hide(); 170 } 171 } 172 }; 173 174 app.getCalendarHtml = ({ year, month }) => { 175 const zeroise = (input) => { 176 return input < 10 ? "0" + input : input; 177 }; 178 179 let html = 180 '<div class="calendar-heading">' + 181 settings.months_label[month - 1] + 182 " " + 183 year + 184 "</div>"; 185 186 // Calendar content. Disable datepicker day name / number translation. 187 html += '<div class="calendar-table">'; 188 189 // Week days. 190 html += '<div class="week-days">'; 191 for (var i = 0; i <= 6; i++) { 192 const weekIndex = (i + settings.start_of_week) % 7; 193 194 html += '<div class="day-name">'; 195 html += settings.weekdays_label[weekIndex]; 196 html += "</div>"; 197 } 198 html += "</div>"; 199 200 var w = 0; // week day 201 var n = 1; // next days date 202 var c = 1; // current date 203 204 let dayOfWeek = calendarUtils.getFirstWeekday(year, month); 205 206 if (settings.start_of_week) { 207 dayOfWeek = dayOfWeek - settings.start_of_week; 208 if (dayOfWeek < 0) { 209 dayOfWeek = 7 + dayOfWeek; 210 } 211 } 212 213 dayOfWeek = dayOfWeek % 7; 214 215 const daysInMonth = calendarUtils.getMonthDayCount(year, month); 216 217 // dates loop 218 for (var i = 0; i < 6 * 7; i++) { 219 if (w == 0) { 220 // first week's day 221 html += '<div class="week">'; 222 } 223 224 if (i < dayOfWeek) { 225 // previous month's day compared to active month. 226 html += '<div class="day day-placeholder"></div>'; 227 } else if (c > daysInMonth) { 228 // next month's day compared to active month. 229 html += '<div class="day day-placeholder"></div>'; 230 n++; 231 } else { 232 const date = `${year}-${zeroise(month)}-${zeroise(c)}`; 233 234 let classnames = ["day", "day-of-month"]; 235 236 if (calendarUtils.isToday(date)) { 237 classnames.push("today"); 238 } 239 240 if (app.isUnavailable(date)) { 241 classnames.push("unavailable"); 242 } 243 244 // unique class names 245 classnames = classnames.filter( 246 (value, index, self) => self.indexOf(value) === index, 247 ); 248 249 let style = ""; 250 let dateStyle = ""; 251 if (cellHeight) { 252 style = ` height: ${cellHeight}px; line-height: ${cellHeight}px;`; 253 dateStyle = ` style="height: ${cellHeight - 4}px; width: ${ 254 cellHeight - 4 255 }px; line-height: ${cellHeight - 4}px;"`; 256 } 257 258 // current month's day 259 html += `<div class="${classnames.join( 260 " ", 261 )}" data-date="${date}" style="${style}"> 262 <span class="date" style="${dateStyle}">${c}</span> 263 </div>`; 264 c++; 265 } 266 267 if (w == 7 - 1) { 268 // last week's day 269 html += "</div>"; 270 w = 0; 271 } else { 272 w++; 273 } 274 } 275 276 html += "</div>"; 277 return html; 278 }; 279 280 app.isMobileContainer = () => { 281 // return $container.parent().width() < 768; 282 return $(window).width() < 768; 283 }; 284 285 app.pickerInit = () => { 286 let startDay = false;
287 let endDay = false; 288 const dayCellSelector = selector.main + " .week .day-of-month"; 289 const $dayCells = $container.find(dayCellSelector); 290 let isLoading = false; 291 292 $container.addClass("calendar-picker"); 293 294 if ($("#homelocal-calendar-tip").length < 1) { 295 $("body") 296 .append(`<div id="homelocal-calendar-tip" class="homelocal-tip"> 297 <div class="tip-content"></div> 298 <div class="tip-arrow"></div> 299 </div>`); 300 } 301 302 let tipTimeout = null; 303 304 const showTip = ($day, tip) => { 305 $("#homelocal-calendar-tip .tip-content").html(tip); 306 307 // calculate placement based on element position in window 308 const placement = 309 $day.offset().top + $day.height() / 2 - $(window).scrollTop() < 310 $(window).height() / 3 311 ? "bottom" 312 : "top"; 313 314 $("#homelocal-calendar-tip").attr("data-placement", placement); 315 316 if (placement === "top" || placement === "bottom") { 317 $("#homelocal-calendar-tip").css({ 318 left: 319 $day.offset().left + 320 $day.width() / 2 - 321 $("#homelocal-calendar-tip").width() / 2, 322 }); 323 324 if (placement === "top") { 325 $("#homelocal-calendar-tip").css({ 326 top: 327 $day.offset().top - $("#homelocal-calendar-tip").height() - 10, 328 }); 329 } else if (placement === "bottom") { 330 $("#homelocal-calendar-tip").css({ 331 top: $day.offset().top + $day.height() + 10, 332 }); 333 } 334 } 335 336 $("#homelocal-calendar-tip").show(); 337 338 clearTimeout(tipTimeout); 339 tipTimeout = setTimeout(() => { 340 removeTip(); 341 }, 5000); 342 }; 343 344 const removeTip = () => { 345 $("#homelocal-calendar-tip").hide(); 346 clearTimeout(tipTimeout); 347 }; 348 349 const maybeHideTip = (e) => { 350 removeTip(); 351 }; 352 353 $(document).on("click", maybeHideTip); 354 355 const isSelectStart = () => 356 (startDay && endDay) || (!startDay && !endDay); 357 358 const getSelectableRange = (day) => { 359 return { 360 minDate: day, 361 maxDate: calendarUtils 362 .getDateZero(day) 363 .plus({ days: options.max_stay }) 364 .toFormat("yyyy-MM-dd"), 365 }; 366 }; 367 368 const getAvailableCheckoutDays = (day) => { 369 let firstDate = calendarUtils 370 .getDateZero(day) 371 .plus({ days: options.min_stay }); 372 373 const lastDate = calendarUtils 374 .getDateZero(day) 375 .plus({ days: options.max_stay }); 376 377 const days = []; 378 while (firstDate < lastDate) { 379 firstDate = firstDate.plus({ days: 1 }); 380 days.push(firstDate.toFormat("yyyy-MM-dd")); 381 } 382 383 return days; 384 }; 385 386 const makeSelection = (startedDay, endedDay) => { 387 $dayCells.each(function () { 388 const dataDate = $(this).attr("data-date"); 389 if (dataDate === startedDay) { 390 $(this).addClass("selected checkin").removeClass("disabled hover"); 391 } else if (dataDate === endedDay) { 392 $(this).addClass("selected checkout").removeClass("disabled hover"); 393 } else if (dayInRange(dataDate, startedDay, endedDay)) { 394 $(this).addClass("selected").removeClass("disabled hover"); 395 } else { 396 $(this).removeClass("selected checkin checkout hover"); 397 } 398 }); 399 400 $container 401 .addClass("checkout-selected") 402 .removeClass("checkin-selected"); 403 }; 404 405 const setCheckinDay = (e, day, reset = false) => { 406 removeTip(); 407 if (day !== startDay) { 408 startDay = day; 409 if (reset) { 410 $(document).trigger("homelocal_booking_widget_reset"); 411 } 412 } 413 414 $container 415 .addClass("checkin-selected") 416 .removeClass("checkout-selected"); 417 418 endDay = false; 419 $dayCells.removeClass("hover selected checkin checkout disabled"); 420 $dayCells 421 .filter(`[data-date="${day}"]`) 422 .addClass("selected checkin checkout") 423 .removeClass("disabled"); 424 425 const selectableDays = getAvailableCheckoutDays(startDay); 426 const selectableRange = getSelectableRange(startDay); 427 428 $dayCells.each(function () { 429 const dataDate = $(this).attr("data-date"); 430 if ( 431 dataDate !== startDay && 432 !$(this).hasClass("unavailable booked") 433 ) { 434 if ( 435 !selectableDays.includes(dataDate) && 436 !dayInRange( 437 dataDate, 438 selectableRange.minDate, 439 selectableRange.maxDate, 440 ) 441 ) { 442 $(this).addClass("disabled"); 443 } 444 } 445 }); 446 $(document).on("mouseover", dayCellSelector, onDayHover); 447 }; 448 449 const setCheckoutDay = (e, day) => { 450 removeTip(); 451 endDay = day; 452 453 makeSelection(startDay, endDay); 454 455 $dayCells.removeClass("disabled"); 456 457 $(document).off("mouseover", dayCellSelector, onDayHover); 458 }; 459 460 const dayClicked = (e) => { 461 const $day = $(e.currentTarget); 462 const dataDate = $day.attr("data-date"); 463 464 if (isLoading) { 465 showTip($day, settings.selection.range_loading); 466 } else if (app.isUnavailable(dataDate)) { 467 showTip($day, settings.selection.unavailable); 468 } else if (isSelectStart()) { 469 if (app.isBooked(dataDate) && !app.canCheckin(dataDate)) { 470 showTip($day, settings.selection.booked); 471 } else if (app.canCheckin(dataDate)) { 472 $(document).trigger(
473 "homelocal_range_calendar_set_checkin_" + options.id, 474 [dataDate, true], 475 ); 476 } else { 477 showTip( 478 $day, 479 settings.selection.cant_checkin.replace( 480 "{night}", 481 calendarUtils.getMinstay(dataDate), 482 ), 483 ); 484 } 485 } else { 486 const minmaxDate = getSelectableRange(startDay); 487 488 if (app.isBooked(dataDate) && !app.canCheckout(dataDate)) { 489 showTip($day, settings.selection.booked); 490 } else if (app.canBook(startDay, dataDate)) { 491 $(document).trigger( 492 "homelocal_range_calendar_set_checkout_" + options.id, 493 [dataDate], 494 ); 495 $(document).trigger( 496 "homelocal_range_calendar_set_dates_" + options.id, 497 [startDay, dataDate], 498 ); 499 500 options.setDates(startDay, dataDate); 501 } else if ( 502 dayInRange(dataDate, minmaxDate.minDate, minmaxDate.maxDate) 503 ) { 504 showTip( 505 $day, 506 settings.selection.min_night.replace( 507 "{night}", 508 calendarUtils.getMinstay(startDay), 509 ), 510 ); 511 } else { 512 showTip($day, settings.selection.unavailable); 513 } 514 } 515 516 return false; 517 }; 518 519 const onDayHover = (e) => { 520 const $day = $(e.currentTarget); 521 const hoverDate = $day.attr("data-date"); 522 const minmaxDate = getSelectableRange(startDay); 523 524 if (dayInRange(hoverDate, minmaxDate.minDate, minmaxDate.maxDate)) { 525 $dayCells.each(function () { 526 const dataDate = $(this).attr("data-date"); 527 if ( 528 dayInRange(dataDate, startDay, hoverDate) && 529 dayInRange(dataDate, minmaxDate.minDate, minmaxDate.maxDate) && 530 dataDate !== startDay 531 ) { 532 $(this).addClass("hover"); 533 if (dataDate === hoverDate) { 534 $(this).addClass("checkout"); 535 } else { 536 $(this).removeClass("checkout"); 537 } 538 } else { 539 $(this).removeClass("hover checkout"); 540 } 541 }); 542 } 543 }; 544 545 const dayInRange = (day, minDay, maxDay) => { 546 return ( 547 new Date(day) >= new Date(minDay) && new Date(day) <= new Date(maxDay) 548 ); 549 }; 550 551 const resetSelection = () => { 552 $container.removeClass("checkin-selected checkout-selected"); 553 $dayCells.removeClass( 554 "hover selectable selected disabled checkin checkout", 555 ); 556 startDay = false; 557 endDay = false; 558 559 $(document).off("mouseover", dayCellSelector, onDayHover); 560 }; 561 562 $container.on("click", dayCellSelector, dayClicked); 563 564 if (options.start_date && options.end_date) { 565 startDay = options.start_date; 566 endDay = options.end_date; 567 makeSelection(startDay, endDay); 568 moveCalendarToDate = startDay; 569 $dayCells.removeClass("disabled"); 570 } 571 572 // New id based event listener. 573 $(document).on( 574 "homelocal_range_calendar_reset_selection_" + options.id, 575 () => { 576 resetSelection(); 577 moveCalendarToDate = calendarUtils 578 .getDateZero() 579 .toFormat("yyyy-MM-dd"); 580 }, 581 ); 582 583 $(document).on( 584 "homelocal_range_calendar_set_checkin_" + options.id, 585 setCheckinDay, 586 ); 587 588 $(document).on( 589 "homelocal_range_calendar_set_checkout_" + options.id, 590 (e, day) => { 591 setCheckoutDay(e, day); 592 moveCalendarToDate = startDay; 593 }, 594 ); 595 }; 596 597 app.createLayout = () => { 598 $container.append( 599 `<div class="hk-light notranslate homelocal-range-calendar"> 600 <div class="calendars"></div> 601 <button class="next-btn" type="button">${settings.next_icon_svg}</button> 602 <button class="prev-btn" type="button">${settings.prev_icon_svg}</button> 603 </div>`, 604 ); 605 }; 606 607 app.renderCalendars = () => { 608 const startMonth = calendarUtils.minDate.startOf("month"); 609 const endMonth = calendarUtils.maxDate.startOf("month"); 610 611 const monthDiffCeiled = Math.ceil( 612 endMonth.diff(startMonth, "months").months, 613 ); 614 615 // Ensure at least 2 months are rendered. 616 const monthsToRender = Math.max(monthDiffCeiled + 1, 2); 617 618 for (let i = 0; i < monthsToRender; i++) { 619 const currentMonth = startMonth.plus({ months: i }); 620 app.addCalendar( 621 { 622 year: currentMonth.year, 623 month: currentMonth.month, 624 }, 625 true, 626 ); 627 } 628 }; 629
630 app.adjustLayout = () => { 631 if ($container.is(":hidden")) { 632 return false; 633 } 634 635 app.updateCellSize(); 636 }; 637 638 app.calendarSliderInit = () => { 639 if (options.modal) { 640 const viewportWidth = $(window).width(); 641 if (viewportWidth < 768) { 642 if (calendarSlider) { 643 calendarSlider.destroy(); 644 calendarSlider = null; 645 } 646 647 $container.find(".prev-btn,.next-btn").addClass("hk-hidden"); 648 return; 649 } 650 } 651 652 if (calendarSlider) { 653 return; 654 } 655 656 $container.find(".prev-btn,.next-btn").removeClass("hk-hidden"); 657 658 calendarSlider = tns({ 659 container: `${options.selector} .calendars`, 660 items: 1, 661 gutter: 0, 662 responsive: { 663 768: { 664 items: 2, 665 gutter: 30, 666 }, 667 }, 668 slideBy: 1, 669 speed: 600, 670 autoplay: false, 671 mouseDrag: true, 672 // preventActionWhenRunning: true, 673 nav: false, 674 loop: false, 675 nextButton: `${options.selector} .next-btn`, 676 prevButton: `${options.selector} .prev-btn`, 677 }); 678 }; 679 680 app.init = () => { 681 app.createLayout(); 682 app.renderCalendars(); 683 app.calendarSliderInit();
684 app.adjustLayout(); 685 686 app.pickerInit(); 687 688 $(window).on("resize", debounce(app.adjustLayout, 300)); 689 690 if (options.modal) { 691 $(document).on("beforeshow", options.modal, app.moveCalendarSlider); 692 $(document).on("show", options.modal, app.adjustLayout); 693 $(document).on("shown", options.modal, app.moveCalendarScroll); 694 } 695 }; 696 697 app.moveCalendarScroll = () => { 698 if (!moveCalendarToDate || !app.isMobileContainer()) { 699 return; 700 } 701 702 const date = calendarUtils.getDateZero(moveCalendarToDate); 703 704 const $el = $container.find( 705 `.calendar[data-y="${date.year}"][data-m="${date.month}"]`, 706 ); 707 708 if ($el.length > 0) { 709 const scrollTop = $el.offset().top - 50; 710 711 if (scrollTop > 0) { 712 $(options.modal).find(".hk-modal-body").scrollTop(scrollTop); 713 } 714 } 715 716 // reset moveCalendarToDate 717 moveCalendarToDate = null; 718 }; 719 720 app.moveCalendarSlider = () => { 721 if (!moveCalendarToDate || app.isMobileContainer()) { 722 return; 723 } 724 725 const date = calendarUtils.getDateZero(moveCalendarToDate); 726 727 const $el = $container.find( 728 `.calendar[data-y="${date.year}"][data-m="${date.month}"]`, 729 ); 730 731 if ($el.length > 0 && !$el.hasClass("tns-slide-active")) { 732 const index = $el.index(); 733 calendarSlider.goTo(index); 734 } 735 736 moveCalendarToDate = null; 737 }; 738 739 app.init(); 740 741 return app; 742 }; 743})(jQuery, homelocal_range_calendar_settings);
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.