PageSourceSearch

https://rutlandhall.co.uk/wp-content/plugins/cn-onejourney/modules…/js/availability-rates.js?ver=0.1

js rutlandhall.co.uk collected 2026-09-26 09:28:34 UTC 13,899 bytes, 456 lines download raw bytes

1var OJ = OJ || {};
2
3OJ.AvailabilityRates = function(id) {
4    this.datepickerConfig = {
5        mode: 'range',
6        inline: true,
7        showMonths: 1,
8        monthSelectorType: 'static',
9        disableMobile: false,
10        minDate: this.getToday()
11    }
12
13    this.connector = new OJ.DataConnector(id)
14    this.displayPrices = true
15    this.displayAvailability = true
16    this.dates = null
17    this.availability = {}
18    this.prePopulate = false
19
20    this.rangeActive = false
21    this.minStay = null
22    this.minStayThreshold = 1
23
24    this.dateSelectionError = false
25}
26
27OJ.AvailabilityRates.prototype.makeDatepicker = function(datepickerElement, datepickerConfig = this.datepickerConfig, tagline = false) {
28
29    // store calendar for use across AvailabilityRates class
30    this.calendar = datepickerElement
31    // assign availablityRates to the calendar for on-the-fly access from elsewhere in the codebase
32    this.calendar.availabilityRates = this
33
34    this.container = this.calendar.closest('form')
35    this.monthRange = datepickerConfig.showMonths || 1
36
37    this.datepickerConfig = this.extendDatepicker(datepickerConfig)
38
39    this.datepicker = flatpickr(datepickerElement, this.datepickerConfig)
40
41    this.dates = this.getMonthRange(datepickerConfig.minDate)
42
43    $(this.container).toggleClass('has-pricing-data', this.displayPrices)
44
45    this.addMonthData()
46    this.populateAvailability(true)
47
48    if(tagline) this.addTagline()
49}
50
51OJ.AvailabilityRates.prototype.populateAvailability = function(reset = false) {
52
53    if(reset && Object.keys(this.availability).length > 0) {
54        this.availability = {}
55        this.datepicker.setDate([])
56        this.updateDatepicker()
57    } else {
58        let newMonth = this.formatDate(new Date(this.datepicker.currentYear, this.datepicker.currentMonth + (this.monthRange - 1)))
59        if(this.availability[newMonth]) return // we already have data for this month
60    }
61
62    this.connector.getAvailability(this.dates, this.rateId, this.roomId, function(availability){
63        this.setAvailability(availability)
64
65        if(!availability || availability.message) return false
66
67        if(reset && this.prePopulate) {
68            this.datepicker.setDate(this.getNextAvailableDates(), true)
69        }
70
71        this.updateDatepicker()
72    }.bind(this))
73}
74
75OJ.AvailabilityRates.prototype.setAvailability = function(availability) {
76    if(!availability) return false
77
78    let message = availability.message
79
80    if(message) {
81        this.displayMessage('\033[31m API Error: ' + message, true)
82    } else {
83        this.availability = Object.assign(this.availability, availability)
84    }
85}
86
87OJ.AvailabilityRates.prototype.extendDatepicker = function(config) {
88    let methods = {
89        onDayCreate: (dateObj, dateStr, fp, dayElem) => this.updateDay(dayElem),
90        onMonthChange: (selectedDates, dateStr, instance) => this.changeMonth(),
91        onValueUpdate: (dateObj, dateStr, fp, dayElem) => this.updateValue(dateStr, dateObj),
92    }
93
94    Object.keys(methods).forEach((method) =>{
95        if(config[method]) {
96            config[method].push(methods[method])
97        } else {
98            config[method] = methods[method]
99        }
100    })
101
102    return {
103        ...config,
104        ...{
105            showMonths: this.monthRange,
106            minDate: config.minDate ? config.minDate : this.datepickerConfig.minDate
107        }
108    }
109
110}
111
112OJ.AvailabilityRates.prototype.updateDatepicker = function() {
113    this.addMonthData()
114
115    $(this.datepicker.daysContainer).find('.flatpickr-day').each((i, el) => {
116        this.updateDay(el)
117    })
118}
119
120OJ.AvailabilityRates.prototype.changeMonth = function() {
121    let month = this.datepicker.currentMonth
122    let year = this.datepicker.currentYear
123
124    this.dates = this.getMonthRange(new Date(year, month))
125
126    this.addMonthData()
127
128    this.populateAvailability()
129}
130
131OJ.AvailabilityRates.prototype.formatDate = function(date) {
132    return flatpickr.formatDate(date, 'Y-m-d')
133}
134
135OJ.AvailabilityRates.prototype.updateDay = function(dayElem) {
136    if(this.dateSelectionError) return
137
138    let dayData = this.displayAvailability ? this.getDayData(dayElem.dateObj) : false
139    let day = $(dayElem).find('.day').length ? $(dayElem).find('.day').text() : $(dayElem).text()
140    let price = dayData ? dayData.price : ''
141    let availability = dayData ? dayData.type : '' // 'available', 'unavailable', 'hotel_closed', 'check_in_closed'
142    let disabled = availability == 'unavailable' || availability == 'hotel_closed'
143
144    if(this.datepicker && this.datepickerConfig.mode == 'range' && this.displayAvailability) disabled = this.checkDisabledState(dayElem, dayData, disabled)
145
146    if($(dayElem).is('.startRange.selected') && disabled){
147        this.dateSelectionError = true
148        return
149    }
150
151    $(dayElem).html(`<span class="day">${day}</span>`)
152
153    if(this.displayPrices) {
154        $(dayElem).append(`<small class="price">${price}</small>`)
155    }
156
157    if(disabled && !$(dayElem).is('.hidden')) {
158        dayElem.className += ' flatpickr-disabled ' + availability
159    }
160}
161
162OJ.AvailabilityRates.prototype.checkDisabledState = function(dayElem, dayData, disabledState) {
163    let minStay = this.minStay || (dayData?.minimum_stay || 1)
164
165    if(dayElem.dateObj < this.datepickerConfig.minDate) return true
166
167    if(this.datepicker.selectedDates.length == 1 && !this.rangeActive && !$(dayElem).is('.selected')) {
168
169        // disable all dates that precede the start date
170        dayElem.dataset.disabledreason = 'Out of range'
171        dayElem.className += ' outofrange'
172
173        return true
174    }
175
176    if(disabledState == true && this.rangeActive) {
177
178        // if subsequent date is unavailable, the range can go no further
179        this.rangeActive = false
180
181        return false // return enabled state to allow checkout on disabled dates
182    }
183
184    if(this.minStayThreshold > 1) {
185
186        this.minStayThreshold -= 1
187
188        // if subsequent date is below the minimum stay threshold, disable it
189        return true
190    }
191
192    if(minStay > 1 && this.datepicker.selectedDates.length != 1 && !$(dayElem).is('.selected,.inRange') && disabledState == false) {
193
194        let dateStr = this.formatDate(dayElem.dateObj)
195
196        // if there aren't enough nights between date and the next disabled date, these can't be selected
197        let nextDisabledDays = this.getAvailableRange(dateStr)
198
199        if(nextDisabledDays.length > 0 && nextDisabledDays[0] != dateStr) {
200            dayElem.dataset.disabledreason = `${minStay}-night minimum`;
201            return true
202        }
203
204    }
205
206    if($(dayElem).is('.selected.startRange') && this.datepicker.selectedDates.length == 1){
207
208        // set minimum stay threshold so we can disable subsequent dates accordingly
209        this.minStayThreshold = minStay
210        this.rangeActive = true
211
212    }
213
214    return disabledState
215}
216
217OJ.AvailabilityRates.prototype.getDayData = function(date, range = 1) {
218    return this.availability[(date instanceof Date) ? this.formatDate(date) : date]
219}
220
221OJ.AvailabilityRates.prototype.isAvailable = function(dayData, lastDay) {
222    return dayData.type != 'unavailable' && (dayData.type != 'hotel_closed' || lastDay)
223}
224
225OJ.AvailabilityRates.prototype.getToday = function() {
226    return new Date().fp_incr(0)
227}
228
229OJ.AvailabilityRates.prototype.getAvailableRange = function(dateStr) {
230    // gets first matching date and {range} subsequent dates
231
232    let dates = Object.keys(this.availability)
233    let i = 0
234    let nextavailable = []
235    let rangeStart = false
236
237    for(let key of dates) {
238
239        if(key == dateStr) rangeStart = true
240
241        if(rangeStart) {
242            let range = this.availability[key].minimum_stay
243
244            nextavailable = dates.slice(i, i + (range + 1)).filter((date, sliceIndex) => {
245                return this.isAvailable(this.availability[date], sliceIndex > 1 && sliceIndex == range)
246            })
247
248            if(nextavailable.length != (range + 1)) {
249                i++
250                continue
251            } else {
252                break
253            }
254        }
255
256        i++
257    }
258
259    return nextavailable
260
261}
262
263OJ.AvailabilityRates.prototype.removeDates = function(reselect) {
264    this.minStayThreshold = 1
265    this.rangeActive = false
266
267    let currentMonth = this.datepicker.currentMonth
268
269    if(this.dateSelectionError) {
270        this.dateSelectionError = false
271        reselect = true
272    }
273
274    this.datepicker.setDate(reselect ? this.getNextAvailableDates() : [], reselect)
275
276    if(!reselect) {
277        this.datepicker.changeMonth(currentMonth, false);
278    }
279
280    this.updateDatepicker()
281}
282
283OJ.AvailabilityRates.prototype.updateValue = function(dateStr, dateObj) {
284    this.checkDayForMessage(dateStr)
285    this.addMonthData()
286
287    if(dateObj[1] && dateStr == this.formatDate(dateObj[1]) || this.dateSelectionError){
288        this.removeDates()
289    } if(this.datepicker.selectedDates.length == 2 && this.datepickerConfig.mode == 'range') {
290        this.rangeActive = false
291    }
292}
293
294OJ.AvailabilityRates.prototype.getMonthRange = function(date) {
295    let month = date.getMonth()
296    let year = date.getFullYear()
297
298    return [
299        this.formatDate(new Date(year, month, 1) < new Date() ? new Date() : new Date(year, month, 1)),
300        this.formatDate(new Date(year, month + parseInt(this.monthRange), 0))
301    ]
302}
303
304OJ.AvailabilityRates.prototype.addMonthData = function() {
305    $(this.datepicker.daysContainer).children().each((i, el) => {
306        el.dataset.month = flatpickr.formatDate(new Date(this.datepicker.currentYear, this.datepicker.currentMonth + i), 'M Y')
307    })
308}
309
310OJ.AvailabilityRates.prototype.setMinDate = function(offset) {
311    let today = this.getToday()
312    return this.datepickerConfig.minDate = today.fp_incr(offset)
313}
314
315OJ.AvailabilityRates.prototype.setMinStay = function(minStay) {
316    return this.minStay = minStay
317}
318
319OJ.AvailabilityRates.prototype.getNextAvailableDates = function() {
320
321    if(Object.keys(this.availability).length) {
322
323        let firstAvailable = this.getAvailableRange(this.formatDate(this.datepickerConfig.minDate))
324
325        if(firstAvailable.length != 0) {
326            return [firstAvailable[0], firstAvailable[firstAvailable.length - 1]]
327        } else {
328            return []
329        }
330
331    } else {
332        let startDate = this.datepickerConfig.minDate
333        return [startDate, startDate.fp_incr(1)]
334    }
335}
336
337OJ.AvailabilityRates.prototype.checkDayForMessage = function(dateStr) {
338    if(this.datepicker.selectedDates.length == 1){
339        this.removeMessages()
340    }
341
342    let dayData = this.displayAvailability ? this.getDayData(dateStr) : false
343
344    if(!dayData) return false
345
346    if(dayData.type == 'check_in_closed') {
347        this.displayMessage("No check-in allowed on selected date.")
348    }
349
350    let minStay = this.minStay || (dayData?.minimum_stay || 1)
351
352    if(minStay > 1) {
353
354        let message = `${minStay}-Night Minimum Stay`;
355
356        if(this.displayPrices) message = `${dayData.price} | ${message}`;
357
358        this.displayMessage(message)
359    }
360}
361
362OJ.AvailabilityRates.prototype.displayMessage = function(message, consoleMessage) {
363    if(!consoleMessage) {
364        this.removeMessages()
365
366        $(`<div class="c-datepicker-message js-booking-data-message"><p>${message}</p></div>`).appendTo($(this.datepicker.calendarContainer))
367    } else {
368        console.log(message);
369    }
370}
371
372OJ.AvailabilityRates.prototype.removeMessages = function() {
373    $('.js-booking-data-message').remove()
374}
375
376OJ.AvailabilityRates.prototype.togglePrices = function(toggle) {
377    if(ojAvailabilityRates.showPrices !== 'true') return
378
379    $(this.container).toggleClass('has-pricing-data', toggle)
380
381    if(this.displayPrices != toggle) {
382        this.displayPrices = toggle
383        this.updateDatepicker()
384    }
385}
386
387OJ.AvailabilityRates.prototype.setDisplayPrices = function(displayPrices) {
388    this.displayPrices = displayPrices
389}
390
391OJ.AvailabilityRates.prototype.toggleAvailability = function(toggle) {
392    if(this.displayAvailability != toggle) {
393        this.displayAvailability = toggle
394        this.updateDatepicker()
395    }
396}
397
398OJ.AvailabilityRates.prototype.setDisplayAvailability = function(displayAvailability) {
399    this.displayAvailability = displayAvailability
400}
401
402OJ.AvailabilityRates.prototype.setRate = function(rateId, refresh) {
403    let newRate = this.rateId != rateId
404    this.rateId = rateId
405
406    if(refresh && newRate) {
407        this.populateAvailability(true)
408    }
409}
410
411OJ.AvailabilityRates.prototype.setRoom = function(roomId, refresh) {
412    let newRoomID = this.roomId != roomId
413    this.roomId = roomId
414
415    if(refresh && newRoomID) {
416        this.populateAvailability(true)
417    }
418}
419
420OJ.AvailabilityRates.prototype.addTagline = function() {
421    if($(this.container).find('.onejourney-tagline').length) return
422
423    let container = this.container
424
425    $.get("/wp-content/plugins/cn-onejourney/assets/powered-by.svg", function(svg) {
426        $(container).append(`
427            <a href='//onejourney.travel' target='_blank' class='onejourney-tagline'>
428                ${$(svg).find('svg')[0].outerHTML}
429            </a>
430        `)
431    })
432}
433
434function populateDatepickers() {
435    $('[data-module="datepicker-booking-form"]').each(function(i, el) {
436        let moduleOptions = $(el).data('module-options')
437        let datepicker = $(el).find('.js-booking-form-datepicker')[0]
438
439        if(!moduleOptions) return
440
441        let availabilityRates = new OJ.AvailabilityRates()
442
443        if(ojAvailabilityRates.enabled === 'true') {
444            let config = Object.assign({}, datepicker._flatpickr.config)
445
446            config['minDate'] = availabilityRates.setMinDate(moduleOptions.minimumAdvanceReservation || 0)
447
448            availabilityRates.prePopulate = true
449            availabilityRates.setDisplayPrices(ojAvailabilityRates.showPrices === 'true')
450            availabilityRates.makeDatepicker(datepicker, config, true)
451        }
452
453    })
454}
455
456$(populateDatepickers)

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.