${a.Title}
\r\n${a.Teaser}
\r\n ${a.Category.Title}\r\n ${a.DatePosted} ${a.Author}`\r\n\r\n // TODO: do something with these articles\r\n const el = document.createElement('div')\r\n el.innerHTML = article\r\n document.getElementsByClassName('blog-main__article-group')[0].appendChild(el.firstChild)\r\n })\r\n}\r\n\r\nblog.ui.inlineTweets = function () {\r\n new Helpers(document.getElementsByClassName('')).eventListener((e) => {\r\n var text = e.target.innerText\r\n window.open('https://twitter.com/intent/tweet?text=' + text + ' ' + window.location.href + '&via=classiccottages&related=classiccottages', 'ArticleTweet', 'width=600,height=400')\r\n })\r\n};\r\n\r\n\r\n\r\nif (new Helpers(document.getElementsByTagName('body')).hasClass('blog')) {\r\n blog.nav.init();\r\n\r\n\r\n if (document.getElementsByClassName('blog-main__article-group').length) {\r\n blog.ui.init();\r\n blog.state.init();\r\n }\r\n}","import Helpers from '../modules/Helpers'\r\n\r\nconst breadcrumbs = {}\r\n\r\nbreadcrumbs.el = document.getElementById('breadcrumb')\r\n\r\nbreadcrumbs.set = (crumbs) => {\r\n breadcrumbs.clear()\r\n\r\n crumbs.forEach((crumb) => {\r\n const newCrumb = document.createElement('li')\r\n const link = document.createElement('a')\r\n link.href = crumb.href\r\n link.innerText = crumb.text\r\n newCrumb.appendChild(link)\r\n breadcrumbs.el.getElementsByTagName('ul')[0].appendChild(newCrumb)\r\n })\r\n}\r\n\r\nbreadcrumbs.clear = () => {\r\n const crumbEls = Array.from(breadcrumbs.el.getElementsByTagName('li'))\r\n // ignore the first el (home link)\r\n crumbEls.shift()\r\n // delete the rest\r\n crumbEls.forEach(crumb => crumb.parentNode.removeChild(crumb))\r\n}\r\n\r\nexport default breadcrumbs","import Helpers from './Helpers'\r\n\r\nconst VALUE_INCREMENT = 1\r\nconst VALUE_DECREMENT = -1\r\n\r\nclass ButtonCounter {\r\n constructor(control) {\r\n this.control = control\r\n\r\n this.name = control.getAttribute(\"data-name\")\r\n this.code = control.getAttribute(\"data-qs-code\")\r\n this.buttons = control.getElementsByTagName('button')\r\n this.input = control.getElementsByTagName('input')[0]\r\n this.text = control.getElementsByTagName('p')[0]\r\n this.max = parseInt(control.getAttribute('data-max')) || 10\r\n this.min = parseInt(control.getAttribute('data-min')) || 0\r\n this.defaultValue = control.getAttribute('data-default-value') || '0'\r\n this.mousedownEvent = -1\r\n this.mousedownInterval = 200\r\n this.mousedownInitialInterval = 600\r\n\r\n this.buttonClickEvent()\r\n }\r\n\r\n getValue() {\r\n return this.input.value\r\n }\r\n\r\n buttonClickEvent() {\r\n const self = this\r\n new Helpers(this.buttons).eventListener((e) => {\r\n const button = e.target\r\n \r\n if (button.getAttribute('aria-label') == 'Plus') {\r\n self.mouseDown(VALUE_INCREMENT)\r\n } else if (button.getAttribute('aria-label') == 'Minus') {\r\n self.mouseDown(VALUE_DECREMENT)\r\n }\r\n\r\n }, 'mousedown')\r\n\r\n new Helpers(this.buttons).eventListener((e) => {\r\n self.mouseUp()\r\n }, 'mouseup')\r\n\r\n new Helpers(this.buttons).eventListener((e) => {\r\n self.mouseUp()\r\n }, 'mouseout')\r\n\r\n }\r\n\r\n updateDisplay() {\r\n let text = parseInt(this.input.value) != 1 ? this.text.getAttribute(\"data-plural\") : this.text.getAttribute(\"data-singular\")\r\n\r\n this.text.innerText = text.replace(\"{0}\", this.input.value)\r\n }\r\n\r\n mouseDown(direction) {\r\n const self = this\r\n if (this.mousedownEvent == -1) {\r\n\r\n if (direction == VALUE_INCREMENT) {\r\n this.increment()\r\n this.mousedownEvent = setTimeout(() => {\r\n self.incrementTimeout()\r\n }, this.mousedownInitialInterval)\r\n }\r\n\r\n if (direction == VALUE_DECREMENT) {\r\n this.decrement()\r\n this.mousedownEvent = setTimeout(() => {\r\n self.decrementTimeout()\r\n }, this.mousedownInitialInterval)\r\n }\r\n }\r\n }\r\n\r\n mouseUp() {\r\n if (this.mousedownEvent != -1) {\r\n clearTimeout(this.mousedownEvent)\r\n this.mousedownEvent = -1\r\n }\r\n }\r\n\r\n increment() {\r\n if (this.input.value < this.max) {\r\n this.input.value = parseInt(this.input.value) + 1\r\n this.updateDisplay()\r\n this.raiseEvent()\r\n }\r\n }\r\n\r\n incrementTimeout() {\r\n this.increment()\r\n this.mousedownEvent = setTimeout(() => {\r\n this.incrementTimeout()\r\n }, this.mousedownInterval)\r\n }\r\n\r\n decrement() {\r\n if (this.input.value > this.min) {\r\n this.input.value = parseInt(this.input.value) - 1\r\n this.updateDisplay()\r\n this.raiseEvent()\r\n }\r\n }\r\n\r\n decrementTimeout() {\r\n this.decrement()\r\n this.mousedownEvent = setTimeout(() => {\r\n this.decrementTimeout()\r\n }, this.mousedownInterval)\r\n }\r\n\r\n raiseEvent() {\r\n this.control.dispatchEvent(new CustomEvent('change'))\r\n }\r\n\r\n setValue(value) {\r\n this.input.value = value\r\n this.updateDisplay()\r\n }\r\n\r\n reset() {\r\n this.setValue(this.defaultValue)\r\n }\r\n}\r\n\r\nexport default ButtonCounter","import moment from 'moment'\r\nimport Hammer from 'hammerjs'\r\nimport Helpers from '../modules/Helpers'\r\n\r\n\r\nclass CalendarRange {\r\n constructor(container, changeEvent = () => { }) {\r\n this.container = container\r\n\r\n this.getControls()\r\n this.getParameters()\r\n this.changeEvent = changeEvent\r\n\r\n this.listeners()\r\n this.hammer()\r\n this.render()\r\n }\r\n\r\n getControls() {\r\n this.prevButtonContainer = this.container.getElementsByClassName('calendar-range__container--left')[0]\r\n this.nextButtonContainer = this.container.getElementsByClassName('calendar-range__container--right')[0]\r\n this.prevButton = this.container.getElementsByClassName('calendar-range__move--left')[0]\r\n this.nextButton = this.container.getElementsByClassName('calendar-range__move--right')[0]\r\n this.monthContainer = this.container.getElementsByClassName('calendar-range__months')[0]\r\n }\r\n\r\n getParameters() {\r\n this.minCalendarWidth = 240\r\n this.minDate = moment(this.container.getAttribute('data-minDate'), \"YYYY-MM-DD\")\r\n this.maxDate = moment(this.container.getAttribute('data-maxDate'), \"YYYY-MM-DD\")\r\n this.currentMonth = moment(this.minDate).startOf('month')\r\n this.noOfCalendars = null\r\n\r\n this.selectedStart = null\r\n this.selectedEnd = null\r\n \r\n return this\r\n }\r\n\r\n listeners() {\r\n new Helpers(this.prevButtonContainer).eventListener((e) => {\r\n e.preventDefault()\r\n this.move('prev')\r\n })\r\n\r\n new Helpers(this.nextButtonContainer).eventListener((e) => {\r\n e.preventDefault()\r\n this.move('next') \r\n })\r\n }\r\n\r\n move(direction) {\r\n if (direction === 'next') {\r\n if (moment(this.currentMonth).endOf('month').isBefore(this.maxDate)) {\r\n this.currentMonth.add(1, 'month')\r\n this.render()\r\n }\r\n } else {\r\n if (moment(this.currentMonth).startOf('month').isAfter(this.minDate)) {\r\n this.currentMonth.subtract(1, 'month')\r\n this.render()\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Instantiate Hammer.js\r\n * \r\n * @return {Object}\r\n */\r\n hammer() {\r\n if (typeof Hammer === 'function') {\r\n const hammertime = new Hammer(this.container)\r\n\r\n hammertime.on('swipeleft swiperight', (e) => {\r\n let direction = ''\r\n if (e.type == 'swiperight') {\r\n direction = 'prev'\r\n } else {\r\n direction = 'next'\r\n }\r\n this.lastMoveTime = (new Date).getTime();\r\n this.move(direction)\r\n })\r\n }\r\n\r\n return this\r\n }\r\n\r\n setNoOfCalendars(calendars) {\r\n this.noOfCalendars = calendars\r\n\r\n return this;\r\n }\r\n\r\n visibleMonths() {\r\n return this.noOfCalendars || Math.max(Math.floor(this.container.offsetWidth / this.minCalendarWidth), 1)\r\n }\r\n\r\n render() {\r\n // clear container\r\n this.monthContainer.innerHTML = ''\r\n\r\n for (let i = 0; i <= this.visibleMonths()-1; i++) {\r\n const monthStart = moment(this.currentMonth).add(i, 'month').startOf('month')\r\n const monthEnd = moment(monthStart).endOf('month')\r\n // wrapper div\r\n const wrap = document.createElement('div')\r\n new Helpers(wrap).addClass('calendar-range__month')\r\n // month title\r\n const title = document.createElement('h4')\r\n title.innerText = monthStart.format(\"MMMM YYYY\")\r\n wrap.appendChild(title)\r\n\r\n // table\r\n const table = document.createElement('table')\r\n\r\n //day headers\r\n const header = document.createElement('tr');\r\n\r\n ['M', 'T', 'W', 'T', 'F', 'S', 'S'].forEach((d) => {\r\n const day = document.createElement('th')\r\n day.innerText = d\r\n header.appendChild(day)\r\n })\r\n table.appendChild(header)\r\n\r\n // days of the month\r\n const calendarStart = moment(monthStart).startOf('isoweek')\r\n const calendarEnd = moment(monthEnd).endOf('isoweek')\r\n\r\n let count = 0\r\n for (let row = 0; row < 6; row++) {\r\n // rows\r\n const tr = document.createElement('tr')\r\n\r\n for (let col = 0; col < 7; col++) {\r\n // days\r\n const calendarDay = moment(calendarStart).add(count,'day')\r\n if (calendarDay.isAfter(calendarEnd)) {\r\n continue;\r\n }\r\n const td = document.createElement('td')\r\n new Helpers(td).addClass('calendar-range__day')\r\n\r\n // check if the day is within the month\r\n if (calendarDay.isBefore(monthStart) || calendarDay.isAfter(monthEnd)) {\r\n new Helpers(td).addClass('calendar-range__day--adjoining')\r\n } else {\r\n td.innerText = calendarDay.date()\r\n td.id = calendarDay.format(\"YYYY-MM-DD\")\r\n if (calendarDay.isBefore(this.minDate) || calendarDay.isAfter(this.maxDate)) {\r\n new Helpers(td).addClass('calendar-range__day--inactive')\r\n }\r\n }\r\n\r\n tr.appendChild(td)\r\n count += 1\r\n }\r\n if (tr.innerHTML.length>0) {\r\n table.appendChild(tr)\r\n }\r\n }\r\n wrap.appendChild(table)\r\n this.monthContainer.appendChild(wrap)\r\n }\r\n\r\n //toggle visiblity of nav buttons\r\n this.prevButton.disabled = this.currentMonth.isSame(this.minDate, 'month')\r\n this.nextButton.disabled = moment(this.currentMonth).add(this.visibleMonths()-1,'month').isSame(this.maxDate, 'month')\r\n\r\n this.highlightRange(this.selectedStart, this.selectedEnd,false)\r\n\r\n this.calendarListeners()\r\n\r\n return this\r\n }\r\n\r\n calendarListeners() {\r\n new Helpers(document.getElementsByClassName('calendar-range__day')).eventListener((e) => {\r\n this.hoverAction(e.currentTarget)\r\n }, 'mouseover')\r\n\r\n new Helpers(document.getElementsByClassName('calendar-range__months')).eventListener((e) => {\r\n this.clearHighlight()\r\n }, 'mouseout')\r\n\r\n new Helpers(document.getElementsByClassName('calendar-range__day')).eventListener((e) => {\r\n if ((this.lastMoveTime || 0) < (new Date).getTime() - 500) {\r\n this.clickAction(e.currentTarget)\r\n }\r\n })\r\n }\r\n\r\n hoverAction(cell) {\r\n const el = new Helpers(cell)\r\n if (this.selectedStart == null) {\r\n if (el.hasClass('calendar-range__day--inactive') || el.hasClass('calendar-range__day--adjoining')) {\r\n this.clearHighlight()\r\n } else {\r\n this.highlightRange(this.cellDate(cell), null, true)\r\n }\r\n } else if (this.selectedEnd === null) {\r\n // end hover\r\n if (el.hasClass('calendar-range__day--inactive') || el.hasClass('calendar-range__day--adjoining')) {\r\n this.clearHighlight();\r\n } else {\r\n this.highlightRange(this.selectedStart, this.cellDate(cell), true)\r\n }\r\n }\r\n }\r\n\r\n clickAction(cell) {\r\n const el = new Helpers(cell)\r\n\r\n if (this.selectedStart === null) {\r\n // start\r\n if (!el.hasClass('calendar-range__day--inactive') && !el.hasClass('calendar-range__day--adjoining')) {\r\n this.selectedStart = this.cellDate(cell)\r\n }\r\n } else if (this.selectedEnd === null) {\r\n // end \r\n if (!el.hasClass('calendar-range__day--inactive') && !el.hasClass('calendar-range__day--adjoining')) {\r\n this.selectedEnd = this.cellDate(cell)\r\n if (this.selectedEnd.isBefore(this.selectedStart)) {\r\n // if the dates are invalid, clear\r\n this.clearSelected()\r\n this.selectedStart = null\r\n this.selectedEnd = null\r\n }\r\n }\r\n } else {\r\n this.clearSelected()\r\n this.selectedStart = this.cellDate(cell)\r\n this.selectedEnd = null\r\n }\r\n\r\n this.highlightRange(this.selectedStart, this.selectedEnd, false)\r\n\r\n this.changeEvent(this.selectedStart,this.selectedEnd)\r\n }\r\n\r\n setDates(startDate, endDate) {\r\n this.clearHighlight()\r\n this.clearSelected()\r\n this.selectedStart = startDate\r\n this.selectedEnd = endDate\r\n this.highlightRange(this.selectedStart, this.selectedEnd, false)\r\n }\r\n\r\n cellDate(cell) {\r\n return moment(cell.id, 'YYYY-MM-DD')\r\n }\r\n\r\n clearHighlight() {\r\n // reset hover UI\r\n new Helpers(document.getElementsByClassName('calendar-range__day')).removeClass('calendar-range__day--highlight')\r\n }\r\n\r\n clearSelected() {\r\n new Helpers(document.getElementsByClassName('calendar-range__day')).removeClass('calendar-range__day--selected')\r\n }\r\n\r\n highlightRange(startDate, endDate, hover = true) {\r\n this.clearHighlight()\r\n\r\n if (startDate === null && endDate === null) {\r\n return;\r\n }\r\n\r\n if (endDate === null) {\r\n endDate = startDate\r\n }\r\n\r\n // first cell\r\n let d = new moment(startDate)\r\n while (d.isBefore(endDate) || d.isSame(endDate)) {\r\n const cellId = d.format(\"YYYY-MM-DD\")\r\n const cell = document.getElementById(cellId)\r\n if (cell !== null) {\r\n const start = moment(d).isSame(startDate)\r\n const end = moment(d).isSame(endDate)\r\n\r\n if (hover) {\r\n new Helpers(cell).addClass('calendar-range__day--highlight')\r\n } else {\r\n new Helpers(cell).addClass('calendar-range__day--selected')\r\n }\r\n }\r\n // move to the next day\r\n d.add(1,'day')\r\n }\r\n }\r\n}\r\n\r\nexport default CalendarRange","//Consent data provided by Termly\r\nconst termlyApiCach = JSON.parse(window.localStorage.getItem(\"TERMLY_API_CACHE\"))\r\n//collection of iframes that were changed at render\r\nconst iframes = document.querySelectorAll('iframe[data-src][data-requiresconsent]')\r\n//Button to load consent page\r\nlet consentRequired = document.getElementsByClassName('cookie-consent__iframe')[0].cloneNode(true)\r\nconsentRequired.classList.add('required')\r\n\r\nlet termlyConsent\r\nif (typeof (termlyApiCach) !== 'undefined' && termlyApiCach !== null && typeof (termlyApiCach.TERMLY_COOKIE_CONSENT) != 'undefined') {\r\n termlyConsent = termlyApiCach.TERMLY_COOKIE_CONSENT.value\r\n}\r\n\r\niframes.forEach(function (el, i) {\r\n //check that only iframes that need advertising consent are changed\r\n if (ConsentGiven(el)) {\r\n //Show the video if the consent is approved\r\n el.setAttribute('src', el.getAttribute('data-src'))\r\n el.removeAttribute('data-src')\r\n } else {\r\n //add a wrapper to the iframe if the consent was not given. This provides the button to change consent options\r\n var wrapper = consentRequired.cloneNode(true);\r\n el.parentNode.insertBefore(wrapper, el)\r\n wrapper.appendChild(el)\r\n }\r\n})\r\nfunction ConsentGiven(el) { \r\n return typeof(termlyConsent) !== 'undefined' && el.getAttribute('data-requiresconsent') === 'advertising' && termlyConsent.advertising === true\r\n}","import Helpers from './Helpers'\r\n\r\nnew Helpers(window).eventListener(() => {\r\n\r\n const images = [].slice.call(document.querySelectorAll('img[data-deferred=\"true\"]'))\r\n const containers = [].slice.call(document.querySelectorAll('.deferred-images'))\r\n\r\n const canUseIntersection = 'IntersectionObserver' in window\r\n\r\n\r\n if (canUseIntersection) {\r\n // if \"isIntersecting\" is not present (Edge 15) add a polyfill\r\n if (!('isIntersecting' in IntersectionObserverEntry.prototype)) {\r\n Object.defineProperty(window.IntersectionObserverEntry.prototype, 'isIntersecting', {\r\n get: () => this.intersectionRatio > 0,\r\n })\r\n }\r\n\r\n const options = {\r\n rootMargin: '100px',\r\n threshold: 0,\r\n }\r\n\r\n const deferredImageObserver = new IntersectionObserver((entries) => {\r\n entries.forEach((entry) => {\r\n if (entry.isIntersecting || entry.intersectionRatio > 0) {\r\n const img = entry.target\r\n if (img.hasAttribute('data-src')) {\r\n img.setAttribute('src', img.getAttribute('data-src'))\r\n // Srcset Images\r\n if (img.hasAttribute('data-srcset')) {\r\n img.setAttribute('srcset', img.getAttribute('data-srcset'))\r\n img.setAttribute('sizes', img.getAttribute('data-sizes'))\r\n\r\n img.removeAttribute('data-srcset')\r\n img.removeAttribute('data-sizes')\r\n }\r\n img.removeAttribute('data-src')\r\n img.removeAttribute('data-deferred')\r\n }\r\n deferredImageObserver.unobserve(img)\r\n }\r\n })\r\n }, options)\r\n\r\n const deferredContainerObserver = new IntersectionObserver((entries) => {\r\n entries.forEach((entry) => {\r\n if (entry.isIntersecting || entry.intersectionRatio > 0) {\r\n const container = entry.target\r\n const imgs = [].slice.call(container.querySelectorAll('img[data-deferred=\"true\"]'))\r\n imgs.forEach((img) => {\r\n if (img.hasAttribute('data-src')) {\r\n img.setAttribute('src', img.getAttribute('data-src'))\r\n // Srcset Images\r\n if (img.hasAttribute('data-srcset')) {\r\n img.setAttribute('srcset', img.getAttribute('data-srcset'))\r\n img.setAttribute('sizes', img.getAttribute('data-sizes'))\r\n\r\n img.removeAttribute('data-srcset')\r\n img.removeAttribute('data-sizes')\r\n }\r\n img.removeAttribute('data-src')\r\n img.removeAttribute('data-deferred')\r\n }\r\n // unobserve the image once loaded\r\n deferredImageObserver.unobserve(img)\r\n })\r\n container.classList.remove('deferred-images')\r\n // unobserve the container once all images within are loaded\r\n deferredContainerObserver.unobserve(container)\r\n }\r\n })\r\n }, options)\r\n\r\n // register observer to elements\r\n images.forEach((img) => {\r\n deferredImageObserver.observe(img)\r\n })\r\n\r\n containers.forEach((container) => {\r\n deferredContainerObserver.observe(container)\r\n })\r\n } else {\r\n images.forEach((img) => {\r\n img.setAttribute('src', img.getAttribute('data-src'))\r\n img.setAttribute('srcset', img.getAttribute('data-srcset'))\r\n img.setAttribute('sizes', img.getAttribute('data-sizes'))\r\n img.removeAttribute('data-src')\r\n img.removeAttribute('data-srcset')\r\n img.removeAttribute('data-sizes')\r\n img.removeAttribute('data-deferred')\r\n })\r\n }\r\n}, 'load')\r\n","/* global L */\r\n\r\nimport Map, { streetLayer, satelliteLayer } from '../modules/Map'\r\nimport MapboxOS from '../modules/maps/mapboxOS'\r\n\r\nif (document.getElementById('exploreMap') !== null) {\r\n // Load Ordinance survey projection for Leaflet Map\r\n MapboxOS()\r\n\r\n const exploreMap = new Map('exploreMap', {\r\n coords: [50.1016, -5.2750],\r\n maxZoom: 16,\r\n minZoom: 7,\r\n zoom: 7,\r\n scrollWheelZoom: false,\r\n }, {\r\n Streets: streetLayer(),\r\n Satellite: satelliteLayer(),\r\n 'Ordnance Survey': L.OSMap.tilelayer('qOGSRiomYmUUAki6J2BZrtZcUVWMl4aS'),\r\n })\r\n\r\n Array.from(document.getElementsByClassName('explore-map-item')).forEach((item) => {\r\n const lat = parseFloat(item.getAttribute('data-lat'))\r\n const lng = parseFloat(item.getAttribute('data-lng'))\r\n const icon = item.getAttribute('data-icon')\r\n const title = item.getAttribute('data-title')\r\n\r\n exploreMap.addMarker([lat, lng], {\r\n title,\r\n icon: L.icon({\r\n iconUrl: `/media/map/explore-icons/${icon}.png`,\r\n iconSize: [35, 44],\r\n iconAnchor: [19, 44],\r\n popupAnchor: [0, -40],\r\n }),\r\n riseOnHover: true,\r\n })\r\n })\r\n\r\n const cottageLat = parseFloat(document.getElementById('exploreMap').getAttribute('data-lat'))\r\n const cottageLng = parseFloat(document.getElementById('exploreMap').getAttribute('data-lng'))\r\n const cottageName = document.getElementById('exploreMap').getAttribute('data-name')\r\n\r\n exploreMap.addMarker([cottageLat, cottageLng], {\r\n title: cottageName,\r\n icon: L.icon({\r\n iconUrl: '/media/map/icon-home.png',\r\n iconSize: [33, 47],\r\n iconAnchor: [17, 46],\r\n popupAnchor: [0, -40],\r\n }),\r\n zIndexOffset: 100,\r\n riseOnHover: true,\r\n })\r\n\r\n exploreMap.fitMarkers({ padding: [40, 40] })\r\n}\r\n","import Helpers from '../modules/Helpers'\r\n\r\nconst featuredProperties = document.getElementsByClassName('featured-properties__item')\r\n\r\nnew Helpers(featuredProperties).eventListener((e) => {\r\n const code = e.currentTarget.getAttribute('data-cottagecode')\r\n const position = e.currentTarget.getAttribute('data-position')\r\n window.dataLayerQueue.push({ 'event': 'featuredPropertyNavigation', 'cottageCode': code, 'position': position });\r\n})\r\n","import Reveal from '../modules/Reveal'\r\nimport Helpers from '../modules/Helpers'\r\nimport XHRPromise from 'xhr-promise'\r\n\r\nclass FindAddress {\r\n \r\n search(postcode) {\r\n return new Promise((resolve) => {\r\n const xhr = new XHRPromise()\r\n\r\n xhr.send({\r\n url: '/ajax/postcode.aspx?postCode=' + postcode\r\n }).then((response) => {\r\n return (new window.DOMParser()).parseFromString(response.responseText, \"text/xml\")\r\n })\r\n .then((data) => {\r\n const results = data.getElementsByTagName('Item')\r\n const addresses = []\r\n let error = null\r\n if (results.length == 1 && results[0].getAttribute('error_number') !== null) {\r\n error = results[0].getAttribute('message')\r\n }\r\n\r\n Array.from(results).forEach((result) => {\r\n if (result.getAttribute('id') !== null) {\r\n addresses.push({\r\n id: result.getAttribute('id'),\r\n description: result.getAttribute('description')\r\n })\r\n }\r\n })\r\n\r\n resolve({\r\n error: error,\r\n addresses: addresses\r\n })\r\n })\r\n })\r\n }\r\n\r\n getAddress(id) {\r\n return new Promise((resolve) => {\r\n const xhr = new XHRPromise()\r\n\r\n xhr.send({\r\n url: '/ajax/postcode.aspx?addressID=' + id\r\n }).then((response) => {\r\n return (new window.DOMParser()).parseFromString(response.responseText, \"text/xml\")\r\n })\r\n .then((data) => {\r\n const result = data.getElementsByTagName('Item')[0]\r\n const address = {\r\n org: result.getAttribute('organisation_name'),\r\n line1: result.getAttribute('line1'),\r\n line2: result.getAttribute('line2'),\r\n line3: result.getAttribute('line3'),\r\n line4: result.getAttribute('line4'),\r\n town: result.getAttribute('post_town'),\r\n county: result.getAttribute('county'),\r\n postcode: result.getAttribute('postcode')\r\n }\r\n\r\n resolve({\r\n address: address\r\n })\r\n })\r\n })\r\n }\r\n\r\n validatePostcode(postcode) {\r\n return /^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))\\s?[0-9][A-Za-z]{2})$/.test(postcode)\r\n }\r\n}\r\n\r\n\r\nclass FindAddressModal {\r\n constructor() {\r\n this.modal = document.getElementsByClassName('modal-postcode')\r\n this.errorEl = document.getElementsByClassName('modal-postcode__error')[0]\r\n this.resultsEl = document.getElementsByClassName('modal-postcode__results')[0]\r\n this.listeners()\r\n }\r\n \r\n listeners() {\r\n new Helpers(document.getElementsByClassName('modal-postcode__submit')).eventListener((e) => {\r\n e.preventDefault()\r\n this.search()\r\n })\r\n new Helpers(this.resultsEl).eventListener((e) => {\r\n this.loadAddress(e.target.getAttribute('data-id'))\r\n })\r\n new Helpers(document.getElementById('btnFindPostcode')).eventListener((e) => {\r\n document.getElementsByClassName('modal-postcode__search')[0].postcode.value = document.getElementById('txtFindPostcode').value\r\n this.search()\r\n })\r\n }\r\n\r\n postcode() {\r\n return document.getElementsByClassName('modal-postcode__search')[0].postcode.value\r\n }\r\n\r\n search() {\r\n\r\n if (new FindAddress().validatePostcode(this.postcode())) {\r\n new FindAddress().search(this.postcode()).then((results) => {\r\n if (results.error !== null) {\r\n this.error(results.error)\r\n return\r\n }\r\n\r\n if (results.addresses.length === 0) {\r\n this.error(\"No address found for this postcode.\")\r\n } else {\r\n this.listAddresses(results.addresses)\r\n }\r\n })\r\n\r\n } else {\r\n if (this.postcode() === '') {\r\n this.resultsEl.setAttribute('aria-hidden', true)\r\n this.errorEl.setAttribute('aria-hidden', true)\r\n } else {\r\n this.error(\"Provided postcode is not valid.\")\r\n }\r\n }\r\n }\r\n\r\n error(message) {\r\n this.resultsEl.setAttribute('aria-hidden', true)\r\n this.errorEl.setAttribute('aria-hidden', false)\r\n this.errorEl.innerText = message\r\n }\r\n\r\n listAddresses(addresses) {\r\n\r\n this.resultsEl.innerHTML = ''\r\n\r\n addresses.forEach((address) => {\r\n const li = document.createElement('li')\r\n li.innerText = address.description\r\n li.setAttribute('data-id', address.id)\r\n this.resultsEl.appendChild(li)\r\n })\r\n\r\n this.resultsEl.setAttribute('aria-hidden', false)\r\n this.errorEl.setAttribute('aria-hidden', true)\r\n\r\n\r\n }\r\n\r\n loadAddress(id) {\r\n new FindAddress().getAddress(id).then((result) => {\r\n document.querySelector('[id$=_txtAdd1]').value = (result.address.org !== null ? `${result.address.org}, ` : '') + result.address.line1\r\n document.querySelector('[id$=_txtAdd2]').value = result.address.line2\r\n document.querySelector('[id$=_txtAdd3]').value = result.address.line3\r\n document.querySelector('[id$=_txtTown]').value = result.address.town\r\n document.querySelector('[id$=_txtCounty]').value = result.address.county\r\n document.querySelector('[id$=_txtPostcode]').value = result.address.postcode\r\n if (document.querySelector('[id$=_ddlCountry]') !== null) {\r\n document.querySelector('[id$=_ddlCountry]').value = 'GB'\r\n }\r\n\r\n // hide modal\r\n new Reveal(this.modal).hide('modal--active')\r\n new Helpers(document.getElementsByTagName('body')).removeClass('no-scroll')\r\n })\r\n }\r\n}\r\n\r\nif (document.getElementsByClassName('modal-postcode').length) {\r\n const modalPostcode = new FindAddressModal() \r\n}","import Helpers from '../modules/Helpers'\r\n\r\nconst activeButtonClass = 'find-cottage-index__char--active'\r\nconst findCottageIndexButtons = document.getElementsByClassName('find-cottage-index__char')\r\nconst findCottageList = document.getElementsByClassName('find-cottage-index__list')\r\n\r\nnew Helpers(findCottageIndexButtons).eventListener((e) => {\r\n e.preventDefault()\r\n\r\n new Helpers(findCottageIndexButtons).removeClass(activeButtonClass)\r\n new Helpers(e.target).addClass(activeButtonClass)\r\n\r\n const char = e.target.value.toUpperCase()\r\n\r\n Array.from(findCottageList).forEach((ul) => {\r\n Array.from(ul.getElementsByTagName('li')).forEach((li) => {\r\n const startsWith = li.getAttribute('data-char').toUpperCase()\r\n const isNumeric = !isNaN(startsWith)\r\n const visible = char === startsWith || (char === \"0-9\" && isNumeric) || char === \"ALL\"\r\n li.setAttribute('aria-hidden', !visible)\r\n })\r\n })\r\n \r\n})","import Helpers from '../modules/Helpers'\r\nimport XHRPromise from 'xhr-promise'\r\n\r\nclass GeoSearch {\r\n \r\n constructor(input, results, options) {\r\n this.defaultOptions = {\r\n minSearchLength: 3,\r\n noMatchTimeoutDelay: 1000,\r\n placeholderText: {\r\n focus: 'Enter place or cottage name...',\r\n blur: 'Where would you like to go?'\r\n }\r\n }\r\n\r\n this.input = input\r\n this.results = results\r\n this.activeSearch = null\r\n\r\n \r\n\r\n this.options = Object.assign(this.defaultOptions, options)\r\n this.selectActions = []\r\n this.invalidActions = []\r\n\r\n this.inputContentIsValid = true\r\n\r\n this.events()\r\n }\r\n\r\n on(event, callback) {\r\n switch (event) {\r\n case 'select':\r\n this.selectActions.push(callback)\r\n break;\r\n case 'invalid':\r\n this.invalidActions.push(callback)\r\n }\r\n }\r\n\r\n events() {\r\n\r\n new Helpers(this.input).eventListener((e) => {\r\n this.inputContentIsValid = false\r\n this.runSearch()\r\n }, 'search')\r\n\r\n new Helpers(this.input).eventListener((e) => {\r\n this.input.dispatchEvent(new CustomEvent('search'))\r\n }, 'input')\r\n\r\n new Helpers(this.input).eventListener((e) => {\r\n this.input.setAttribute('placeholder', this.options.placeholderText.focus)\r\n }, 'focus')\r\n\r\n new Helpers(this.input).eventListener((e) => {\r\n \r\n // clear the results box on blur - with a delay to ensure click events in the results box would already have fired\r\n setTimeout(() => {\r\n this.input.setAttribute('placeholder', this.options.placeholderText.blur)\r\n if (!this.inputContentIsValid) {\r\n this.invalidActions.forEach((cb) => {\r\n cb(this.input.value)\r\n }) \r\n }\r\n this.clearResults()\r\n }, 300)\r\n }, 'blur')\r\n\r\n new Helpers(this.input).eventListener((e) => {\r\n switch (e.keyCode) {\r\n case 40:\r\n e.preventDefault()\r\n e.stopPropagation()\r\n this.highlightNextResult()\r\n return false;\r\n break;\r\n case 38:\r\n e.preventDefault()\r\n e.stopPropagation()\r\n this.highlightPrevResult()\r\n return false;\r\n break;\r\n case 13:\r\n e.preventDefault()\r\n e.stopPropagation()\r\n this.selectResult()\r\n return false;\r\n break;\r\n case 9:\r\n e.preventDefault()\r\n e.stopPropagation()\r\n this.selectResult()\r\n return false;\r\n break;\r\n }\r\n return true;\r\n }, 'keydown')\r\n\r\n new Helpers(this.searchResults).eventListener((e) => {\r\n this.clearHighlight()\r\n }, 'mouseover')\r\n\r\n new Helpers(this.results).eventListener((e) => {\r\n const result = new Helpers(e.target).getClosest('li')\r\n const index = Array.from(this.results.children).indexOf(result)\r\n this.highlightResult(index)\r\n this.selectResult()\r\n })\r\n }\r\n\r\n getHighlightedResult() {\r\n return this.results.querySelector('li[aria-current=\"true\"]')\r\n }\r\n\r\n clearHighlight() {\r\n const results = this.results.querySelectorAll('li')\r\n new Helpers(results).setAttributes({ 'aria-current': false })\r\n }\r\n\r\n highlightResult(index) {\r\n this.clearHighlight()\r\n const result = this.results.children[index]\r\n result.setAttribute('aria-current', true)\r\n }\r\n\r\n highlightPrevResult() {\r\n const currentHighlight = this.getHighlightedResult()\r\n let newHighlight = null\r\n if (currentHighlight === null || currentHighlight.previousElementSibling === null) {\r\n newHighlight = this.results.lastElementChild\r\n } else {\r\n newHighlight = currentHighlight.previousElementSibling\r\n }\r\n const newIndex = Array.from(this.results.children).indexOf(newHighlight)\r\n\r\n this.highlightResult(newIndex)\r\n return this\r\n }\r\n\r\n highlightNextResult() {\r\n const currentHighlight = this.getHighlightedResult()\r\n let newHighlight = null\r\n if (currentHighlight === null || currentHighlight.nextElementSibling === null) {\r\n newHighlight = this.results.firstElementChild\r\n } else {\r\n newHighlight = currentHighlight.nextElementSibling\r\n }\r\n const newIndex = Array.from(this.results.children).indexOf(newHighlight)\r\n\r\n this.highlightResult(newIndex)\r\n return this\r\n }\r\n\r\n\r\n activeResultData() {\r\n const result = this.getHighlightedResult()\r\n if (result !== null) {\r\n return {\r\n type: result.getAttribute('data-type'),\r\n code: result.getAttribute('data-code'),\r\n text: result.getAttribute('data-value'),\r\n url: result.getAttribute('data-url')\r\n }\r\n } else {\r\n return null\r\n }\r\n }\r\n\r\n selectResult() {\r\n if (this.hasResults()) {\r\n this.inputContentIsValid = true\r\n this.selectActions.forEach((cb) => {\r\n const data = this.activeResultData()\r\n if (data !== null) {\r\n cb(data)\r\n }\r\n })\r\n }\r\n }\r\n\r\n setValue(value) {\r\n this.inputContentIsValid = true\r\n this.input.value = value\r\n }\r\n\r\n clearResults() {\r\n this.results.innerHTML = ''\r\n }\r\n\r\n hasResults() {\r\n return this.results.querySelectorAll('li').length>0\r\n }\r\n \r\n createResultElement(data) {\r\n const el = document.createElement('li')\r\n el.setAttribute('class','geo-search-result')\r\n if (data.Place.length > 0) {\r\n el.innerHTML = `${data.Name}${data.Place}`\r\n } else {\r\n el.innerHTML = `${data.Name}`\r\n }\r\n el.setAttribute('data-value', data.Name)\r\n el.setAttribute('data-type', data.Type)\r\n el.setAttribute('data-code', data.Code)\r\n el.setAttribute('data-point', data.Point.join(','))\r\n el.setAttribute('data-url', data.Url)\r\n\r\n return el\r\n }\r\n\r\n\r\n runSearch() { \r\n if (this.activeSearch !== null) {\r\n this.activeSearch.getXHR().abort()\r\n this.activeSearch = null\r\n }\r\n\r\n // if search term is atleast three characters or a numeric value\r\n if (this.input.value.length >= this.options.minSearchLength || (this.input.value.length >= 1 && !isNaN(this.input.value))) {\r\n\r\n this.activeSearch = new XHRPromise()\r\n\r\n this.activeSearch.send({\r\n url: `/feeds/geoSearch.aspx?s=${this.input.value}`\r\n }).then((response) => {\r\n const resultCount = response.responseText.length\r\n // clear the results list\r\n this.clearResults()\r\n // build and apply html for each result\r\n response.responseText.forEach((r) => {\r\n this.results.appendChild(this.createResultElement(r))\r\n })\r\n\r\n if (resultCount > 0) {\r\n if (resultCount === 1) {\r\n // we have one result, highlight it\r\n this.highlightResult(0)\r\n }\r\n } else {\r\n // no results for this term, log it as a GA event (with some debounce logic)\r\n clearTimeout(this.noMatchTimeout)\r\n this.noMatchTimeout = setTimeout(() => {\r\n window.dataLayerQueue = window.dataLayerQueue || []\r\n window.dataLayerQueue.push({ 'event': 'resultsLocationSearch', 'searchTerm': this.input.value });\r\n }, this.options.noMatchTimeoutDelay)\r\n\r\n }\r\n this.activeSearch = null\r\n }).catch((error) => {\r\n if (error.reason !== 'abort') {\r\n console.log(error)\r\n }\r\n });\r\n } else {\r\n // clear the results list\r\n this.clearResults()\r\n }\r\n }\r\n\r\n}\r\n\r\nexport default GeoSearch","import Helpers from '../modules/Helpers'\r\nimport StarRating from '../modules/StarRating'\r\nimport XHRPromise from 'xhr-promise'\r\n\r\nconst modal = document.getElementById('guideReviewModal')\r\n\r\nif (modal !== null) {\r\n\r\n const titleEl = modal.getElementsByTagName('h3')[0]\r\n const form = modal.getElementsByTagName('form')[0]\r\n const successView = modal.getElementsByClassName('modal-guide-review__success')\r\n const errorView = modal.getElementsByClassName('modal-guide-review__error')\r\n\r\n // on button click, initialise modal\r\n new Helpers(document.getElementsByClassName('property-explore__review-cta')).eventListener((e) => {\r\n const el = e.target\r\n\r\n // get attraction info from button\r\n const attractionId = el.getAttribute('data-id')\r\n const attractionName = el.getAttribute('data-name')\r\n \r\n // reset view\r\n form.setAttribute('aria-hidden', false)\r\n new Helpers(successView).setAttributes({ 'aria-hidden': true })\r\n new Helpers(errorView).setAttributes({ 'aria-hidden': true })\r\n\r\n // set the title and the id\r\n titleEl.innerText = `Review ${attractionName}`\r\n form.attractionId.value = attractionId\r\n\r\n // reset form values\r\n form.reviewTitle.value = ''\r\n form.reviewDescription.value = ''\r\n form.reviewAnonymous.checked = false\r\n new StarRating(modal.getElementsByClassName('star-rating')[0]).clear()\r\n \r\n })\r\n\r\n\r\n // on submit of modal form, handle with ajax\r\n new Helpers(form).eventListener((e) => {\r\n e.preventDefault()\r\n const xhr = new XHRPromise()\r\n\r\n xhr.send({\r\n method: 'POST',\r\n url: \"/my-classic/Ajax.aspx\",\r\n data: `type=submitreview&AttractionId=${form.attractionId.value}&Title=${form.reviewTitle.value}&Description=${form.reviewDescription.value}&StarRating=${form.reviewRating.value}&Anonymous=${form.reviewAnonymous.checked ? 1 : 0}`\r\n })\r\n .then((response) => {\r\n if (response.status !== 200) {\r\n throw new Error('submission failed')\r\n }\r\n //show success dialog, with timeout to close modal\r\n form.setAttribute('aria-hidden', true)\r\n new Helpers(successView).setAttributes({ 'aria-hidden': false })\r\n })\r\n .catch((e) => {\r\n // show failure dialog\r\n form.setAttribute('aria-hidden', true)\r\n new Helpers(errorView).setAttributes({ 'aria-hidden': false })\r\n })\r\n }, 'submit')\r\n\r\n}","import Helpers from '../modules/Helpers'\r\nimport Reveal from '../modules/Reveal'\r\n\r\nconst headerSecondaryLinksIcon = document.getElementById('headerSecondaryLinksIcon')\r\nconst headerLinks = document.getElementById('headerLinks')\r\n\r\n// Toggle header secondary navigation when clicking\r\n// burger icon (only visible on small desktops).\r\nnew Helpers(headerSecondaryLinksIcon).eventListener((e) => {\r\n e.preventDefault()\r\n\r\n new Helpers(headerSecondaryLinksIcon).toggleClass('burger-icon--active')\r\n new Reveal(headerLinks).toggle('header__links--active')\r\n})\r\n","import isNodeList from './IsNodeList'\r\n\r\nclass Helpers {\r\n constructor(element) {\r\n // Ensure whatever element is passed into the\r\n // constructor is an array, as methods loop\r\n // over every element using `forEach`.\r\n if (Array.isArray(element)) {\r\n this.element = element\r\n } else if (!isNodeList(element)) {\r\n this.element = [element]\r\n } else {\r\n this.element = Array.from(element)\r\n }\r\n }\r\n\r\n /**\r\n * Get closest element\r\n *\r\n * @param {HTMLElement|HTMLCollection|NodeList} selector Name of closest element to get\r\n * @return {HTMLElement|HTMLCollection|NodeList} Closest element\r\n */\r\n getClosest(selector) {\r\n this.element.forEach((element) => {\r\n let currentElement = element\r\n\r\n while (currentElement) {\r\n if (typeof selector === 'string') {\r\n if (currentElement.matches(selector)) {\r\n this.closestElement = currentElement\r\n return\r\n }\r\n } else if (currentElement === selector) {\r\n this.closestElement = currentElement\r\n return\r\n }\r\n\r\n currentElement = currentElement.parentElement\r\n }\r\n })\r\n\r\n return this.closestElement\r\n }\r\n\r\n getNext(selector) {\r\n this.element.forEach((element) => {\r\n let currentElement = element\r\n\r\n while (currentElement = currentElement.nextElementSibling) {\r\n if (currentElement.matches(selector)) {\r\n this.nextElement = currentElement\r\n return\r\n }\r\n }\r\n })\r\n\r\n return this.nextElement\r\n }\r\n\r\n getPrevious(selector) {\r\n this.element.forEach((element) => {\r\n let currentElement = element\r\n\r\n while (currentElement = currentElement.previousElementSibling) {\r\n if (currentElement.matches(selector)) {\r\n this.previousElement = currentElement\r\n return\r\n }\r\n }\r\n })\r\n\r\n return this.previousElement\r\n }\r\n\r\n getParents(elem, selector) {\r\n // credit to Chris Ferdinandi: https://gomakethings.com/climbing-up-and-down-the-dom-tree-with-vanilla-javascript/#getting-all-matches-up-the-tree\r\n // Element.matches() polyfill\r\n if (!Element.prototype.matches) {\r\n Element.prototype.matches =\r\n Element.prototype.matchesSelector ||\r\n Element.prototype.mozMatchesSelector ||\r\n Element.prototype.msMatchesSelector ||\r\n Element.prototype.oMatchesSelector ||\r\n Element.prototype.webkitMatchesSelector ||\r\n function (s) {\r\n var matches = (this.document || this.ownerDocument).querySelectorAll(s),\r\n i = matches.length;\r\n while (--i >= 0 && matches.item(i) !== this) { }\r\n return i > -1;\r\n };\r\n }\r\n\r\n // Setup parents array\r\n var parents = [];\r\n\r\n // Get matching parent elements\r\n for (; elem && elem !== document; elem = elem.parentNode) {\r\n\r\n // Add matching parents to array\r\n if (selector) {\r\n if (elem.matches(selector)) {\r\n parents.push(elem);\r\n }\r\n } else {\r\n parents.push(elem);\r\n }\r\n\r\n }\r\n\r\n return parents;\r\n\r\n };\r\n\r\n /**\r\n * Set multiple element attributes\r\n *\r\n * @param {Object} attributes Attributes and values to set\r\n * @return {Object}\r\n */\r\n setAttributes(attributes) {\r\n this.element.forEach((element) => {\r\n Object.keys(attributes).forEach((key) => {\r\n const attrValue = attributes[key]\r\n if (attrValue === null) {\r\n element.removeAttribute(key)\r\n } else {\r\n element.setAttribute(key, attrValue)\r\n }\r\n })\r\n })\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Remove multiple element attributes\r\n *\r\n * @param {Array} attributes Array of objects to remove\r\n * @return {Object}\r\n */\r\n removeAttributes(attributes) {\r\n this.element.forEach((element) => {\r\n attributes.forEach(function (key, index) {\r\n element.removeAttribute(key)\r\n })\r\n })\r\n\r\n return this\r\n }\r\n\r\n\r\n /**\r\n * Add class to clement\r\n *\r\n * @param {String} className Class name to add\r\n * @return {Object}\r\n */\r\n addClass(...className) {\r\n this.element.forEach(element => element.classList.add(...className))\r\n\r\n return this\r\n }\r\n\r\n\r\n /**\r\n * Remove class from element\r\n *\r\n * @param {String} className Class name to remove\r\n * @return {Object}\r\n */\r\n removeClass(...className) {\r\n this.element.forEach(element => element.classList.remove(...className))\r\n\r\n return this\r\n }\r\n\r\n\r\n /**\r\n * Toggle class on element\r\n *\r\n * @param {String} className Class name to toggle\r\n * @return {Object}\r\n */\r\n toggleClass(...className) {\r\n this.element.forEach(element => element.classList.toggle(...className))\r\n\r\n return this\r\n }\r\n\r\n\r\n /**\r\n * Check if element has class\r\n *\r\n * @param {String} className Class name to check\r\n * @return {Boolean} Whether element has class or not\r\n */\r\n hasClass(className) {\r\n return this.element.filter(element => element.classList.contains(className)).length\r\n }\r\n\r\n\r\n /**\r\n * Toggle element true/false attribute\r\n *\r\n * @param {String} attribute Attribute to toggle\r\n * @return {Object}\r\n */\r\n toggleAttribute(attribute) {\r\n this.element.forEach((element) => {\r\n if (element.getAttribute(attribute) === 'true') {\r\n element.setAttribute(attribute, 'false')\r\n } else {\r\n element.setAttribute(attribute, 'true')\r\n }\r\n })\r\n\r\n return this\r\n }\r\n\r\n\r\n /**\r\n * Create event listener\r\n *\r\n * @param {Function} callback Event listener callback function\r\n * @param {String} [eventListener='click'] Desktop event\r\n * @return {Object}\r\n */\r\n eventListener(callback, eventListener = 'click') {\r\n this.element.forEach((element) => {\r\n if (element) {\r\n element.addEventListener(eventListener, callback, false)\r\n }\r\n })\r\n\r\n return this\r\n }\r\n\r\n delegateEventListener(selector, handler, eventListener = 'click') {\r\n this.element.forEach((element) => {\r\n if (element) {\r\n element.addEventListener(eventListener, function (event) {\r\n var listeningTarget = new Helpers(event.target).getClosest(selector)\r\n if (listeningTarget) {\r\n handler.call(listeningTarget, event)\r\n }\r\n });\r\n }\r\n })\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * Remove item from array\r\n *\r\n * @param {HTMLElement|HTMLCollection|NodeList} element Element to remove from array\r\n * @return {Object}\r\n */\r\n exclude(element) {\r\n this.element = this.element.filter(item => element !== item)\r\n\r\n return this\r\n }\r\n\r\n\r\n /**\r\n * Check if element has aria-controls attribute\r\n *\r\n * @return {Boolean} Whether element has aria-controls attribute or not\r\n */\r\n hasAriaControlsAttr() {\r\n return this.element.filter(element => element.getAttribute('aria-controls')).length\r\n }\r\n\r\n // Check if element is in visible viewport (completely in view port)\r\n isInViewport() {\r\n this.elementIsInViewport = false\r\n this.element.forEach((element) => {\r\n const rect = element.getBoundingClientRect()\r\n if ((rect.height > 0 || rect.width > 0) &&\r\n rect.top >= 0 &&\r\n rect.left >= 0 &&\r\n rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\r\n rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {\r\n\r\n this.elementIsInViewport = true\r\n }\r\n })\r\n\r\n return this.elementIsInViewport\r\n }\r\n\r\n\r\n // Check if element is even partially in viewport\r\n isPartiallyInViewport() {\r\n return this.element.some(element => {\r\n const rect = element.getBoundingClientRect();\r\n return (\r\n rect.top < (window.innerHeight || document.documentElement.clientHeight) &&\r\n rect.bottom > 0 &&\r\n rect.left < (window.innerWidth || document.documentElement.clientWidth) &&\r\n rect.right > 0\r\n );\r\n });\r\n }\r\n\r\n /**\r\n * Get largest element width\r\n *\r\n * @return {Integer} Width of largest element\r\n */\r\n getWidth(includePadding = true) {\r\n const widths = this.element.map((element) => {\r\n const styles = window.getComputedStyle(element)\r\n let width = parseInt(styles.width, 10)\r\n\r\n if (!includePadding) {\r\n width = width - parseInt(styles.paddingLeft, 10) - parseInt(styles.paddingRight, 10)\r\n }\r\n\r\n return width\r\n })\r\n\r\n return Math.max(widths)\r\n }\r\n\r\n\r\n /**\r\n * Get largest element height\r\n *\r\n * @return {Integer} Height of largest element\r\n */\r\n getHeight(includePadding = true) {\r\n const heights = this.element.map((element) => {\r\n const styles = window.getComputedStyle(element)\r\n let height = parseInt(styles.height, 10)\r\n\r\n if (!includePadding) {\r\n height = height - parseInt(styles.paddingTop, 10) - parseInt(styles.paddingBottom, 10)\r\n }\r\n\r\n return height\r\n })\r\n\r\n return Math.max(heights)\r\n }\r\n}\r\n\r\nexport default Helpers\r\n","/**\r\n * Check if browser is IE 10\r\n * \r\n * @return {Boolean}\r\n */\r\nconst isBrowserIE10 = () => navigator.userAgent.match('MSIE 10.0;')\r\n\r\nexport default isBrowserIE10\r\n","import Helpers from '../modules/Helpers'\r\n\r\n/**\r\n * Check if browser is IE 9\r\n * \r\n * @return {Boolean}\r\n */\r\nconst isBrowserIE9 = () => {\r\n const htmlTag = document.getElementsByTagName('html')[0]\r\n\r\n return new Helpers(htmlTag).hasClass('ie9')\r\n}\r\n\r\nexport default isBrowserIE9\r\n","/**\r\n * Check if element is a node list\r\n *\r\n * @param {HTMLElement|HTMLCollection|NodeList} el Element to check\r\n * @return {Boolean}\r\n */\r\nconst IsNodeList = (el) => {\r\n const stringRepr = Object.prototype.toString.call(el)\r\n\r\n return typeof el === 'object' &&\r\n /^\\[object (HTMLCollection|NodeList|RadioNodeList|Object)\\]$/.test(stringRepr) &&\r\n (typeof el.length === 'number') &&\r\n (el.length === 0 || (typeof el[0] === 'object' && el[0].nodeType > 0))\r\n}\r\n\r\nexport default IsNodeList\r\n","window.IS_TOUCH_DEVICE = false\r\n\r\n/**\r\n * Check if user's device supports touch\r\n * \r\n * @return {Boolean}\r\n */\r\nwindow.IS_TOUCH_DEVICE = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch\r\n","/* global L */\r\n\r\n// Set mapbox API access token\r\nif (typeof (L) !== 'undefined') {\r\n L.mapbox.accessToken = 'pk.eyJ1IjoiY2xhc3NpY2NvdHRhZ2VzIiwiYSI6ImNpcmJ3djZjNDAwNXNpOW1nbTF3bjIwcjcifQ.wUw-oQaU0UmbS3vsv_e8yQ'\r\n}\r\n\r\nexport function streetLayer(provider = 'osm') {\r\n switch (provider) {\r\n case 'osm':\r\n return L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {\r\n tileSize: 256,\r\n minZoom: 1,\r\n maxZoom: 19,\r\n attribution: 'Map data © OpenStreetMap contributors',\r\n crossOrigin: true,\r\n })\r\n case 'mapbox':\r\n return L.mapbox.styleLayer('mapbox://styles/mapbox/streets-v10')\r\n default:\r\n return null\r\n }\r\n}\r\n\r\nexport function satelliteLayer(provider = 'mapbox') {\r\n switch (provider) {\r\n case 'mapbox':\r\n return L.mapbox.styleLayer('mapbox://styles/mapbox/satellite-streets-v10')\r\n default:\r\n return null\r\n }\r\n}\r\n\r\nclass Map {\r\n constructor(elementId, options = {}, customLayers = null) {\r\n this.container = document.getElementById(elementId)\r\n const opts = Object.assign({\r\n zoom: 9,\r\n maxZoom: 15,\r\n minZoom: 7,\r\n }, options)\r\n\r\n const layers = customLayers || {\r\n Streets: streetLayer(),\r\n Satellite: satelliteLayer(),\r\n }\r\n\r\n this.map = L.mapbox.map(elementId, null, opts)\r\n // assign the first layer as default\r\n layers[Object.keys(layers)[0]].addTo(this.map)\r\n\r\n // if there are more than one layers, show the control to\r\n // be able to switch between them\r\n if (Object.keys(layers).length > 1) {\r\n L.control.layers(layers, {}, { position: 'topleft' }).addTo(this.map)\r\n }\r\n\r\n // empty arrays for additional ui elements\r\n this.markers = []\r\n this.shapes = []\r\n this.cluster = null\r\n }\r\n\r\n addMarker(coords, options = {}, popupContent = null) {\r\n const defaults = {\r\n zindex: 1,\r\n }\r\n\r\n const marker = L.marker(coords, Object.assign({}, defaults, options)).addTo(this.map)\r\n this.markers.push(marker)\r\n\r\n if (popupContent !== null) {\r\n marker.bindPopup(popupContent)\r\n } else if (options.title) {\r\n marker.bindPopup(`${options.title}`)\r\n }\r\n\r\n return marker\r\n }\r\n\r\n fitMarkers(options = {}) {\r\n const boundsOpts = Object.assign({\r\n padding: [10, 10],\r\n }, options)\r\n\r\n if (this.markers.length > 0) {\r\n // create a group to hold all shapes\r\n const group = L.featureGroup(this.markers)\r\n // reference this group to retrieve the combined bounds\r\n this.map.fitBounds(group.getBounds(), boundsOpts)\r\n }\r\n\r\n return this\r\n }\r\n\r\n clusterMarkers() {\r\n if (this.cluster === null) {\r\n this.cluster = L.markerClusterGroup({\r\n showCoverageOnHover: false,\r\n maxClusterRadius: 30,\r\n spiderLegPolylineOptions: { weight: 3, color: '#206FA2', opacity: 1 },\r\n spiderfyDistanceMultiplier: 2,\r\n })\r\n this.map.addLayer(this.cluster)\r\n }\r\n\r\n this.markers.forEach((marker) => {\r\n marker.remove()\r\n this.cluster.addLayer(marker)\r\n })\r\n }\r\n\r\n\r\n clearMarkers() {\r\n if (this.cluster !== null) {\r\n this.cluster.clearLayers()\r\n }\r\n\r\n this.markers.forEach((marker) => {\r\n marker.remove()\r\n })\r\n this.markers = []\r\n\r\n return this\r\n }\r\n\r\n addShape(wkt, options = {}) {\r\n const shapeOpts = Object.assign({\r\n weight: 3,\r\n color: '#29abe3',\r\n opacity: 1,\r\n fillColor: '#29abe3',\r\n fillOpacity: 0.5,\r\n }, options)\r\n const coords = this.parseWKT(wkt)\r\n\r\n if (coords !== null) {\r\n const shape = L.polyline(coords, shapeOpts).addTo(this.map)\r\n this.shapes.push(shape)\r\n }\r\n return this\r\n }\r\n\r\n fitShapes(options = {}) {\r\n const boundsOpts = Object.assign({\r\n padding: [10, 10],\r\n }, options)\r\n\r\n if (this.shapes.length > 0) {\r\n // create a group to hold all shapes\r\n const group = L.featureGroup(this.shapes)\r\n // reference this group to retrieve the combined bounds\r\n this.map.fitBounds(group.getBounds(), boundsOpts)\r\n }\r\n return this\r\n }\r\n\r\n clearShapes() {\r\n this.shapes.forEach((shape) => {\r\n shape.remove()\r\n })\r\n this.shapes = []\r\n\r\n return this\r\n }\r\n\r\n /* eslint class-methods-use-this:0 */\r\n parseWKT(wkt = '') {\r\n if (wkt !== '') {\r\n const parameters = wkt.match(/([A-Z ]+)\\([(]?([0-9-. ,]+)\\)[)]?/)\r\n if (parameters) {\r\n const coords = []\r\n const lls = parameters[2].split(',')\r\n lls.forEach((ll) => {\r\n const llarr = ll.trim().split(' ')\r\n coords.push(L.latLng(...llarr.reverse()))\r\n })\r\n return coords\r\n }\r\n }\r\n return null\r\n }\r\n}\r\n\r\nexport default Map\r\n","import Helpers from '../modules/Helpers'\r\n\r\nconst body = document.querySelector('body')\r\nconst header = document.getElementById('header')\r\nconst mobileNavigationIcon = document.getElementById('mobileNavIcon')\r\nconst megaMenus = document.getElementsByClassName('megamenu')\r\nconst megaMenuToggles = document.getElementsByClassName('megamenu-toggle')\r\nconst ownerNavTaggles = document.getElementsByClassName('owners-navigation__toggles')\r\nconst ownerNavMenu = document.getElementsByClassName('owners-navigation__options')\r\n\r\n// Open and close mobile navigation when clicking mobile menu icon\r\nnew Helpers(mobileNavigationIcon).eventListener((e) => {\r\n e.preventDefault()\r\n\r\n new Helpers(mobileNavigationIcon).toggleClass('burger-icon--active')\r\n new Helpers(header).toggleAttribute('aria-expanded')\r\n\r\n new Helpers(body)\r\n .toggleClass('mobile-nav--active')\r\n .removeClass('megamenu--expanded')\r\n\r\n new Helpers(megaMenus).removeClass('megamenu--active')\r\n new Helpers(megaMenuToggles).removeClass('megamenu-toggle--active')\r\n})\r\n\r\nnew Helpers(ownerNavTaggles).eventListener((e) => {\r\n e.preventDefault()\r\n\r\n new Helpers(ownerNavMenu).toggleClass('owners-navigation__options--active')\r\n})\r\n\r\n","import Helpers from '../modules/Helpers'\r\n\r\nconst body = document.querySelector('body')\r\nconst resultsFilterToggle = document.getElementsByClassName('results-mobile-nav__filter')\r\nconst resultsFilterClose = document.getElementsByClassName('results-aside__close')\r\nconst resultFilterApply = document.getElementsByClassName('results-aside__apply-filter')\r\n\r\n// Results filter active classes\r\nconst resultsFilterClass = 'results-aside--active'\r\nconst noScrollClass = 'no-scroll'\r\n\r\n// Show results page mobile 'Filter by'\r\n// navigation when clicking open button.\r\nnew Helpers(resultsFilterToggle).eventListener((e) => {\r\n e.preventDefault()\r\n\r\n new Helpers(body).addClass(\r\n resultsFilterClass,\r\n noScrollClass,\r\n )\r\n})\r\n\r\n// Hide all active results page mobile filters\r\n// when clicking close button.\r\nnew Helpers(resultsFilterClose).eventListener((e) => {\r\n e.preventDefault()\r\n\r\n new Helpers(body).removeClass(\r\n resultsFilterClass,\r\n noScrollClass,\r\n )\r\n})\r\n\r\n// Hide mobile filters and return to search\r\n// when clicking apply\r\nnew Helpers(resultFilterApply).eventListener((e) => {\r\n e.preventDefault()\r\n\r\n new Helpers(body).removeClass(\r\n resultsFilterClass,\r\n noScrollClass,\r\n )\r\n})\r\n\r\n","import Helpers from '../modules/Helpers'\r\nimport Reveal from '../modules/Reveal'\r\n\r\nconst body = document.querySelector('body')\r\nconst modals = document.getElementsByClassName('modal')\r\nconst modalContent = document.getElementsByClassName('modal__content')\r\nconst modalOpen = document.getElementsByClassName('modal-open')\r\nconst modalClose = document.querySelectorAll('.modal, .modal-close')\r\nconst noScrollClass = 'no-scroll'\r\n\r\n// Modal active class\r\nconst activeClass = 'modal--active'\r\n\r\n\r\n// Listen to all modal toggle buttons in order \r\n// to open corresponding modal.\r\nnew Helpers(document.getElementsByTagName('body')).delegateEventListener('.modal-open', (e) => {\r\n let el = e.target\r\n if (!new Helpers(el).hasClass('modal-open')) {\r\n el = new Helpers(el).getClosest('.modal-open')\r\n }\r\n\r\n const modalId = el.getAttribute('aria-controls')\r\n const modal = document.getElementById(modalId)\r\n \r\n // condition for modals with \"modal-processing\" class\r\n // only open them if the form is valid and in a submittable state\r\n if (new Helpers(modal).hasClass('modal-processing')) {\r\n const form = new Helpers(e.target).getClosest('form')\r\n if (!form.checkValidity()) {\r\n return\r\n }\r\n }\r\n\r\n new Reveal(modal).show(activeClass)\r\n new Helpers(body).addClass(noScrollClass)\r\n\r\n // if this is a standard modal, prevent the original click event from proceeding\r\n // otherwise continue, this has been added to accomodate loading modals that have the class .modal-processing\r\n if (new Helpers(modal).hasClass('modal')) {\r\n e.preventDefault()\r\n }\r\n\r\n const modalType = el.getAttribute('data-modal-type')\r\n const modalValue = el.getAttribute('data-modal-value')\r\n if (modalType !== null) {\r\n window.dataLayerQueue = window.dataLayerQueue || []\r\n window.dataLayerQueue.push({ 'event': 'modalOpen', 'modalType': modalType, 'modalValue': modalValue })\r\n }\r\n\r\n})\r\n\r\n\r\n// Listen to all modal toggle buttons in order \r\n// to close corresponding modal.\r\nnew Helpers(document.getElementsByTagName('body')).delegateEventListener('.modal, .modal-close', (e) => {\r\n if (new Helpers(e.target).getClosest('.modal-close') || !new Helpers(e.target).getClosest('.modal__content')) {\r\n new Reveal(modals).hide(activeClass)\r\n new Helpers(body).removeClass(noScrollClass)\r\n }\r\n})\r\n\r\n","import Helpers from '../modules/Helpers'\r\n\r\nclass NRequired {\r\n constructor(group) {\r\n this.group = group\r\n\r\n this.requiredCount = parseInt(group.getAttribute(\"data-required-count\") || 0)\r\n this.inputs = Array.from(group.getElementsByTagName('input'))\r\n\r\n if (this.inputs[0].setCustomValidity === undefined) {\r\n // can't use this here, the browser doesn't support it\r\n return\r\n }\r\n\r\n this.setValidity()\r\n this.setListener()\r\n }\r\n\r\n populatedCount() {\r\n return this.inputs.filter(i => i.value.length > 0).length\r\n }\r\n\r\n setValidity() {\r\n // reset\r\n this.clearValidity()\r\n // if set count less than required specify invalidity\r\n if (this.populatedCount() < this.requiredCount) {\r\n this.inputs.forEach((input) => {\r\n if (input.value.length == 0) {\r\n input.setCustomValidity('At least ' + this.requiredCount + ' must be provided')\r\n }\r\n })\r\n }\r\n }\r\n\r\n clearValidity() {\r\n this.inputs.forEach((input) => {\r\n input.setCustomValidity('')\r\n })\r\n }\r\n\r\n setListener() {\r\n let self = this\r\n\r\n this.inputs.forEach((input) => {\r\n new Helpers(input).eventListener((e) => {\r\n self.setValidity()\r\n }, 'change')\r\n })\r\n }\r\n}\r\n\r\nexport default NRequired","import Helpers from './Helpers'\r\nimport Reveal from '../modules/Reveal'\r\nimport XhrPromise from 'xhr-promise'\r\n\r\n\r\n// Request\r\nconst sendEmailToNewsletterSignUp = (e, signUpContainer, formObject) => {\r\n e.preventDefault();\r\n\r\n const xhr = new XhrPromise();\r\n const { emailField, formUI, errorUI, successUI } = formObject;\r\n const emailAddress = emailField.value;\r\n\r\n xhr.send({\r\n url: '/ajax/newsletter.aspx',\r\n method: 'POST',\r\n data: `email=${emailAddress}`\r\n })\r\n .then((response) => {\r\n return response.responseText.length > 0 ? JSON.parse(response.responseText) : {};\r\n })\r\n .then((data) => {\r\n formUI.setAttribute('aria-hidden', 'true');\r\n errorUI.setAttribute('aria-hidden', data.error ? 'false' : 'true');\r\n successUI.setAttribute('aria-hidden', data.error ? 'true' : 'false');\r\n\r\n if (!data.error) {\r\n setTimeout(() => new Helpers(signUpContainer).removeClass('modal--active'), 6000);\r\n }\r\n });\r\n};\r\n\r\n// Create Sign Up Object\r\nconst createSignUpFormObject = container => {\r\n return {\r\n formUI: container.getElementsByClassName('sign-up')[0],\r\n successUI: container.getElementsByClassName('newsletter__success')[0],\r\n errorUI: container.getElementsByClassName('newsletter__error')[0],\r\n submitButton: container.getElementsByTagName('button')[0],\r\n emailField: container.getElementsByTagName('input')[0],\r\n };\r\n};\r\n\r\n// Footer sign up\r\nconst newsletterFooterSignUp = document.getElementById('footer-newsletter-sign-up');\r\nif (newsletterFooterSignUp) {\r\n const formObject = createSignUpFormObject(newsletterFooterSignUp);\r\n new Helpers(formObject.formUI.getElementsByTagName('form')).eventListener((e) => {\r\n sendEmailToNewsletterSignUp(e, newsletterFooterSignUp, formObject);\r\n }, 'submit');\r\n}\r\n\r\n// Modal sign Up\r\nconst newsletterModalSignUp = document.getElementById('modal-newsletter-sign-up');\r\nif (newsletterModalSignUp) {\r\n const formObject = createSignUpFormObject(newsletterModalSignUp);\r\n new Helpers(formObject.formUI.getElementsByTagName('form')).eventListener((e) => {\r\n sendEmailToNewsletterSignUp(e, newsletterModalSignUp, formObject);\r\n }, 'submit');\r\n}\r\n\r\n\r\n\r\n// Get the query parameters and check if we want to load the page with the newsletter sign up modal open\r\nif (newsletterModalSignUp) {\r\n const queryParams = new URLSearchParams(window.location.search);\r\n const paramName = 'newsletter';\r\n if (newsletterModalSignUp && queryParams.has(paramName)) {\r\n const activeClass = 'modal--active';\r\n new Reveal(newsletterModalSignUp).show(activeClass);\r\n }\r\n}\r\n","import Helpers from '../modules/Helpers'\r\nimport Map, { streetLayer } from '../modules/Map'\r\n\r\nif (document.getElementsByClassName('owners-contact').length) {\r\n \r\n new Helpers(document.getElementById('showMap')).eventListener((e) => {\r\n if (this.map === undefined) {\r\n /**\r\n * Initialise the map\r\n * use a small timeout to ensure the panel is visible first\r\n **/\r\n setTimeout(() => {\r\n this.map = new Map('map', {\r\n center: [50.72, -3.53], zoom: 7,\r\n }, {\r\n 'Streets': streetLayer(),\r\n })\r\n\r\n /**\r\n * Add marker to the map, where the user clicked and set a hidden value\r\n **/\r\n this.map.map.on('click', (data) => {\r\n this.map.clearMarkers()\r\n this.map.addMarker(data.latlng)\r\n document.getElementsByClassName('owners-contact__latlng')[0].value = data.latlng.lat + \",\" + data.latlng.lng\r\n })\r\n\r\n /**\r\n * Remove the marker icon (and clear the hidden value)\r\n **/\r\n new Helpers(document.getElementById('clearMarker')).eventListener((e) => {\r\n e.preventDefault()\r\n this.map.clearMarkers()\r\n document.getElementsByClassName('owners-contact__latlng')[0].value = ''\r\n })\r\n\r\n\r\n }, 100)\r\n }\r\n })\r\n \r\n}","import imageMapResize from 'image-map-resizer'\r\nimport Map from '../modules/Map'\r\nimport Helpers from '../modules/Helpers'\r\n\r\nif (document.getElementsByClassName('owners-location__map').length) {\r\n const map = new Map('map', { center: [50.102, -5.275], zoom: 12 })\r\n const wkt = document.getElementById('map').getAttribute('data-shape')\r\n map.addShape(wkt)\r\n map.fitShapes()\r\n}\r\n\r\nconst imageEl = document.getElementById('owners-cms-block-map__image')\r\nif (typeof (imageEl) !== 'undefined' && imageEl !== null) {\r\n imageMapResize()\r\n\r\n const imageSrc = imageEl.src\r\n const regions = {\r\n 'owners-cms-block-map__cornwall': '/media/owners/02-Cornwall.jpg',\r\n 'owners-cms-block-map__devon': '/media/owners/03-Devon.jpg',\r\n 'owners-cms-block-map__somerset': '/media/owners/04-Somerset.jpg',\r\n 'owners-cms-block-map__dorset': '/media/owners/05-Dorset.jpg',\r\n 'owners-cms-block-map__hampshire': '/media/owners/06-Hampshire.jpg',\r\n 'owners-cms-block-map__sussex': '/media/owners/09_Sussex_Kent.jpg',\r\n 'owners-cms-block-map__isle-of-wight': '/media/owners/08-Isle-Of-Wight.jpg',\r\n 'owners-cms-block-map__pembrokeshire': '/media/owners/09-pembrokeshire.jpg',\r\n 'owners-cms-block-map__cotswolds': '/media/owners/03-Cotswolds.jpg'\r\n }\r\n\r\n Object.keys(regions).forEach((selector) => {\r\n const regionArea = document.getElementById(selector)\r\n const regionImage = regions[selector]\r\n\r\n new Helpers(regionArea).eventListener((e) => {\r\n imageEl.src = regionImage\r\n }, 'mouseover')\r\n\r\n new Helpers(regionArea).eventListener((e) => {\r\n imageEl.src = imageSrc\r\n }, 'mouseout')\r\n })\r\n}\r\n","import XHRPromise from 'xhr-promise'\r\nimport moment from 'moment'\r\n\r\n(function () {\r\n let activeFetch = -1\r\n const daySelector = document.getElementById('daySelector') \r\n const urlParams = new URLSearchParams(window.location.search);\r\n const bookingId = urlParams.get('brn');\r\n\r\n if (daySelector != null) {\r\n daySelector.addEventListener(\"change\", () => {\r\n const hidSelectedDay = document.getElementById('hidSelectedDay')\r\n hidSelectedDay.value = daySelector.value\r\n getPaymentSchedule(bookingId, daySelector.value)\r\n })\r\n\r\n\r\n let getPaymentSchedule = async (bookingID, dayOfMonth) => {\r\n const feedEndpoint = `/feeds/paymentScheduleFeed.aspx?bookingID=${bookingID}&dayOfMonth=${dayOfMonth}`\r\n\r\n try {\r\n if (activeFetch !== -1) {\r\n activeFetch.getXHR().abort()\r\n }\r\n\r\n activeFetch = new XHRPromise()\r\n const response = await activeFetch.send({\r\n url: feedEndpoint,\r\n })\r\n const data = response.responseText\r\n updateUI(data)\r\n activeFetch = -1\r\n } catch (error) {\r\n if (error.reason !== 'abort') {\r\n console.log(error)\r\n }\r\n }\r\n };\r\n\r\n let updateUI = (data) => {\r\n\r\n let finalPayment = data.Table[data.Table.length - 1].Amount\r\n\r\n // Filter the table to only include rows with PaymentId as null\r\n const filteredTable = data.Table.filter(row => row.PaymentId === null)\r\n\r\n let noOfPayments = filteredTable.length\r\n let monthlyPayments = filteredTable[0].Amount\r\n\r\n const paymentSummary = document.getElementById('paymentSummary')\r\n\r\n if (monthlyPayments === finalPayment) {\r\n paymentSummary.innerText = `You will be charged ${formatCurrency(monthlyPayments)} for ${noOfPayments} months.`\r\n } else {\r\n noOfPayments--; // Remove the final payment from the count\r\n paymentSummary.innerText = `You will be charged ${formatCurrency(monthlyPayments)} for ${noOfPayments} months and then a final payment of ${formatCurrency(finalPayment)}.`\r\n }\r\n }\r\n\r\n // Function to format number as currency\r\n const formatCurrency = (amount) => {\r\n return Number(amount).toLocaleString('en-GB', {\r\n style: 'currency',\r\n currency: 'GBP'\r\n })\r\n }\r\n\r\n getPaymentSchedule(bookingId, 1)\r\n } \r\n})()\r\n\r\n","import Helpers from '../modules/Helpers'\r\nimport Reveal from '../modules/Reveal'\r\n\r\n\r\nvar StarReviewField = document.getElementsByClassName('StarReviewField')\r\nvar stars = document.getElementsByClassName('star')\r\nvar submit = document.getElementsByClassName('questionnaire__buttons-submit')\r\n\r\n//set the review stars from db\r\n\r\nfor (var i = 0; i < stars.length; i++) {\r\n var starNum = stars[i].getAttribute('data-starNumber')\r\n if (starNum <= StarReviewField[0].value) {\r\n new Helpers(stars[i]).addClass('star--rated')\r\n }\r\n}\r\n\r\nnew Helpers(stars).eventListener((e) => {\r\n new Helpers(stars).removeClass('star--rated')\r\n var StarClicked = e.target\r\n\r\n for (var i = 0; i < StarClicked.getAttribute('data-starNumber'); i++) {\r\n new Helpers(stars[i]).addClass('star--rated')\r\n }\r\n StarReviewField[0].value = StarClicked.getAttribute('data-starNumber')\r\n})\r\n\r\n\r\nvar quest = { unAnsweredQuestions: 0, submitCount: 0 }\r\nquest.validationMessage = document.getElementById('questionnaire__validation-message')\r\n\r\n//Validate responses as essential fields on version 2:\r\nif (quest.validationMessage !== null){\r\n var submitButton = document.getElementsByClassName('questionnaire__buttons-submit')\r\n var form = new Helpers(submitButton).getClosest('form')\r\n form.checkValidity = function () {\r\n return quest.checkOnComplete()\r\n }\r\n\r\n var answers = document.getElementsByTagName(\"input\")\r\n new Helpers(answers).eventListener((e) => {\r\n quest.trackUnanswered()\r\n })\r\n}\r\n\r\n\r\nquest.checkOnComplete = function () {\r\n quest.submitCount++\r\n quest.trackUnanswered()\r\n return !(quest.unAnsweredQuestions > 0)\r\n}\r\n\r\nquest.trackUnanswered = function () {\r\n\r\n if (quest.validationMessage !== null) {\r\n quest.checkQuestions()\r\n if (quest.unAnsweredQuestions > 0) {\r\n quest.validationMessage.innerText = 'Please complete the ' + quest.unAnsweredQuestions.toString() + ' unanswered question' + (quest.unAnsweredQuestions > 1 ? 's' : '') + ' above before pressing submit.'\r\n if (quest.submitCount > 0) {\r\n new Helpers(quest.validationMessage).addClass('incomplete')\r\n }\r\n } else {\r\n quest.validationMessage.innerText = ''\r\n }\r\n }\r\n}\r\n\r\nquest.checkAQuestion = function (rdoGroupName, rdoLabelId) {\r\n\r\n rdoLabelId = (typeof rdoLabelId === 'undefined') ? rdoGroupName : rdoLabelId\r\n var rdoLabel = document.getElementById(rdoLabelId)\r\n\r\n if (rdoLabel !== null) {\r\n try {\r\n if (quest.RDONull(rdoGroupName)) {\r\n quest.unAnsweredQuestions++\r\n if (quest.submitCount > 0) {\r\n rdoLabel.classList.add('incomplete')\r\n }\r\n } else {\r\n rdoLabel.classList.remove('incomplete')\r\n }\r\n }\r\n catch (e) {\r\n console.log(e)\r\n rdoLabel.classList.remove('incomplete')\r\n }\r\n }\r\n}\r\n\r\nquest.checkQuestions = function () {\r\n quest.unAnsweredQuestions = 0\r\n quest.checkAQuestion('qstDescription')\r\n quest.checkAQuestion('qstBooking')\r\n quest.checkAQuestion('qstRD')\r\n quest.checkAQuestion('qstStaff')\r\n quest.checkAQuestion('nps','nps-label')\r\n quest.checkAQuestion('qstCleanliness')\r\n quest.checkAQuestion('qstLocation')\r\n quest.checkAQuestion('qstComfort')\r\n quest.checkAQuestion('qstDFE')\r\n quest.checkAQuestion('qstInventory')\r\n quest.checkAQuestion('qstValue')\r\n quest.checkAQuestion('qstExpectation')\r\n //quest.checkAQuestion('rdoReccommend', 'qstReccommend')\r\n\r\n}\r\n\r\n\r\nquest.RDONull = function (RDOGroupName) {\r\n var ctls = document.getElementById(RDOGroupName).getElementsByTagName(\"input\")\r\n for (var i = 0; i < ctls.length; i++) {\r\n if (ctls[i].checked) {\r\n return false\r\n }\r\n }\r\n return true;\r\n}\r","import Helpers from '../modules/Helpers'\r\nimport isBrowserIE10 from '../modules/IsBrowserIE10'\r\n\r\nconst rangerSliders = document.getElementsByClassName('ranger-slider')\r\n\r\n// Ensure correct event is used in IE 10 as\r\n// 'input' event is buggy in IE 10\r\nconst event = isBrowserIE10() ? 'change' : 'input'\r\n\r\n// When a slider inputs value is changed by sliding,\r\n// change the corresponding output to show user\r\n// their current selection.\r\nnew Helpers(rangerSliders).eventListener((e) => {\r\n e.preventDefault()\r\n\r\n const rangeSliderOutputId = e.target.getAttribute('aria-controls')\r\n const rangeSliderOutput = document.getElementById(rangeSliderOutputId)\r\n\r\n // When the value of the slider is greater the 1,\r\n // update the iner HTML of the corresponding output\r\n // element to reflect their selection.\r\n if (parseFloat(e.target.value) == 1) {\r\n rangeSliderOutput.innerHTML = `1 mile`\r\n } else if (parseFloat(e.target.value) > 0) {\r\n rangeSliderOutput.innerHTML = `${e.target.value} miles`\r\n } else {\r\n rangeSliderOutput.innerHTML = 'This area only'\r\n }\r\n}, event)\r\n\r\nnew Helpers(rangerSliders).eventListener((e) => {\r\n const rangeSliderOutputId = e.target.getAttribute('aria-controls')\r\n const rangeSliderOutput = document.getElementById(rangeSliderOutputId)\r\n\r\n // When the value of the slider is greater the 1,\r\n // update the iner HTML of the corresponding output\r\n // element to reflect their selection.\r\n if (parseFloat(e.target.value) == 1) {\r\n rangeSliderOutput.innerHTML = `1 mile`\r\n } else if (parseFloat(e.target.value) > 0) {\r\n rangeSliderOutput.innerHTML = `${e.target.value} miles`\r\n } else {\r\n rangeSliderOutput.innerHTML = 'This area only'\r\n }\r\n}, 'sync')\r\n","import he from 'he'\r\nimport moment from 'moment'\r\nimport XHRPromise from 'xhr-promise'\r\nimport './vendor/History'\r\nimport Helpers from './Helpers'\r\nimport Locations from './results/Results.Location'\r\nimport Facets from './results/Results.Facets'\r\nimport Party from './results/Results.Party'\r\nimport Dates from './results/Results.Dates'\r\nimport UI from './results/Results.UI'\r\nimport Header from './results/Results.Header'\r\nimport Pagination from './results/Results.Pagination'\r\nimport Map from './results/Results.Map'\r\nimport Sorting from './results/Results.Sorting'\r\nimport Slider from '../modules/Slider'\r\nimport Tracking from './results/Results.Tracking'\r\nimport LastMinute from './results/Results.LastMinute'\r\n\r\nconst History = window.History\r\n\r\nconst searchControl = document.getElementsByClassName('results-aside')[0]\r\n\r\nclass Results {\r\n constructor() {\r\n\r\n this.resultsList = document.getElementsByClassName('results-list__items')[0]\r\n this.featuredSearchId = parseInt(document.getElementsByName('FeaturedSearchID')[0].value, 10)\r\n this.locations = new Locations(this.changeEvent, this.regionChangeEvent)\r\n this.facets = new Facets(this.changeEvent)\r\n this.party = new Party(this.changeEvent)\r\n this.dates = new Dates(this.changeEvent)\r\n this.pages = new Pagination(this.changeEvent)\r\n this.sorting = new Sorting(this.changeEvent)\r\n this.tracking = new Tracking()\r\n this.activeFetch = -1\r\n this.stateAction = ''\r\n this.map = new Map(() => {\r\n const qs = this.getNonDefaultValues()\r\n return this.buildQueryString(qs)\r\n })\r\n\r\n this.ui = new UI(this.showMoreEvent, this.extendEvent)\r\n this.lastMinute = new LastMinute(this)\r\n this.header = new Header()\r\n\r\n this.listeners()\r\n this.initState()\r\n\r\n const options = {\r\n threshold: 0.4\r\n }\r\n\r\n this.resultsItemObserver = new IntersectionObserver((entries) => {\r\n entries.forEach((entry) => {\r\n if (entry.isIntersecting || entry.intersectionRatio > 0) {\r\n const item = entry.target\r\n let position = 0;\r\n const resultsItems = document.querySelectorAll('.results-list__item')\r\n let params = this.getNonDefaultValues()\r\n let queryString = this.buildQueryString(params)\r\n if (item.classList.contains('results-list__item') && item.attributes.getNamedItem('data-logged') === null) {\r\n const cottageCode = item.getAttribute('data-code').substring(1)\r\n //if the item in view is a complex, log that the complex collection has been seen and also the individual cottages of the complex\r\n if (item.classList.contains('results-complex')) {\r\n const complexCode = item.getAttribute('data-complexCode')\r\n resultsItems.forEach((resultsItem, i) => {\r\n if (resultsItem.getAttribute('data-complexCode') === item.getAttribute('data-complexCode')) {\r\n position = i + 1\r\n }\r\n })\r\n let complexCottages = []\r\n item.querySelectorAll('.slider__item').forEach((c) => {\r\n complexCottages.push(c.getAttribute('data-code').substring(1))\r\n })\r\n let complexCodes = JSON.stringify(complexCottages)\r\n let complex = { 'complexCode': complexCode, 'complexCodes': complexCodes, 'position': position, 'queryString': queryString, 'params': this.tracking.searchQsToJSON(params) }\r\n this.tracking.trackComplexResultViewed(complex)\r\n item.setAttribute('data-logged', 'true')\r\n } else {\r\n resultsItems.forEach((resultsItem, i) => {\r\n if (resultsItem.getAttribute('data-code') === item.getAttribute('data-code')) {\r\n position = i + 1\r\n }\r\n })\r\n ////log a cottage view when the results item has been seen\r\n let cottage = { 'cottageCode': cottageCode, 'position': position, 'queryString': queryString, 'params': this.tracking.searchQsToJSON(params) }\r\n this.tracking.trackResultsItemViewed(cottage)\r\n item.setAttribute('data-logged', 'true')\r\n }\r\n }\r\n }\r\n })\r\n }, options)\r\n }\r\n\r\n listeners() {\r\n // control change events\r\n\r\n new Helpers(window).eventListener((e) => {\r\n const resultsItems = [].slice.call(document.querySelectorAll('.results-list__item'))\r\n resultsItems.forEach((item) => {\r\n this.resultsItemObserver.observe(item)\r\n })\r\n }, 'load')\r\n\r\n new Helpers(searchControl).eventListener((e) => {\r\n if (e.detail.control !== 'region' && e.detail.control !== 'page' && e.detail.control !== 'sorting') {\r\n this.featuredSearchId = 0\r\n }\r\n this.changeSearch(e.detail.control === 'page')\r\n }, 'search-change')\r\n\r\n new Helpers(searchControl).eventListener(() => {\r\n this.showMore()\r\n }, 'search-more')\r\n\r\n new Helpers(searchControl).eventListener((e) => {\r\n this.extendSearch(e.detail.params)\r\n }, 'search-extend')\r\n\r\n new Helpers(searchControl).eventListener(() => {\r\n this.updateState(this.currentUrl())\r\n }, 'search-map')\r\n\r\n // history navigation\r\n History.Adapter.bind(window, 'statechange', () => {\r\n const historyState = History.getState();\r\n const state = historyState.data;\r\n if (this.stateAction !== 'replace' && state !== undefined) { \r\n this.updateControls(state);\r\n let options = this.lastMinute.valid ? { ...this.lastMinute.createUpdateSearchOptions(), url: historyState.url } : { ...this.setDefaultSearchOptions(), url: historyState.url };\r\n this.updateSearch(this.stateAction, options);\r\n if (this.lastMinute.valid) this.lastMinute.setSelectedDateRangeButton(this.lastMinute.lastMinuteButtons?.[state.lmbtn ?? 0]);\r\n // reset this flag\r\n this.stateAction = '';\r\n }\r\n })\r\n }\r\n\r\n // raise event to trigger search update - passed to child search control objects\r\n changeEvent(control) {\r\n // raise event to be picked up by the root change controller\r\n searchControl.dispatchEvent(new CustomEvent('search-change', { detail: { control } }))\r\n return this\r\n }\r\n\r\n showMoreEvent() {\r\n // raise event to be picked up by the root change controller\r\n searchControl.dispatchEvent(new CustomEvent('search-more'))\r\n return this\r\n }\r\n\r\n extendEvent(params) {\r\n searchControl.dispatchEvent(new CustomEvent('search-extend', {\r\n detail: { params },\r\n }))\r\n return this\r\n }\r\n\r\n // fetch all querystring params from independant controls\r\n getValues() {\r\n let values = Object.assign({},\r\n this.locations.getValues(),\r\n this.facets.getValues(),\r\n this.party.getValues(),\r\n this.dates.getValues(),\r\n this.pages.getValues(),\r\n this.map.getValues(),\r\n this.sorting.getValues(),\r\n\r\n {\r\n fsi: this.featuredSearchId,\r\n lmbtn: this.lastMinute.valid ? this.lastMinute.getCurrentIndexOfHighlightedButton() : null\r\n })\r\n\r\n return values\r\n }\r\n\r\n // fetch non-default querystring params from independant controls\r\n getNonDefaultValues() {\r\n const values = Object.assign({},\r\n this.locations.getNonDefaultValues(),\r\n //this.locations.getTownDefaultDistance(),\r\n this.facets.getNonDefaultValues(),\r\n this.party.getNonDefaultValues(),\r\n this.dates.getNonDefaultValues(),\r\n this.pages.getNonDefaultValues(),\r\n this.map.getNonDefaultValues(),\r\n this.sorting.getNonDefaultValues()\r\n )\r\n\r\n if (this.featuredSearchId !== 0) {\r\n values.fsi = this.featuredSearchId\r\n }\r\n\r\n return values\r\n }\r\n\r\n isCustomizedSearch() {\r\n const values = Object.assign({},\r\n this.facets.getNonDefaultValues(),\r\n this.party.getNonDefaultValues(),\r\n this.dates.getNonDefaultValues(),\r\n this.map.getNonDefaultValues(),\r\n this.sorting.getNonDefaultValues(),\r\n )\r\n\r\n const loc = this.locations.getNonDefaultValues()\r\n const pages = this.pages.getValues()\r\n\r\n const result = (Object.keys(values).length === 0 && (loc.nlc === undefined || loc.rgn === 'UK' || loc.rgn === '') && pages.pMax === pages.pMin && loc.nlm === undefined)\r\n return !result\r\n }\r\n\r\n // utility to turn search state parameters object into a querystring\r\n buildQueryString(values) {\r\n return Object\r\n .keys(values)\r\n .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(values[key])}`)\r\n .join('&')\r\n }\r\n\r\n // utility to return current search state parameters object from querystring\r\n readQueryString() {\r\n const searchParams = window.location.href.split('?')[1]\r\n const result = {}\r\n //in case the queryString is empty\r\n if (searchParams !== undefined) {\r\n const paramParts = searchParams.split('&')\r\n for (let part of paramParts) {\r\n const paramValuePair = part.split('=')\r\n //exclude the case when the param has no value\r\n if (paramValuePair.length === 2) {\r\n result[paramValuePair[0]] = decodeURIComponent(paramValuePair[1])\r\n }\r\n }\r\n }\r\n return result\r\n }\r\n\r\n initState() {\r\n // replace current state with one that also contains the control state object\r\n const currentUrl = window.location.href.replace(window.location.protocol + '//' + window.location.hostname, '')\r\n const initialControlValues = this.getValues()\r\n\r\n // naff workaround for history.js - set flag to prevent ajax fetches\r\n // when we're just trying to modify current state\r\n this.stateAction = 'replace'\r\n History.replaceState(initialControlValues, document.title, currentUrl)\r\n this.stateAction = ''\r\n // ensure UI consistency by setting the controls from their read state\r\n this.updateControls(initialControlValues)\r\n new Helpers(window).eventListener((e) => {\r\n const resultsItems = [].slice.call(document.querySelectorAll('.results-list__item'))\r\n resultsItems.forEach((item) => {\r\n this.resultsItemObserver.observe(item)\r\n })\r\n }, 'load')\r\n\r\n // log cottage impressions of starting cottages\r\n Array.from(document.getElementsByClassName('results-list__item')).forEach((item, position) => {\r\n const cottageCode = item.getAttribute('data-code').substring(1)\r\n // track impression through GTM\r\n window.dataLayerQueue.push({ event: 'cottageImpression', cottageCode, position })\r\n })\r\n }\r\n\r\n // Push a new search state into history\r\n changeSearch(persistPagination = false) {\r\n // Reset pagination for a new search\r\n if (!persistPagination) {\r\n this.pages.setValues({})\r\n }\r\n let qs = this.getNonDefaultValues()\r\n const state = this.getValues()\r\n this.stateAction = 'push'\r\n History.pushState(state, document.title, this.currentUrl(qs))\r\n this.stateAction = ''\r\n\r\n // Store search dates for use on cottage availability form.\r\n try {\r\n const dates = {\r\n dss: moment(state.dss, 'ddd D MMM YY').toISOString(true),\r\n des: moment(state.des, 'ddd D MMM YY').toISOString(true),\r\n nday: state.nday,\r\n }\r\n sessionStorage.setItem('searchDates', JSON.stringify(dates))\r\n } catch (e) {\r\n console.log(e)\r\n }\r\n }\r\n\r\n currentUrl(nonDefaultParams = null) {\r\n //get params\r\n const qs = nonDefaultParams || this.getNonDefaultValues()\r\n // build a url appropriate for the current search\r\n let url = ''\r\n if ('fsi' in qs) {\r\n // featured search is set, therefore the search has yet to be modified\r\n // & a serverside built region url can be used\r\n url = this.locations.getRegionHref(this.pages.page())\r\n if ('sorting' in qs) {\r\n url += '?sorting=' + qs.sorting\r\n }\r\n } else if (!this.isCustomizedSearch()) {\r\n // not customised, excepting location. Use the location url\r\n url = this.locations.getLocationUrl(this.pages.page())\r\n } else {\r\n\r\n url = `/results.aspx?search=Y&${this.buildQueryString(qs)}`\r\n }\r\n\r\n return url\r\n }\r\n\r\n extendSearch(params) {\r\n // get current state\r\n const state = History.getState().data\r\n // change values\r\n Object.assign(state, params)\r\n // modify controls\r\n this.updateControls(state)\r\n\r\n // navigate to the top of the results list\r\n document.querySelector('.results-output').scrollIntoView({ behavior: 'smooth' })\r\n\r\n // update list\r\n this.changeSearch()\r\n }\r\n\r\n // without fetching new data, just update the state to reflect current control values\r\n updateState(url) {\r\n if (url === null) url = this.currentUrl();\r\n // naff workaround for history.js - set flag to prevent ajax fetches\r\n // when we're just trying to modify current state\r\n this.stateAction = 'replace'\r\n History.replaceState(this.getValues(), document.title, url)\r\n this.stateAction = ''\r\n }\r\n\r\n\r\n setDefaultSearchOptions = () => {\r\n return {\r\n feedEndpoint: this.getFeedEndpoint(),\r\n updateMetaTitle: true,\r\n preservePageDescription: false,\r\n preservePageHeader: false,\r\n url: this.currentUrl()\r\n };\r\n };\r\n\r\n getFeedEndpoint = () => {\r\n const nonDefaultValues = this.getNonDefaultValues(); // Define qs here\r\n let feedEndpoint = `/feeds/resultsFeedHtml.aspx?rpp=10&${this.buildQueryString(nonDefaultValues)}`;\r\n if (this.isCustomizedSearch()) {\r\n feedEndpoint += '&search=Y';\r\n }\r\n return feedEndpoint;\r\n };\r\n\r\n // Fetch results from feed with the current search state\r\n updateSearch(action, options) {\r\n options = options || this.setDefaultSearchOptions();\r\n const { feedEndpoint, updateMetaTitle, preservePageDescription, preservePageHeader, url } = options;\r\n const self = this;\r\n const qs = this.getNonDefaultValues();\r\n this.ui.loading();\r\n\r\n if (this.activeFetch !== -1) {\r\n this.activeFetch.getXHR().abort()\r\n }\r\n // ajax fetch new results\r\n this.activeFetch = new XHRPromise()\r\n this.activeFetch.send({ url: feedEndpoint })\r\n .then(response => (new window.DOMParser()).parseFromString(response.responseText, 'text/xml'))\r\n .then((data) => {\r\n const regions = data.getElementsByTagName('regionTotals')[0]\r\n const description = data.getElementsByTagName('description')[0]\r\n const pageTitle = description.getAttribute('pageTitle')\r\n const sortOrder = data.getElementsByTagName('SortOrder')[0]\r\n this.sorting.updateSortControl(sortOrder)\r\n const params = Object.assign({\r\n totalPages: parseInt(description.getAttribute('maxPages'), 10),\r\n totalCount: parseInt(description.getAttribute('propertyCount'), 10),\r\n title: description.getAttribute('resultsTitle'),\r\n warning: description.getAttribute('warning'),\r\n totalWithoutOfferFilter: parseInt(description.getAttribute('totalWithoutOfferFilter'), 10),\r\n regionName: description.getAttribute('regionName'),\r\n }, History.getState().data)\r\n\r\n if (data.getElementsByTagName('location').length > 0) {\r\n params.locationName = data.getElementsByTagName('location')[0].getAttribute('name')\r\n }\r\n\r\n self.ui.updateUI(params)\r\n self.pages.setNoOfPages(params.totalPages)\r\n // Breadcrumbs\r\n self.ui.setBreadcrumbs(data.getElementsByTagName('breadcrumbs')[0])\r\n\r\n // Update region counters\r\n self.locations.setRegionCounts(regions)\r\n\r\n // toggle header state (i.e. remove FS or location header) if apending results then leave header alone :)\r\n const headerXML = data.getElementsByTagName('header');\r\n if (headerXML.length == 0) {\r\n if (!preservePageHeader)\r\n this.header.hide();\r\n } else {\r\n this.header.update(headerXML[0]);\r\n }\r\n\r\n // record a page view\r\n window.dataLayerQueue.push({ event: 'pageView', 'virtualPageURL': History.getState().url.replace(/^.*\\/\\/[^\\/]+/, '') });\r\n this.tracking.trackSearch(qs, params)\r\n\r\n // Update the list of cottages.\r\n this.resultsList.innerHTML = ''\r\n\r\n const listItems = data.querySelectorAll('listItem, listAd')\r\n\r\n Array.from(listItems).forEach((item) => {\r\n switch (item.tagName) {\r\n case 'listItem':\r\n const cottageCode = item.getAttribute('code')\r\n const itemCode = item.getAttribute('type') + cottageCode\r\n const position = parseInt(item.getAttribute('positionInResults'), 10)\r\n const tempWrapper = document.createElement('div')\r\n tempWrapper.innerHTML = he.decode(item.getAttribute('html'))\r\n const resultItemEl = tempWrapper.children[0]\r\n this.resultsList.appendChild(resultItemEl)\r\n\r\n // if its a complex listing, init the slider\r\n if (new Helpers(resultItemEl).hasClass('results-complex')) {\r\n new Slider(resultItemEl.querySelector('.slider'), {\r\n navTemplate: '{index}',\r\n }).activate()\r\n }\r\n this.resultsItemObserver.observe(resultItemEl)\r\n // track impression through GTM\r\n // window.dataLayerQueue.push({ event: 'cottageImpression', cottageCode, position })\r\n break;\r\n\r\n case 'listAd':\r\n if (item.getAttribute('html') !== '') {\r\n const tempWrapper = document.createElement('div')\r\n tempWrapper.innerHTML = he.decode(item.getAttribute('html'))\r\n const adEl = tempWrapper.children[0]\r\n this.resultsList.appendChild(adEl)\r\n }\r\n break;\r\n }\r\n })\r\n\r\n // set the document title for the browser tab\r\n if (updateMetaTitle)\r\n document.title = pageTitle\r\n // highlight the shortlisted cottages\r\n window.sl.HighlightShortlistedCottages()\r\n // clear the ajax fetch indicator\r\n self.activeFetch = -1\r\n\r\n if (document.getElementsByClassName('short-break-links').length > 0) {\r\n if (document.getElementsByClassName('short-break-links')[0].parentElement.getAttribute('aria-hidden') === 'false') {\r\n // if region not west then set then\r\n const currentRegion = self.locations.getRegionForFeaturedSearchLinks()\r\n if (currentRegion !== null) {\r\n const buttons = document.querySelectorAll('.short-break-links__link')\r\n if (currentRegion === '') {\r\n Array.from(buttons).forEach((button) => {\r\n const featuredSearchCode = button.getAttribute('data-fsCode')\r\n button.setAttribute('href', `/${featuredSearchCode}.html`)\r\n })\r\n } else {\r\n Array.from(buttons).forEach((button) => {\r\n const featuredSearchCode = button.getAttribute('data-fsCode')\r\n button.setAttribute('href', `/${featuredSearchCode}${currentRegion}`)\r\n })\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (preservePageDescription && document.querySelector('.results-lead__text')) {\r\n document.querySelector('.results-lead__text').setAttribute('aria-hidden', false);\r\n }\r\n\r\n\r\n // final call to ensure UI <-> state consistency \r\n this.updateState(url);\r\n }).catch((error) => {\r\n // TODO: UI/UX for error handling\r\n if (error.reason !== 'abort') {\r\n console.log(error)\r\n }\r\n })\r\n }\r\n\r\n showMore() {\r\n // if there's a current fetch, escape now\r\n if (this.activeFetch !== -1) return\r\n const self = this\r\n // increment max page\r\n this.pages.showMore()\r\n\r\n // fetch updated qs\r\n const values = this.getNonDefaultValues()\r\n let url = `/results.aspx?${this.buildQueryString(values)}`\r\n // for the sake of \"show more\", pMin should be equal to pMax\r\n // as we're only fetching the delta\r\n values.pMin = values.pMax\r\n let feedEndpoint = `/feeds/resultsFeedHtml.aspx?rpp=10&${this.buildQueryString(values)}`\r\n\r\n if (this.isCustomizedSearch()) {\r\n url += '&search=Y'\r\n feedEndpoint += '&search=Y'\r\n }\r\n\r\n // naff workaround for history.js - set flag to prevent ajax fetches\r\n // when we're just trying to modify current state\r\n this.stateAction = 'replace'\r\n History.replaceState(this.getValues(), document.title, url)\r\n this.stateAction = ''\r\n // init loading anim\r\n this.ui.showMoreLoading(true)\r\n // ajax fetch new results\r\n this.activeFetch = new XHRPromise()\r\n this.activeFetch.send({ url: feedEndpoint })\r\n .then(response => (new window.DOMParser()).parseFromString(response.responseText, 'text/xml'))\r\n .then((data) => {\r\n const description = data.getElementsByTagName('description')[0]\r\n\r\n const params = Object.assign({\r\n totalPages: parseInt(description.getAttribute('maxPages'), 10),\r\n totalCount: parseInt(description.getAttribute('propertyCount'), 10),\r\n title: description.getAttribute('resultsTitle'),\r\n warning: description.getAttribute('warning'),\r\n totalWithoutOfferFilter: parseInt(description.getAttribute('totalWithoutOfferFilter'), 10),\r\n regionName: description.getAttribute('regionName'),\r\n }, History.getState().data)\r\n\r\n if (data.getElementsByTagName('location').length > 0) {\r\n params.locationName = data.getElementsByTagName('location')[0].getAttribute('name')\r\n }\r\n\r\n self.ui.updateUI(params)\r\n self.pages.setNoOfPages(params.totalPages)\r\n\r\n // record a page view\r\n window.dataLayerQueue.push({ event: 'pageView', 'virtualPageURL': History.getState().url.replace(/^.*\\/\\/[^\\/]+/, '') });\r\n\r\n // Update the list of cottages.\r\n const listItems = data.querySelectorAll('listItem, listAd')\r\n Array.from(listItems).forEach((item) => {\r\n if (item.tagName === 'listItem') {\r\n const cottageCode = item.getAttribute('code')\r\n const position = parseInt(item.getAttribute('positionInResults'), 10)\r\n // create the element in a temp wrapper div\r\n const tempWrapper = document.createElement('div')\r\n tempWrapper.innerHTML = he.decode(item.getAttribute('html'))\r\n const resultItemEl = tempWrapper.children[0]\r\n // append it to the results list\r\n this.resultsList.appendChild(resultItemEl)\r\n // if its a complex listing, init the slider\r\n if (new Helpers(resultItemEl).hasClass('results-complex')) {\r\n new Slider(resultItemEl.querySelector('.slider'), {\r\n navTemplate: '{index}',\r\n }).activate()\r\n }\r\n\r\n // track impression through GTM\r\n window.dataLayerQueue.push({ event: 'cottageImpression', cottageCode, position })\r\n\r\n //add new logging here\r\n this.resultsItemObserver.observe(resultItemEl)\r\n\r\n }\r\n if (item.tagName === 'listAd' && item.getAttribute('html') !== '') {\r\n const tempWrapper = document.createElement('div')\r\n tempWrapper.innerHTML = he.decode(item.getAttribute('html'))\r\n const adEl = tempWrapper.children[0]\r\n this.resultsList.appendChild(adEl)\r\n }\r\n })\r\n\r\n // load of \"show more\" complete, hide the loading element\r\n this.ui.showMoreLoading(false)\r\n // highlight the shortlisted cottages\r\n window.sl.HighlightShortlistedCottages()\r\n // clear the ajax fetch indicator\r\n self.activeFetch = -1\r\n }).catch((error) => {\r\n // TODO: UI/UX for error handling\r\n if (error.reason !== 'abort') {\r\n console.log(error)\r\n this.ui.showMoreLoading(false)\r\n this.activeFetch = -1\r\n }\r\n })\r\n }\r\n\r\n // update search controls to reflect current search state in query string\r\n updateControls(values) {\r\n this.locations.setValues(values, window.location.pathname)\r\n this.facets.setValues(values)\r\n this.party.setValues(values)\r\n this.dates.setValues(values)\r\n this.pages.setValues(values)\r\n this.map.setValues(values)\r\n this.sorting.setValues(values)\r\n this.featuredSearchId = values.fsi || 0\r\n\r\n //update share control with current search link\r\n new Helpers(document.getElementsByClassName('social-share'))\r\n .setAttributes({ 'data-link': window.location.href.replace('http://', 'https://') })\r\n }\r\n}\r\n\r\n\r\nif (searchControl !== undefined) {\r\n new Results()\r\n}\r\n","import Helpers from '../modules/Helpers'\r\nimport IsNodeList from './IsNodeList'\r\n\r\nclass Reveal {\r\n constructor(element) {\r\n // Ensure whatever element is passed into the\r\n // constructor is an array, as methods loop\r\n // over every element using `forEach`.\r\n if (Array.isArray(element)) {\r\n this.element = element\r\n } else if (!IsNodeList(element)) {\r\n this.element = [element]\r\n } else {\r\n this.element = Array.from(element)\r\n }\r\n }\r\n\r\n\r\n /**\r\n * 'Show' element by adding class to element\r\n * and setting `aria-expanded` value to 'true'\r\n * \r\n * @param {String} className Class name to add to element\r\n * @return {Object}\r\n */\r\n show(className) {\r\n this.element.forEach((element) => {\r\n new Helpers(element).addClass(className)\r\n element.setAttribute('aria-expanded', 'true')\r\n })\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * 'Hide' element by removing class on element\r\n * and setting `aria-expanded` value to 'false'\r\n * \r\n * @param {String} className Class name to remove from element\r\n * @return {Object}\r\n */\r\n hide(className) {\r\n this.element.forEach((element) => {\r\n new Helpers(element).removeClass(className)\r\n element.setAttribute('aria-expanded', 'false')\r\n })\r\n\r\n return this\r\n }\r\n\r\n /**\r\n * 'Toggle' element by adding and removing class on element\r\n * and changing `aria-expanded` value to either 'true' or\r\n * 'false' depending on current value.\r\n * \r\n * @param {String} className Class name to toggle on element\r\n * @return {Object}\r\n */\r\n toggle(className) {\r\n this.element.forEach((element) => {\r\n new Helpers(element)\r\n .toggleClass(className)\r\n .toggleAttribute('aria-expanded')\r\n })\r\n\r\n return this\r\n }\r\n}\r\n\r\nexport default Reveal\r\n","class ReviewsReadMore {\r\n\r\n //#region constructor\r\n\r\n constructor() {\r\n\r\n // 'Read More' buttons method binding\r\n this.toggleReadMore = this.toggleReadMore.bind(this);\r\n\r\n }\r\n\r\n //#endregion\r\n\r\n //#region Reviews 'Read More' \r\n\r\n // Toggle between displaying truncated and full review text\r\n toggleReadMore(button) {\r\n const parent = button.parentNode;\r\n const isOpen = parent.classList.toggle('open');\r\n button.innerText = isOpen ? 'Read less...' : 'Read all...';\r\n }\r\n\r\n //#endregion\r\n\r\n}\r\n\r\n// Check for instance of reviews before class\r\nconst reviewsContainer = document.querySelector(\"body.cottage #reviews\");\r\nif (reviewsContainer) {\r\n\r\n // Create an instance of the Reviews class\r\n const readMore = new ReviewsReadMore();\r\n\r\n //#region Add Click Events \r\n\r\n const handleReadMoreClick = (event) => {\r\n const target = event.target;\r\n if (target.classList.contains('read-more-btn')) {\r\n // Toggle between displaying truncated and full review text\r\n readMore.toggleReadMore(target);\r\n }\r\n };\r\n\r\n // Attach onclick event for 'Read More' buttons on readMore\r\n reviewsContainer.addEventListener('click', handleReadMoreClick);\r\n\r\n //#endregion\r\n}\r\n","class Reviews {\r\n\r\n //#region General/Global Properties\r\n\r\n reviewsLocation = document.getElementById(\"reviews\");\r\n cottageCode = this.reviewsLocation.getAttribute('data-cottage-code');\r\n pageCount = parseInt(this.reviewsLocation.getAttribute('data-page-count'), 10);\r\n initialPageNum = parseInt(this.reviewsLocation.getAttribute('data-initial-page'), 10);\r\n showMore = document.getElementById(\"show-more-btn\");\r\n pagingContainer = document.getElementById(\"paging-container\");\r\n pagingDisplay = document.getElementById(\"paging-display\");\r\n prevPageButton = document.getElementById(\"prev-page-button\");\r\n nextPageButton = document.getElementById(\"next-page-button\");\r\n endpoint = \"/feeds/reviewCommentsFeed.aspx\";\r\n\r\n // Review character length properties (dynamically populated on first call before used in a global context)\r\n maximumCharLengthOfReview;\r\n maximumCharLengthOfReviewWithBuffer;\r\n\r\n //#endregion\r\n\r\n //#region constructor\r\n\r\n constructor() {\r\n\r\n // Bottom [Show More] Reviews method binding\r\n this.showMoreReviews = this.showMoreReviews.bind(this);\r\n this.getMoreReviews = this.getMoreReviews.bind(this);\r\n this.getLoadMoreNextPageNum = this.getLoadMoreNextPageNum.bind(this);\r\n this.updateCurrentLoadMorePageRange = this.updateCurrentLoadMorePageRange.bind(this);\r\n this.updateCurrentLoadMorePageRangeStart = this.updateCurrentLoadMorePageRangeStart.bind(this);\r\n this.updateCurrentLoadMorePageRangeEnd = this.updateCurrentLoadMorePageRangeEnd.bind(this);\r\n this.hideLoadMore = this.hideLoadMore.bind(this);\r\n\r\n // Pagination method bindings\r\n this.getPrevPaginationInfo = this.getPrevPaginationInfo.bind(this);\r\n this.getNextPaginationInfo = this.getNextPaginationInfo.bind(this);\r\n this.disablePagingPrevButton = this.disablePagingPrevButton.bind(this);\r\n this.disablePagingNextButton = this.disablePagingNextButton.bind(this);\r\n this.updatePagingDisplay = this.updatePagingDisplay.bind(this);\r\n\r\n // Utilities \r\n this.updateReviewPageURLParam = this.updateReviewPageURLParam.bind(this);\r\n this.checkIntitalQueryStringIsInRange = this.checkIntitalQueryStringIsInRange.bind(this);\r\n\r\n // Review HTML templating method binding\r\n this.reviewHTMLTemplate = this.reviewHTMLTemplate.bind(this);\r\n }\r\n\r\n //#endregion\r\n\r\n //#region Utilites\r\n\r\n // Update 'reviewpage' page URL params\r\n updateReviewPageURLParam = (requestedPage) => {\r\n const firstPage = requestedPage === 1;\r\n const currentUrl = new URL(window.location.href);\r\n const newPageState = firstPage ? {} : { reviewpage: requestedPage };\r\n\r\n currentUrl.searchParams.delete('reviewpage');\r\n\r\n if (!firstPage) {\r\n currentUrl.searchParams.set('reviewpage', requestedPage);\r\n }\r\n\r\n history.pushState(newPageState, \"\", currentUrl.toString());\r\n }\r\n\r\n\r\n // Ensure that the review page query parameter is within the valid range\r\n checkIntitalQueryStringIsInRange = () => {\r\n const reviewPageQueryParam = new URLSearchParams(window.location.search).get('reviewpage');\r\n if (reviewPageQueryParam && reviewPageQueryParam > this.pageCount) {\r\n this.updateReviewPageURLParam(this.pageCount);\r\n }\r\n }\r\n\r\n\r\n //#endregion\r\n\r\n //#region Bottom [Show More] Reviews\r\n\r\n // Set inital show more page number\r\n currentLoadMorePageRange = { start: parseInt(this.initialPageNum, 10), end: parseInt(this.initialPageNum, 10) };\r\n\r\n // Get the next page number, constrained within the maximum page count, for show more functionality\r\n getLoadMoreNextPageNum = () => {\r\n const nextPage = this.currentLoadMorePageRange.end + 1;\r\n return nextPage > this.pageCount ? this.pageCount : nextPage;\r\n }\r\n\r\n // Update the next page number for show more functionality\r\n updateCurrentLoadMorePageRange = (start, end) => { this.currentLoadMorePageRange.start = start; this.currentLoadMorePageRange.end = end };\r\n updateCurrentLoadMorePageRangeStart = (pageNumber) => this.currentLoadMorePageRange.start = pageNumber;\r\n updateCurrentLoadMorePageRangeEnd = (pageNumber) => this.currentLoadMorePageRange.end = pageNumber;\r\n\r\n // Hide or show 'Load More' button\r\n hideLoadMore = (hide) => this.showMore.setAttribute('aria-hidden', hide);\r\n\r\n // load more readmore when 'Load More' button is clicked\r\n showMoreReviews = () => {\r\n const requestedPage = this.getLoadMoreNextPageNum();\r\n this.updatePagingDisplay(`${this.currentLoadMorePageRange.start}-${requestedPage}`);\r\n this.getMoreReviews(requestedPage, false);\r\n this.updateCurrentLoadMorePageRangeEnd(requestedPage);\r\n this.hideLoadMore(requestedPage === this.pageCount);\r\n\r\n // Deal with crossover in pagination \r\n this.updateCurrentPaginationPage(requestedPage);\r\n this.disablePagingNextButton(requestedPage === this.pageCount);\r\n }\r\n\r\n\r\n //#endregion\r\n\r\n //#region Fetch Reviews\r\n\r\n // Asynchronously load more readmore from the server/feed\r\n async getMoreReviews(requestedPage, replaceReviews) {\r\n\r\n // Construct the query string for call\r\n const queryString = `?cottagecode=${this.cottageCode}&reviewpage=${requestedPage}&usepagination=Y`;\r\n const callURL = this.endpoint + queryString;\r\n\r\n // Set loading state if replacing readmore\r\n if (replaceReviews) this.setLoadingState(true);\r\n\r\n // Callback for successful fetch\r\n const loadMoreReviewsSuccess = (data) => {\r\n\r\n // Update maximum character lengths\r\n this.maximumCharLengthOfReview = data.MaximumCharLengthOfReview;\r\n this.maximumCharLengthOfReviewWithBuffer = data.MaximumCharLengthOfReviewWithBuffer;\r\n\r\n // If replacing remove the current readmore\r\n if (replaceReviews) this.reviewsLocation.innerHTML = \"\";\r\n\r\n // Loop through and create/insert readmore \r\n data.Reviews.forEach(review => {\r\n const reviewHTML = this.reviewHTMLTemplate(review);\r\n this.reviewsLocation.insertAdjacentHTML(\"beforeend\", reviewHTML);\r\n });\r\n\r\n // Reset loading state if replacing readmore\r\n if (replaceReviews) this.setLoadingState(false);\r\n }\r\n\r\n // Callback for fetch error\r\n const loadMoreReviewsError = (errorMessage) => {\r\n console.log(errorMessage);\r\n this.pagingContainer.remove();\r\n this.showMore.remove();\r\n if (replaceReviews)\r\n this.setLoadingState(false);\r\n }\r\n\r\n try {\r\n // Fetch reviews from feed\r\n const response = await fetch(callURL);\r\n if (response.ok) {\r\n const data = await response.json();\r\n loadMoreReviewsSuccess(data);\r\n } else\r\n loadMoreReviewsError(`${response.status} - ${response.statusText}`);\r\n } catch (error) {\r\n // Handle fetch error\r\n loadMoreReviewsError(error.message);\r\n }\r\n }\r\n\r\n //#endregion\r\n\r\n //#region Pagination\r\n\r\n // Set inital pagination page number\r\n currentPaginationPageNum = parseInt(this.initialPageNum, 10);\r\n\r\n // Update the current pagination page number\r\n updateCurrentPaginationPage = (page) => this.currentPaginationPageNum = page;\r\n\r\n // Set loading state for readmore container\r\n setLoadingState(loading) {\r\n this.reviewsLocation.classList.toggle('loading', loading);\r\n }\r\n\r\n // Get information for the next pagination page number\r\n getNextPaginationInfo = () => {\r\n const nextPage = this.currentPaginationPageNum + 1;\r\n return {\r\n NextPageIsLast: nextPage === this.pageCount,\r\n NextPageNumber: nextPage\r\n };\r\n }\r\n\r\n // Get information for the previous pagination page\r\n getPrevPaginationInfo = () => {\r\n const prevPage = this.currentPaginationPageNum - 1;\r\n return {\r\n PrevPageIsFirst: prevPage === 1,\r\n PrevPageNumber: prevPage\r\n };\r\n };\r\n\r\n // Update the current page in the pagination (e.g. Page 1 of 4)\r\n updatePagingDisplay(currentPage) {\r\n this.pagingDisplay.querySelector(\".current-page\").innerHTML = currentPage;\r\n }\r\n\r\n // Disable or enable the [Next] pagination button\r\n disablePagingNextButton = (disable) => {\r\n this.nextPageButton.setAttribute(\"data-disabled\", disable);\r\n }\r\n\r\n // Disable or enable the [Prev] pagination button\r\n disablePagingPrevButton = (disable) => {\r\n this.prevPageButton.setAttribute(\"data-disabled\", disable);\r\n }\r\n\r\n // Handle click on the [Prev] button\r\n reviewsPrevPage() {\r\n const { PrevPageIsFirst, PrevPageNumber } = this.getPrevPaginationInfo();\r\n\r\n // Enable/Disable pagination buttons\r\n this.disablePagingNextButton(false);\r\n this.disablePagingPrevButton(PrevPageIsFirst);\r\n\r\n // Update current pagination page\r\n this.updateCurrentPaginationPage(PrevPageNumber);\r\n\r\n // Update next page for show more functionality\r\n this.updateCurrentLoadMorePageRange(PrevPageNumber, PrevPageNumber);\r\n this.hideLoadMore(false);\r\n\r\n // Load more readmore for the previous page\r\n this.getMoreReviews(PrevPageNumber, true);\r\n this.updatePagingDisplay(PrevPageNumber);\r\n this.updateReviewPageURLParam(PrevPageNumber);\r\n }\r\n\r\n // Handle click on the [Next] button\r\n reviewsNextPage() {\r\n const { NextPageIsLast, NextPageNumber } = this.getNextPaginationInfo();\r\n\r\n // Enable/Disable pagination buttons\r\n this.disablePagingPrevButton(false);\r\n this.disablePagingNextButton(NextPageIsLast);\r\n\r\n // Update current pagination page\r\n this.updateCurrentPaginationPage(NextPageNumber);\r\n\r\n // Update next page for show more functionality\r\n this.updateCurrentLoadMorePageRange(NextPageNumber, NextPageNumber);\r\n this.hideLoadMore(NextPageIsLast);\r\n\r\n // Load more readmore for the next page\r\n this.getMoreReviews(NextPageNumber, true);\r\n this.updatePagingDisplay(NextPageNumber);\r\n this.updateReviewPageURLParam(NextPageNumber);\r\n }\r\n\r\n //#endregion\r\n\r\n //#region Reviews HTML Template\r\n\r\n // Generate HTML for a review\r\n reviewHTMLTemplate(review) {\r\n const reviewDate = new Date(review.ToDate);\r\n const reviewMonth = reviewDate.toLocaleString('en-GB', { month: 'long' });\r\n const reviewYear = reviewDate.getFullYear();\r\n const { TenantName, Comment, ModeratorReply } = review;\r\n\r\n // Start block\r\n let html = `\r\n ${TenantName}\r\n ${reviewMonth} ${reviewYear}\r\n
`;\r\n\r\n // Handle the guest comment section\r\n if (Comment.length > this.maximumCharLengthOfReviewWithBuffer) {\r\n // Truncated Long Comment\r\n html += `${Comment.substring(0, this.maximumCharLengthOfReview)}${Comment.substring(this.maximumCharLengthOfReview)}\r\n ...\r\n
\r\nRead all...
\r\nReply from Classic Cottages
`;\r\n if (ModeratorReply.length > this.maximumCharLengthOfReviewWithBuffer) {\r\n // Truncated Long Reply\r\n html += `${ModeratorReply.substring(0, this.maximumCharLengthOfReview)}${ModeratorReply.substring(this.maximumCharLengthOfReview)}\r\n ...\r\n
\r\nRead all...
`;\r\n }\r\n else {\r\n // Normal Reply\r\n html += `${ModeratorReply}
`;\r\n }\r\n }\r\n\r\n // Close the review HTML\r\n html += `Choose ${remaining} more bedroom(s).
`\r\n\r\n if (remaining === 0) {\r\n new Helpers(document.getElementById('RoomConfigFeedback')).addClass('complete')\r\n\r\n document.getElementById('RoomConfigFeedback').innerHTML = 'Correct number of bedrooms chosen.
'\r\n\r\n new Helpers(this.el.querySelectorAll('[type=\"checkbox\"]:not(:checked)')).setAttributes({ disabled: true })\r\n }\r\n\r\n return this\r\n }\r\n}\r\n\r\n// Disable zip and link radios if the bedroom is not selected.\r\nclass RoomConfigRow {\r\n constructor(el) {\r\n this.el = el\r\n\r\n if (this.el === null) {\r\n return\r\n }\r\n\r\n this.input = el.querySelector('[type=\"checkbox\"]')\r\n\r\n if (this.input !== null) {\r\n this.radios = el.querySelectorAll('[type=\"radio\"]')\r\n this.ul = el.querySelectorAll('ul')\r\n this.p = el.querySelectorAll('p')\r\n\r\n if (this.input.checked) {\r\n new Helpers(this.radios).removeAttributes(['disabled'])\r\n new Helpers(this.ul).removeClass('hidden')\r\n new Helpers(this.p).removeClass('hidden')\r\n } else {\r\n new Helpers(this.radios).setAttributes({ disabled: true })\r\n new Helpers(this.ul).addClass('hidden')\r\n new Helpers(this.p).addClass('hidden')\r\n }\r\n }\r\n\r\n return this\r\n }\r\n\r\n activate() {\r\n if (this.el === null) {\r\n return\r\n }\r\n\r\n this.listeners()\r\n\r\n return this\r\n }\r\n\r\n listeners() {\r\n if (this.el === null) {\r\n return\r\n }\r\n\r\n new Helpers(this.input).eventListener((e) => {\r\n if (e.currentTarget.checked) {\r\n new Helpers(this.radios).removeAttributes(['disabled'])\r\n new Helpers(this.ul).removeClass('hidden')\r\n new Helpers(this.p).removeClass('hidden')\r\n } else {\r\n new Helpers(this.radios).setAttributes({ disabled: true })\r\n new Helpers(this.ul).addClass('hidden')\r\n new Helpers(this.p).addClass('hidden')\r\n }\r\n })\r\n return this\r\n }\r\n}\r\n\r\ntry {\r\n // Instantiate the objects above.\r\n Array.from(document.getElementsByClassName('bedroom-config-row')).forEach((el) => {\r\n new RoomConfigRow(el).activate()\r\n })\r\n // FIXME(jameskennedy@classic.co.uk): Use class name not id.\r\n const totals = document.getElementById('MainContent_pnlBedConfiguration')\r\n if (totals !== null) {\r\n new RoomConfigTotals(totals).activate()\r\n }\r\n} catch (e) {\r\n console.log(e)\r\n}","import Cookies from 'js-cookie'\r\nimport Helpers from './Helpers'\r\nimport Map from '../modules/Map'\r\nclass Shortlist {\r\n constructor() {\r\n\r\n }\r\n\r\n list() {\r\n if (Cookies.get('shortlist') !== undefined) {\r\n return Cookies.get('shortlist').split(\",\").filter(code => code.length > 0)\r\n } else {\r\n return []\r\n }\r\n }\r\n\r\n set(list) {\r\n Cookies.set('shortlist', list.join(\",\"), { expires: 365 })\r\n this.updateHeaderIcon()\r\n }\r\n\r\n clear() {\r\n Cookies.remove('shortlist')\r\n this.updateHeaderIcon()\r\n }\r\n\r\n add(code) {\r\n if (!this.contains(code)) {\r\n let list = this.list()\r\n list.push(code.replace(/[CX]/, ''))\r\n this.set(list)\r\n window.dataLayerQueue.push({ 'event': 'addToShortlist', 'cottageCode': code });\r\n }\r\n }\r\n\r\n remove(code) {\r\n if (this.contains(code)) {\r\n let list = this.list()\r\n this.set(list.filter((c) => c.replace(/[CX]/, '') != code.replace(/[CX]/, '')))\r\n }\r\n }\r\n\r\n toggle(code) {\r\n if (!this.contains(code)) {\r\n this.add(code)\r\n } else {\r\n this.remove(code)\r\n }\r\n }\r\n\r\n contains(code) {\r\n return this.list().map(value => value.replace(/[CX]/, '')).indexOf(code.replace(/[CX]/, '')) >= 0\r\n }\r\n\r\n updateHeaderIcon() {\r\n const highlightClass = 'header__favourites--active'\r\n const icon = document.getElementsByClassName('header__favourites')\r\n if (this.list().length > 0) {\r\n new Helpers(icon).addClass(highlightClass)\r\n } else {\r\n new Helpers(icon).removeClass(highlightClass)\r\n }\r\n }\r\n}\r\n\r\nconst shortlist = new Shortlist()\r\nnew Helpers(document.getElementsByClassName('set-item')).eventListener((e) => {\r\n if (e.target.getAttribute('data-inlist') === true) {\r\n window.sl.DeleteCottageFromShortList(e.target.getAttribute('data-code'), e.target.getAttribute('data-guid'))\r\n e.target.setAttribute('data-active', 'false')\r\n }\r\n})\r\n\r\n// Pageload - set cottage shortlist button states\r\nconst favouriteButtons = () => Array.from(document.querySelectorAll('.favourite-button'))\r\n\r\nif (typeof Cookies.get('auth') === 'undefined') {\r\n favouriteButtons().forEach((el) => {\r\n el.setAttribute('data-multi', 'false')\r\n el.setAttribute('data-active', shortlist.contains(el.getAttribute('data-code')))\r\n })\r\n}\r\n\r\n// Favourite button click events\r\nnew Helpers(document.getElementsByTagName('body')).delegateEventListener('.favourite-button', (e) => {\r\n // don't react if we're not in \"cookie shortlist\" mode as that behaviour is handled by the shortlistapi\r\n if (e.target.getAttribute('data-multi') !== 'false') return\r\n\r\n const cottageCode = new Helpers(e.target).getClosest('[data-code]').getAttribute('data-code')\r\n const isShortlisted = (e.target.getAttribute('data-active') || 'false') == 'true'\r\n const affectedButtons = favouriteButtons().filter(button => button.getAttribute('data-code') === cottageCode)\r\n\r\n if (isShortlisted) {\r\n shortlist.remove(cottageCode)\r\n new Helpers(affectedButtons).setAttributes({ 'data-active': 'false' })\r\n } else {\r\n shortlist.add(cottageCode)\r\n new Helpers(affectedButtons).setAttributes({ 'data-active': 'true' })\r\n }\r\n})\r\n\r\n// My Shortlist\r\nif (document.getElementsByClassName('shortlist__map').length) {\r\n const map = new Map('shortlistMap', {\r\n center: [50.1016, -5.2750],\r\n scrollWheelZoom: false,\r\n })\r\n\r\n const icon = L.icon({\r\n iconUrl: '/media/map/icon-home.png',\r\n iconSize: [33, 47],\r\n iconAnchor: [17, 46],\r\n popupAnchor: [0, -40],\r\n })\r\n\r\n const loadMarkers = () => {\r\n map.clearMarkers()\r\n Array.from(document.getElementsByClassName('results-list__item')).forEach((item) => {\r\n const lat = item.getAttribute('data-lat')\r\n const lng = item.getAttribute('data-lng')\r\n const title = item.getElementsByTagName('h2')[0].innerText\r\n map.addMarker([lat, lng], {\r\n title,\r\n icon,\r\n })\r\n })\r\n map.clusterMarkers()\r\n map.fitMarkers({ padding: [40, 40] })\r\n }\r\n\r\n loadMarkers()\r\n\r\n new Helpers(document.getElementsByTagName('body')).eventListener(() => {\r\n loadMarkers()\r\n }, 'shortlist.remove')\r\n\r\n new Helpers(document.getElementsByClassName('shortlist__main')).delegateEventListener('[aria-label=\"Favourite\"][data-multi=\"false\"]', (e) => {\r\n const item = new Helpers(e.target).getClosest('.results-list__item')\r\n item.parentElement.removeChild(item)\r\n loadMarkers()\r\n })\r\n\r\n new Helpers(document.getElementsByClassName('shortlist__clear')).eventListener((e) => {\r\n if (typeof Cookies.get('auth') !== 'undefined') {\r\n if (document.getElementsByClassName('shortlists-item').length > 0) {\r\n const shortlistId = document.querySelector('.shortlists-item[aria-current=\"true\"]').getAttribute('data-shortlistid')\r\n window.sl.DeleteShortlist(shortlistId).then(() => {\r\n window.location.replace('/my-classic/myshortlist.aspx')\r\n })\r\n }\r\n } else {\r\n Array.from(document.getElementsByClassName('shortlist__main')).forEach((item) => {\r\n item.parentElement.removeChild(item)\r\n })\r\n shortlist.clear()\r\n }\r\n loadMarkers()\r\n })\r\n}\r\n\r\nexport default Shortlist\r\n","import Cookies from 'js-cookie'\r\nimport Helpers from './Helpers'\r\nimport Shortlist from './Shortlist.js'\r\nimport XHRPromise from 'xhr-promise'\r\nimport he from 'he'\r\n\r\nconst LOCALSTORAGE_SHORTLIST_KEY = 'shortlists'\r\n\r\nclass Shortlistitem {\r\n constructor(item = null) {\r\n if (item != null) {\r\n this.value = item.value\r\n this.id = item.id\r\n this.shortlistId = item.shortlistId\r\n // this.created = item.created\r\n }\r\n }\r\n}\r\n\r\nclass ShortlistNew {\r\n constructor(shortlist = null) {\r\n this.name = ''\r\n this.public = false\r\n this.id = ''\r\n // this.created = ''\r\n this.items = []\r\n if (shortlist != null) {\r\n this.populate(shortlist)\r\n }\r\n }\r\n\r\n populate(shortlist) {\r\n this.name = shortlist.name\r\n this.public = shortlist.public\r\n this.id = shortlist.id\r\n // this.created = shortlist.created\r\n shortlist.items.forEach((item) => {\r\n this.items.push(new Shortlistitem(item))\r\n })\r\n }\r\n addItem(value) {\r\n const item = new Shortlistitem()\r\n item.value = value\r\n this.items.push(item)\r\n }\r\n}\r\n\r\nclass ShortlistAPI {\r\n constructor() {\r\n this.baseURL = '/api'\r\n this.getAllUrl = `${this.baseURL}/shortlists`\r\n this.shortlists = []\r\n this.shortListItems = []\r\n\r\n if (!this.userIsAuthenticated()) {\r\n window.localStorage.removeItem(LOCALSTORAGE_SHORTLIST_KEY)\r\n }\r\n this.GetAllFromLocalStorage()\r\n }\r\n\r\n userIsAuthenticated() {\r\n return typeof Cookies.get('auth') !== 'undefined'\r\n }\r\n\r\n token() {\r\n return Cookies.get('auth')\r\n }\r\n\r\n LoadShortlists(forceRefresh = false) {\r\n return new Promise((resolve) => {\r\n if (this.userIsAuthenticated()) {\r\n if (window.localStorage.getItem(LOCALSTORAGE_SHORTLIST_KEY) === null || forceRefresh) {\r\n this.GetAllshortlists().then(() => {\r\n this.GetAllFromLocalStorage()\r\n resolve()\r\n })\r\n }\r\n }\r\n resolve()\r\n })\r\n \r\n }\r\n\r\n UpdateLocalStorage(Lsshortlists) {\r\n const obj = JSON.stringify(Lsshortlists)\r\n window.localStorage.setItem(LOCALSTORAGE_SHORTLIST_KEY, obj)\r\n this.updateListOfShortlists()\r\n }\r\n\r\n GetAllFromLocalStorage() {\r\n this.shortlists = []\r\n if (window.localStorage.getItem(LOCALSTORAGE_SHORTLIST_KEY) !== null) {\r\n try {\r\n const obj = JSON.parse(window.localStorage.getItem(LOCALSTORAGE_SHORTLIST_KEY))\r\n if (obj.length > 0) {\r\n obj.forEach(sl => this.shortlists.push(new ShortlistNew(sl)))\r\n }\r\n }\r\n catch(err) {\r\n window.localStorage.removeItem(LOCALSTORAGE_SHORTLIST_KEY)\r\n }\r\n }\r\n }\r\n\r\n GetAllshortlists() {\r\n return new Promise((resolve) => {\r\n if (this.userIsAuthenticated()) {\r\n const GetAllRequest = new XHRPromise()\r\n GetAllRequest.send({\r\n url: this.getAllUrl,\r\n headers: { Authorization: `Bearer ${this.token()}` },\r\n }).then((response) => {\r\n this.shortlists = []\r\n if (response.responseText.length) {\r\n response.responseText.forEach((shortlist) => {\r\n this.shortlists.push(new ShortlistNew(shortlist))\r\n })\r\n }\r\n this.UpdateLocalStorage(this.shortlists)\r\n this.updateHeaderIcon()\r\n resolve()\r\n })\r\n }\r\n\r\n })\r\n }\r\n\r\n CreateShortlist(Slname, SlPublic) {\r\n const PostShortList = new XHRPromise()\r\n const obj = JSON.stringify({ name: Slname, public: SlPublic })\r\n return new Promise((resolve) => {\r\n PostShortList.send({\r\n method: 'POST',\r\n url: this.getAllUrl,\r\n headers: { Authorization: `Bearer ${this.token()}`, 'Content-Type': 'application/json' },\r\n data: obj,\r\n }).then(() => {\r\n this.GetAllshortlists().then(() => resolve())\r\n })\r\n })\r\n }\r\n\r\n PatchShortlist(SlName, SlGuid) {\r\n const PatchShortList = new XHRPromise()\r\n const obj = JSON.stringify({ name: SlName })\r\n const patchURL = `${this.getAllUrl}/${SlGuid}/`\r\n return new Promise((resolve) => {\r\n PatchShortList.send({\r\n method: 'PATCH',\r\n url: patchURL,\r\n headers: { Authorization: `Bearer ${this.token()}`, 'Content-Type': 'application/json' },\r\n data: obj,\r\n }).then(() => {\r\n this.GetAllshortlists().then(() => resolve())\r\n })\r\n })\r\n }\r\n\r\n AddShortlistItem(item) {\r\n const shortlist = this.shortlists.find(s => s.id === item.shortlistId)\r\n let matchedItem = 'undefined'\r\n if (typeof shortlist.items !== 'undefined') {\r\n matchedItem = shortlist.items.find(i => i.value === item.value)\r\n }\r\n if (typeof matchedItem === 'undefined' || matchedItem === '') {\r\n const PostShortListItem = new XHRPromise()\r\n const obj = JSON.stringify({ value: item.value })\r\n const postItemUrl = `${this.getAllUrl}/${item.shortlistId}/items`\r\n PostShortListItem.send({\r\n method: 'POST',\r\n url: postItemUrl,\r\n headers: { Authorization: `Bearer ${this.token()}`, 'Content-Type': 'application/json' },\r\n data: obj,\r\n }).then(() => {\r\n this.GetAllshortlists()\r\n this.updateListOfShortlists()\r\n })\r\n }\r\n }\r\n\r\n DeleteShortlist(listId) {\r\n if (typeof listId !== 'undefined') {\r\n const DeleteShortList = new XHRPromise()\r\n const deleteUrl = `${this.getAllUrl}/${listId}`\r\n return new Promise((resolve) => {\r\n DeleteShortList.send({\r\n method: 'DELETE',\r\n url: deleteUrl,\r\n headers: { Authorization: `Bearer ${this.token()}`, 'Content-Type': 'application/json' },\r\n }).then(() => {\r\n this.GetAllshortlists().then(() => resolve())\r\n })\r\n })\r\n }\r\n }\r\n\r\n DeleteShortListItem(item) {\r\n const shortlist = this.shortlists.find(s => s.id === item.shortlistId)\r\n const matchedItem = shortlist.items.find(i => i.value === item.value)\r\n if (typeof matchedItem !== 'undefined') {\r\n const DeleteShortListItem = new XHRPromise()\r\n const deleteItemUrl = `${this.getAllUrl}/${item.shortlistId}/items/${item.id}`\r\n DeleteShortListItem.send({\r\n method: 'DELETE',\r\n url: deleteItemUrl,\r\n headers: { Authorization: `Bearer ${this.token()}`, 'Content-Type': 'application/json' },\r\n }).then(() => {\r\n this.GetAllshortlists()\r\n })\r\n }\r\n }\r\n\r\n DeleteCottageFromShortListMultiList(cottageCode, ListId) {\r\n this.DeleteCottageFromShortList(cottageCode, ListId)\r\n const container = event.target.closest('div')\r\n const p = container.querySelector('p')\r\n p.setAttribute('data-inlist', 'false')\r\n container.setAttribute('data-active', 'false')\r\n let dataactive = false\r\n const items = container.parentElement.querySelectorAll('p')\r\n items.forEach((i) => {\r\n if (i.getAttribute('data-inlist') === 'true') {\r\n dataactive = true\r\n }\r\n })\r\n new Helpers(document.querySelectorAll(`.favourite-button[data-code=\"${cottageCode}\"]`)).setAttributes({ 'data-active': dataactive })\r\n document.getElementsByTagName('body')[0].dispatchEvent(new CustomEvent('shortlist.remove'))\r\n this.updateListOfShortlists()\r\n }\r\n\r\n ChangeSetValueMulti() {\r\n const multiListItem = new Helpers(event.target).getClosest('p')\r\n let inList = multiListItem.getAttribute('data-inlist')\r\n const cottageCode = multiListItem.getAttribute('data-code')\r\n const listId = multiListItem.getAttribute('data-guid')\r\n if (inList === '') { inList = false }\r\n if (inList !== 'true') {\r\n this.AddCottageToShortListMultiList(cottageCode, listId)\r\n } else {\r\n this.DeleteCottageFromShortListMultiList(cottageCode, listId)\r\n }\r\n }\r\n DeleteCottageFromShortList(cottageCode, ListId) {\r\n this.GetAllFromLocalStorage()\r\n let sl = ''\r\n if (this.shortlists.find(i => i.id === ListId)) {\r\n sl = this.shortlists.find(i => i.id === ListId)\r\n }\r\n if (sl.items.length) {\r\n const thisItem = sl.items.find(i => i.value === cottageCode)\r\n thisItem.shortlistId = sl.id\r\n this.DeleteShortListItem(thisItem)\r\n }\r\n }\r\n AddCottageToShortListMultiList(cottageCode, ListId) {\r\n this.AddCottageToShortList(cottageCode, ListId)\r\n const container = event.target.closest('div')\r\n container.setAttribute('data-active', 'true')\r\n new Helpers(document.querySelectorAll(`.favourite-button[data-code=\"${cottageCode}\"]`)).setAttributes({ 'data-active': 'true' })\r\n const p = container.querySelector('p')\r\n p.setAttribute('data-inlist', 'true')\r\n }\r\n\r\n AddCottageToShortList(cottageCode, ListId) {\r\n this.GetAllFromLocalStorage()\r\n let sl = ''\r\n if (this.shortlists.find(i => i.id === ListId)) {\r\n sl = this.shortlists.find(i => i.id === ListId)\r\n }\r\n const thisItem = new Shortlistitem()\r\n if (typeof sl === 'undefined' || sl === '') {\r\n this.CreateShortlist('Default', false).then(() => {\r\n thisItem.shortlistId = window.sl.shortlists[0].id\r\n thisItem.value = cottageCode\r\n this.AddShortlistItem(thisItem)\r\n })\r\n } else {\r\n thisItem.shortlistId = sl.id\r\n thisItem.value = cottageCode\r\n this.AddShortlistItem(thisItem)\r\n }\r\n }\r\n\r\n updateHeaderIcon() {\r\n const highlightClass = 'header__favourites--active'\r\n const cookieName = 'CC_SHLC'\r\n const icon = document.getElementsByClassName('header__favourites')\r\n\r\n Cookies.remove(cookieName)\r\n\r\n if (this.userIsAuthenticated()) {\r\n this.GetAllFromLocalStorage()\r\n if (typeof this.shortlists !== 'undefined') {\r\n if (this.shortlists.length) {\r\n let shortlistsItemsCount = 0\r\n this.shortlists.forEach((sl) => shortlistsItemsCount += sl.items.length)\r\n\r\n if (shortlistsItemsCount > 0) {\r\n new Helpers(icon).addClass(highlightClass)\r\n Cookies.set(cookieName, shortlistsItemsCount)\r\n } else {\r\n new Helpers(icon).removeClass(highlightClass)\r\n }\r\n }\r\n }\r\n } else {\r\n const shortlist = Cookies.get('shortlist')\r\n if (typeof shortlist !== 'undefined' && shortlist.length > 0) {\r\n new Helpers(icon).addClass(highlightClass)\r\n } else {\r\n new Helpers(icon).removeClass(highlightClass)\r\n }\r\n }\r\n }\r\n\r\n HighlightShortlistedCottages() {\r\n const buttons = document.getElementsByClassName('favourite-button')\r\n if (this.userIsAuthenticated()) {\r\n if (typeof this.shortlists === 'undefined') {\r\n this.GetAllshortlists().then(() => {\r\n this.HighlightShortlistedCottages()\r\n })\r\n return\r\n }\r\n new Helpers(buttons).setAttributes({ 'data-multi': 'true' })\r\n } else {\r\n new Helpers(buttons).setAttributes({ 'data-multi': 'false' })\r\n }\r\n \r\n Array.from(buttons).forEach((button) => {\r\n const val = this.IsCodeInShortlists(button.getAttribute('data-code'))\r\n button.setAttribute('data-active', val)\r\n })\r\n }\r\n\r\n updateListOfShortlists() {\r\n const shortlistLinks = document.querySelector('.shortlist-list ul')\r\n if (shortlistLinks !== null) {\r\n // empty items from div\r\n shortlistLinks.innerHTML = ''\r\n // repopulate div\r\n const currentId = document.getElementById('listId').value\r\n this.shortlists.forEach((s) => {\r\n let activeFlag = ''\r\n if (currentId === s.id) {\r\n activeFlag = 'aria-current=\"true\"'\r\n }\r\n shortlistLinks.innerHTML += `more images`;\r\n sliderItem.appendChild(link); \r\n }\r\n\r\n const img = document.createElement('img');\r\n img.setAttribute('src', p.getAttribute('url'));\r\n img.setAttribute('srcset', p.getAttribute('srcsets'));\r\n img.setAttribute('sizes', p.getAttribute('sizes'));\r\n sliderItem.appendChild(img);\r\n imageGroup.getElementsByClassName('slides__container')[0].appendChild(sliderItem);\r\n })\r\n\r\n imageGroup.getElementsByClassName('slides__container')[0].style.width = '999999px';\r\n const slider = new Slider(imageGroup);\r\n new Helpers(imageGroup).removeClass('slider--inactive').addClass('slider')\r\n resolve(slider)\r\n })\r\n})\r\n\r\nif (resultsItemsContainer.length > 0) {\r\n new Helpers(resultsItemsContainer).delegateEventListener(imageGroupSelector, (e) => {\r\n const el = new Helpers(e.target).getClosest(imageGroupSelector)\r\n if (el.getAttribute('data-loaded') !== 'true') {\r\n // set as loaded\r\n el.setAttribute('data-loaded', 'true')\r\n // get images and load swiper\r\n loadImages(el)\r\n }\r\n }, 'mouseover')\r\n\r\n new Helpers(resultsItemsContainer).delegateEventListener(imageGroupSelector, (e) => {\r\n const el = new Helpers(e.target).getClosest(imageGroupSelector)\r\n if (el.getAttribute('data-loaded') !== 'true') {\r\n const touchStart = e.touches[0].pageX\r\n // set as loaded\r\n el.setAttribute('data-loaded', 'true')\r\n // get images and load swiper\r\n let slider = null\r\n // load the images and retrieve instance\r\n loadImages(el).then((sliderInstance) => {\r\n slider = sliderInstance\r\n })\r\n\r\n // register a one off touchend event to perform the first navigation event\r\n const touchendCallback = (event) => {\r\n event.currentTarget.removeEventListener(event.type, touchendCallback)\r\n const touchEnd = event.changedTouches[0].pageX\r\n const touchChange = touchStart - touchEnd\r\n // if there is sufficient positional change, navigate the slider\r\n if (Math.abs(touchChange) > 30) {\r\n // use an interval timer as the slider may not yet have been initialized\r\n const sliderNavigateOnLoad = setInterval(() => {\r\n if (slider !== null) {\r\n clearInterval(sliderNavigateOnLoad)\r\n if (touchChange > 30) slider.changeSlide('next', slider.isLoop())\r\n if (touchChange < -30) slider.changeSlide('prev', slider.isLoop())\r\n }\r\n }, 50)\r\n }\r\n }\r\n\r\n el.addEventListener('touchend', touchendCallback)\r\n }\r\n }, 'touchstart')\r\n}\r\n","import Helpers from '../modules/Helpers'\r\n\r\n// Slider thumbnail class (can't currently get this to 'extend')\r\nclass SliderThumbNailGallery {\r\n\r\n //#region constructor\r\n\r\n constructor(sliderClass, options, sliderProperties) {\r\n\r\n // Create an accessible instance of the current slider\r\n this.sliderClass = sliderClass;\r\n this.slider = sliderClass.slider;\r\n this.options = options;\r\n this.sliderProperties = sliderProperties;\r\n\r\n // General properties\r\n this.thumbnailGalleryId = this.getThumbnailGalleryId(this);\r\n if(!this.thumbnailGalleryId) return;\r\n this.thumbnailGallery = document.getElementById(this.thumbnailGalleryId);\r\n this.thumbnailContainer = this.thumbnailGallery.querySelector('.property-slider__thumbnails');\r\n this.thumbs = Array.from(this.thumbnailGallery.querySelectorAll('.thumbnail'));\r\n this.thumbImages = this.thumbnailContainer.querySelectorAll('img');\r\n\r\n // Set slider Thumb General Slider Properties\r\n this.thumbProperties = this.setThumbProperties();\r\n\r\n // Load up thumbnail fun\r\n this.thumbnailLoad();\r\n\r\n this.thumbHammer();\r\n }\r\n\r\n //#endregion\r\n \r\n //#region Options Settings/Properties\r\n\r\n // Set all thumb setting properties: this.thumbProperties\r\n setThumbProperties = () => {\r\n return {\r\n clonesOffest: this.thumbs.slice().length,\r\n clonelessThumbCount: this.thumbs.length,\r\n currentThumbPosition: 0,\r\n }\r\n }\r\n\r\n //#endregion\r\n\r\n //#region Utility Functions\r\n\r\n // Get the containing element id to associate with the slider\r\n getThumbnailGalleryId = () => this.slider.dataset.thumbnailId;\r\n\r\n // get a cloned version of the thumbnails\r\n getClonedThumbs = () => this.thumbs.filter(thumb => !thumb.classList.contains('thumb__item--clone')).slice();\r\n\r\n // Update current thumb\r\n setCurrentThumb = (position) => this.thumbProperties.currentThumbPosition = position;\r\n\r\n // Check if certain thumb is a 'cloned' thumb item\r\n isThumbAClone = (index) => this.thumbs[index].classList.contains('thumb__item--clone');\r\n \r\n\r\n //#endregion\r\n\r\n //#region Loading\r\n\r\n // Initial Load Up Of The Slider Thumbnail Gallery\r\n thumbnailLoad = () => {\r\n\r\n // Add Thumb Clones For Looping\r\n if (this.sliderProperties.isLoop) this.addThumbClones();\r\n \r\n // Update CSS\r\n this.setThumbActiveCSSClasses(this.thumbProperties.currentThumbPosition, false);\r\n\r\n // Add click events to the thumbs\r\n this.thumbnailClicks();\r\n\r\n // Add hover events to thumbs\r\n this.thumbnailHover();\r\n\r\n // Check all the thumbnail images have loaded before we reveal them\r\n this.checkAllImagesLoaded(() => {\r\n\r\n // Set initial position \r\n this.setThumbXAxisPosition(this.thumbProperties.currentThumbPosition, false);\r\n\r\n // Take thumbs out of loading state\r\n this.setThumbLoadingState(false);\r\n\r\n })\r\n\r\n }\r\n\r\n //#endregion\r\n\r\n //#region utility functions \r\n\r\n // Set the thumbs in/out of the 'loading' state\r\n setThumbLoadingState = loading => loading ? this.thumbnailGallery.classList.add('loading') : this.thumbnailGallery.classList.remove('loading');\r\n\r\n //#endregion\r\n\r\n //#region Image Loading\r\n\r\n // Callback function for when all the thumbnail images are loaded\r\n checkAllImagesLoaded = (callback) => {\r\n const checkImagesRendered = () => {\r\n const allImagesRendered = Array.from(this.thumbImages).every(image => image.complete && image.naturalWidth !== 0);\r\n allImagesRendered ? callback() : requestAnimationFrame(checkImagesRendered);\r\n };\r\n checkImagesRendered();\r\n };\r\n \r\n //#endregion\r\n\r\n //#region Set Up Clones\r\n\r\n // Basic entry point for adding clones to slides array and the DOM\r\n addThumbClones = () => {\r\n\r\n // Clone Last thumb(s) and add them to the front of the slides\r\n this.addFirstThumbClones();\r\n\r\n // Clone First thumb(s) and add them to the back of the slides\r\n this.addLastThumbClones();\r\n }\r\n\r\n // Take the last thumb(s) and clone them before the first thumb array nodes and thumb DOM elements \r\n addFirstThumbClones = () => {\r\n\r\n const clones = this.getClonedThumbs();\r\n\r\n clones.reverse().forEach((thumb) => {\r\n const clonedThumb = thumb.cloneNode(true);\r\n // Add clone specific class\r\n new Helpers(clonedThumb).addClass('thumb__item--clone');\r\n // Add clone to global thumb array\r\n this.thumbs.unshift(clonedThumb);\r\n // Add to the DOM\r\n this.thumbnailContainer.prepend(clonedThumb);\r\n });\r\n\r\n // Adjust for inserted clones\r\n this.setCurrentThumb(clones.length);\r\n\r\n };\r\n\r\n // Take the first thumb(s) and clone them after the last thumb array nodes and thumb DOM elements \r\n addLastThumbClones = () => {\r\n\r\n this.getClonedThumbs().forEach(thumb => {\r\n const clonedThumb = thumb.cloneNode(true);\r\n // Add clone specific class\r\n new Helpers(clonedThumb).addClass('thumb__item--clone');\r\n // Append clone to global thumb array \r\n this.thumbs.push(clonedThumb);\r\n // Append clone to the DOM\r\n this.thumbnailContainer.append(clonedThumb);\r\n });\r\n };\r\n\r\n\r\n\r\n //#endregion\r\n\r\n //#region Position change and associated Functions\r\n\r\n // Previous thumb\r\n previousThumb = () => this.nextPrevFunctions(this.thumbProperties.currentThumbPosition-1);\r\n\r\n // Next thumb\r\n nextThumb = () => this.nextPrevFunctions(this.thumbProperties.currentThumbPosition+1);\r\n\r\n // shared functionality for moving to prev/next thumb\r\n nextPrevFunctions = (newPostiton) => {\r\n\r\n // set global property\r\n this.setCurrentThumb(newPostiton);\r\n\r\n // Change thumb\r\n this.setThumbXAxisPosition(newPostiton, true);\r\n\r\n // Update CSS\r\n this.setThumbActiveCSSClasses(newPostiton, true);\r\n\r\n // Check if loop is required\r\n this.loopThumbs(newPostiton);\r\n }\r\n\r\n // Change where the thumb is\r\n changeActiveThumb = (newPostiton, useTransition = true) => {\r\n\r\n // set global property\r\n this.setCurrentThumb(newPostiton);\r\n\r\n // Trigger the DOM change\r\n this.setThumbXAxisPosition(newPostiton, useTransition);\r\n\r\n // Update CSS\r\n this.setThumbActiveCSSClasses(newPostiton, useTransition);\r\n\r\n // Check if loop is required\r\n this.loopThumbs(newPostiton);\r\n\r\n }\r\n\r\n // Set thumb position\r\n setThumbXAxisPosition = (thumbIndex, useTransition = true) => {\r\n let position = 0;\r\n let sliced = this.thumbs.slice(0, thumbIndex);\r\n sliced.forEach(thumb => {\r\n const rect = thumb.getBoundingClientRect();\r\n position += rect.width;\r\n });\r\n\r\n // If thumb is gracefully 'sliding' to new position or jumping (Jump to, probably part of a 'loop')\r\n if (useTransition) {\r\n this.thumbnailContainer.style.transition = `transform ${this.options.speed}s ease-in-out 0s`;\r\n } else {\r\n this.thumbnailContainer.style.transition = 'none';\r\n }\r\n\r\n // Move thumb into position\r\n if (this.sliderProperties.isBrowserIE9) {\r\n this.thumbnailContainer.style.msTransform = `translateX(-${position}px)`\r\n } else {\r\n this.thumbnailContainer.style.transform = `translate3d(-${position}px, 0px, 0px)`\r\n this.thumbnailContainer.style.webkitTransform = `translateX(-${position}px)`\r\n }\r\n\r\n }\r\n\r\n // Set thumb as active via class\r\n // Note: we use 'active--no-transition' to avoid buggy double transitions while looping the thumb\r\n setThumbActiveCSSClasses = (position, useTransition = true) => {\r\n this.thumbs.forEach(x => x.classList.remove('active', 'active--no-transition'));\r\n useTransition ? this.thumbs[position].classList.add('active') : this.thumbs[position].classList.add('active--no-transition');\r\n }\r\n\r\n //#endregion\r\n\r\n //#region Thumb loop functions\r\n\r\n // Check and then loop the thumbs if need be\r\n loopThumbs = (newThumbPosition) => {\r\n \r\n const offset = this.thumbProperties.clonesOffest;\r\n\r\n if (this.isThumbAClone(newThumbPosition)) {\r\n if (newThumbPosition < offset) {\r\n let adjustedNewPosition = newThumbPosition + (this.thumbs.length - (offset * 2));\r\n this.doThumbBackLoop(adjustedNewPosition);\r\n }\r\n else if (newThumbPosition >= this.thumbs.length - offset) {\r\n let adjustedNewPosition = newThumbPosition - (this.thumbs.length - offset) + offset;\r\n this.doThumbFrontLoop(adjustedNewPosition);\r\n }\r\n }\r\n }\r\n\r\n\r\n // Loop back over images (so from the first thumb to the last)\r\n doThumbBackLoop = (newThumbPosition) => {\r\n setTimeout(() => {\r\n this.loopThumbFunctions(newThumbPosition);\r\n }, this.options.speed * 1000);\r\n }\r\n\r\n\r\n // Loop back over images (so from the first slider to the last)\r\n doThumbFrontLoop = (newThumbPosition) => {\r\n setTimeout(() => {\r\n this.loopThumbFunctions(newThumbPosition);\r\n }, this.options.speed * 1000);\r\n }\r\n\r\n\r\n // Shared loop functionality regardless of front or back loop \r\n loopThumbFunctions = (newThumbPosition) => {\r\n\r\n // Trigger the DOM change\r\n this.setThumbXAxisPosition(newThumbPosition, false);\r\n\r\n // // Update CSS\r\n this.setThumbActiveCSSClasses(newThumbPosition, false);\r\n\r\n // // Update global position\r\n this.setCurrentThumb(newThumbPosition);\r\n }\r\n\r\n\r\n //#endregion\r\n\r\n //#region Thumbnail clicks\r\n\r\n // Add click events for the thumbnail gallery\r\n thumbnailClicks = () => {\r\n\r\n // Loop through each thumbnail\r\n this.thumbs.forEach((thumb, thumbIndex) => {\r\n\r\n // Add click event listener to each thumbnail\r\n thumb.addEventListener('click', () => {\r\n\r\n // Get the index of the clicked thumbnail\r\n const dataIndex = parseInt(thumb.dataset.sliderIndex, 10);\r\n const currentSlideDataIndex = parseInt(this.sliderClass.slides[this.sliderProperties.currentSliderPosition].dataset.sliderIndex, 10);\r\n if(dataIndex !== currentSlideDataIndex)\r\n {\r\n // Adjust index to target to prevent 'whirling' visual bug \r\n const newSlidePosition = this.PreventWhirlingEffect(dataIndex);\r\n\r\n // Change slider position to the clicked index\r\n this.sliderClass.changeActiveSlider(newSlidePosition);\r\n\r\n // Change thumb\r\n this.changeActiveThumb(thumbIndex, true);\r\n }\r\n });\r\n });\r\n }\r\n\r\n // Adjust index of selected to next clone rather than actual slide if that would cause a whirling visual bug\r\n PreventWhirlingEffect = (dataIndex) => {\r\n \r\n const sliderLength = this.sliderClass.slides.length;\r\n let currentIndex = this.sliderProperties.currentSliderPosition;\r\n let nextIndex = (currentIndex + 1) % sliderLength;\r\n \r\n // Check if current index is less than or equal to half of the total slides\r\n if (currentIndex >= sliderLength / 2) {\r\n // Search forward (will be a clone)\r\n for (let i = nextIndex; i !== currentIndex; i = (i + 1) % sliderLength) { \r\n if (this.sliderClass.slides[i].getAttribute('data-slider-index') === dataIndex.toString()) {\r\n return i;\r\n }\r\n }\r\n } \r\n\r\n // Return dataIndex corrected with clone offset \r\n return dataIndex + this.sliderClass.options.clonesOffset;\r\n }\r\n \r\n\r\n //#endregion\r\n\r\n //#region Thumbnail hover\r\n \r\n // Add hover event to thumbs (mainly to predict and pre-load images)\r\n thumbnailHover = () => {\r\n\r\n // Iterate through each element in the array\r\n this.thumbs.forEach((thumb) => {\r\n // Add a hover event listener to each element\r\n thumb.addEventListener('mouseover', () => {\r\n // Get the hovered element's data-slider-index attribute value\r\n const hoveredIndex = parseInt(thumb.getAttribute('data-slider-index'));\r\n \r\n // Create an array of indices from 0 to the hovered index\r\n const indices = Array.from({ length: hoveredIndex + this.options.clonesOffset }, (_, i) => i);\r\n \r\n // Load the array of indices\r\n this.sliderClass.lazyLoadSlideImages(indices, true, true);\r\n });\r\n });\r\n }\r\n\r\n //#endregion\r\n\r\n //#endregion hammer / swipe gesture\r\n\r\n thumbHammer = () => {\r\n if (typeof Hammer === 'function') {\r\n\r\n const thumbHammertime = new Hammer(this.thumbnailGallery);\r\n\r\n thumbHammertime.on('panright', () => {\r\n this.hammerDirection = 'prev';\r\n });\r\n\r\n thumbHammertime.on('panleft', () => {\r\n this.hammerDirection = 'next';\r\n });\r\n\r\n thumbHammertime.on('panend', () => {\r\n\r\n let newSliderPosition = this.hammerDirection === 'next' ? ++this.sliderProperties.currentSliderPosition : --this.sliderProperties.currentSliderPosition;\r\n\r\n // Update slider\r\n this.sliderClass.changeActiveSlider(newSliderPosition);\r\n\r\n /// Update thumbs\r\n if(this.hammerDirection === 'next')\r\n this.nextThumb();\r\n else\r\n this.previousThumb();\r\n \r\n clearInterval(this.autoInt);\r\n });\r\n }\r\n }\r\n\r\n //#endregion\r\n\r\n}\r\n\r\nexport default SliderThumbNailGallery;","import Helpers from '../modules/Helpers'\r\nimport XhrPromise from 'xhr-promise'\r\nimport Clipboard from 'clipboard'\r\n\r\nconst emailShare = document.getElementsByClassName('modal-share__email')\r\nconst emailShareForm = document.getElementsByClassName('modal-share__email-form')\r\nconst emailShareSuccess = document.getElementsByClassName('modal-share__email-success')\r\nconst emailShareError = document.getElementsByClassName('modal-share__email-error')\r\n\r\n// initiate dialog copy link to clipboard function\r\nnew Clipboard(document.getElementsByClassName('modal-share__copy-link'))\r\n\r\nnew Helpers(document.getElementsByClassName('social-share')).eventListener((e) => {\r\n let link = e.target.getAttribute('data-link')\r\n const tweet = e.target.getAttribute('data-tweet')\r\n const title = e.target.getAttribute('data-title')\r\n const emailType = e.target.getAttribute('data-emailType')\r\n const emailData = e.target.getAttribute('data-emailData')\r\n const emailSubject = e.target.getAttribute('data-emailSubject')\r\n const message = e.target.getAttribute('data-message')\r\n\r\n const modal = document.getElementById('shareModal')\r\n\r\n // Set form values for email send\r\n modal.getElementsByClassName('modal-share__title')[0].innerText = title\r\n modal.getElementsByClassName('modal-share__email-type')[0].value = emailType\r\n modal.getElementsByClassName('modal-share__email-subject')[0].value = emailSubject\r\n modal.getElementsByClassName('modal-share__email-data')[0].value = emailData\r\n modal.getElementsByTagName('textarea')[0].value = message\r\n\r\n if (document.getElementsByClassName('shortlist-home').length > 0) {\r\n const shareURL = `https://${e.target.getAttribute('data-siteurl')}my-classic/MyShortlist.aspx`\r\n const slname = encodeURI(e.target.getAttribute('data-shortlistname'))\r\n const slcodes = Array.from(document.getElementsByClassName('favourite-button')).map(i => i.getAttribute(\"data-code\")).join(\",\")\r\n link = `${shareURL}?addShortlist=${slcodes}&slname=${slname}`\r\n }\r\n modal.getElementsByClassName('modal-share__shortlist-url')[0].value = link\r\n // Set links for social buttons\r\n modal.getElementsByClassName('btn-facebook')[0].href = `https://www.facebook.com/sharer/sharer.php?u=${escape(link)}`\r\n modal.getElementsByClassName('btn-twitter')[0].href = `https://twitter.com/share/?url=${escape(link)}&text=${escape(tweet)}`\r\n // set link for copy/paste\r\n modal.getElementsByClassName('modal-share__share-link-url')[0].value = link\r\n\r\n // Set initial display state of form\r\n new Helpers(emailShare).setAttributes({ 'aria-hidden': false })\r\n new Helpers(emailShareSuccess).setAttributes({ 'aria-hidden': true })\r\n new Helpers(emailShareError).setAttributes({ 'aria-hidden': true })\r\n})\r\n\r\n// toggle visiblity of custom email message box\r\nnew Helpers(document.getElementsByClassName('modal-share__email-add-message')).eventListener((e) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n\r\n new Helpers(emailShareForm).toggleClass('add-message')\r\n})\r\n\r\n// Handle ajax email send\r\nnew Helpers(document.getElementsByClassName('modal-share__email')).eventListener((e) => {\r\n e.preventDefault()\r\n const values = []\r\n Array.from(e.target.elements).forEach((element) => {\r\n if (element.name !== undefined && element.name !== '') {\r\n values.push(`${encodeURIComponent(element.name)}=${encodeURIComponent(element.value)}`)\r\n }\r\n })\r\n // hide form to prevent duplicate submission\r\n new Helpers(emailShare).setAttributes({ 'aria-hidden': true })\r\n\r\n const xhr = new XhrPromise()\r\n xhr.send({\r\n url: '/ajax/email.aspx',\r\n method: 'POST',\r\n data: values.join('&'),\r\n })\r\n .then((response) => {\r\n return (new window.DOMParser()).parseFromString(response.responseText, \"text/xml\")\r\n })\r\n .then((data) => {\r\n\r\n const results = data.getElementsByTagName('results')[0]\r\n if (results.getAttribute('outcome') === 'sent') {\r\n new Helpers(emailShareSuccess).setAttributes({ 'aria-hidden': false })\r\n } else {\r\n new Helpers(emailShareError).setAttributes({ 'aria-hidden': false })\r\n }\r\n }).catch((ex) => {\r\n new Helpers(emailShareError).setAttributes({ 'aria-hidden': false })\r\n })\r\n\r\n}, 'submit')\r\n","import Helpers from '../modules/Helpers'\r\n\r\nclass StarRating {\r\n constructor(el) {\r\n this.input = document.getElementById(el.getAttribute('aria-controls'))\r\n\r\n this.stars = el.getElementsByClassName('star-rating__star')\r\n return this\r\n }\r\n\r\n activate() {\r\n this.listeners()\r\n\r\n return this\r\n }\r\n\r\n listeners() {\r\n new Helpers(this.stars).eventListener((e) => {\r\n const rating = parseInt(e.currentTarget.getAttribute('data-rating'))\r\n this.set(rating)\r\n return this\r\n })\r\n }\r\n\r\n clear() {\r\n new Helpers(this.stars).removeClass('star-rating__star--active')\r\n this.input.value = ''\r\n return this\r\n }\r\n\r\n set(rating) {\r\n this.clear()\r\n this.input.value = rating\r\n const activeStars = Array.from(this.stars).filter(star => parseInt(star.getAttribute('data-rating')) <= rating)\r\n new Helpers(activeStars).addClass('star-rating__star--active')\r\n return this\r\n }\r\n}\r\n\r\nArray.from(document.getElementsByClassName('star-rating')).forEach((el) => {\r\n new StarRating(el).activate()\r\n})\r\n\r\nexport default StarRating","import moment from 'moment'\r\nimport XHRPromise from 'xhr-promise'\r\nimport Helpers from '../modules/Helpers'\r\nimport Reveal from '../modules/Reveal'\r\nimport ButtonCounter from '../modules/ButtonCounter'\r\nimport CalendarRange from '../modules/CalendarRange'\r\nimport Map, { streetLayer } from '../modules/Map'\r\nimport GeoSearch from '../modules/GeoSearch'\r\n\r\nconst superSearch = document.getElementById('superSearch')\r\nconst superSearchImage = document.getElementById('superSearchImage')\r\nconst superSearchGroup = document.getElementById('superSearchGroup')\r\nconst superSearchToggles = document.getElementsByClassName('super-search__button')\r\nconst superSearchDropdowns = document.getElementsByClassName('super-search__dropdown')\r\nconst superSearchClose = document.getElementsByClassName('super-search__close')\r\n\r\n// Super search active classes\r\nconst superSearchActiveClass = 'super-search--active'\r\nconst dropdownActiveClass = 'super-search__dropdown--active'\r\nconst toggleActiveClass = 'super-search__button--active'\r\n\r\n// Toggle super search dropdown open and closed\r\n// when corresponding toggle button is clicked.\r\nnew Helpers(superSearchToggles).eventListener((e) => {\r\n e.preventDefault()\r\n\r\n const body = document.querySelector('body')\r\n const currentToggle = document.getElementById(e.currentTarget.id)\r\n const currentDropdownId = e.currentTarget.getAttribute('aria-controls')\r\n const currentDropdown = document.getElementById(currentDropdownId)\r\n const isShown = new Helpers(currentDropdown).hasClass(dropdownActiveClass)\r\n const targetIsSearchResult = new Helpers(e.target).getClosest('.geo-search-result')\r\n const isWhereSearchBox = currentToggle.id === 'whereToGoToggle' && (e.target.tagName.toLowerCase() === 'input' || targetIsSearchResult)\r\n\r\n if (!isShown) {\r\n // Hide all currently active dropdowns in order\r\n // to ensure only one is open at a time.\r\n new Helpers(superSearchDropdowns).removeClass(dropdownActiveClass)\r\n new Helpers(superSearchToggles).removeClass(toggleActiveClass)\r\n\r\n // Add active classes to super search elements\r\n // in order to display correct dropdown.\r\n new Helpers(superSearch).addClass(superSearchActiveClass)\r\n new Reveal(currentDropdown).show(dropdownActiveClass)\r\n new Helpers(currentToggle).addClass(toggleActiveClass)\r\n\r\n if (currentToggle.id === 'whereToGoToggle') {\r\n if (map === null) {\r\n getGeography()\r\n } else {\r\n map.map.invalidateSize()\r\n map.fitShapes()\r\n }\r\n }\r\n if (window.outerWidth <= 600) {\r\n new Helpers(body).addClass('no-scroll')\r\n }\r\n\r\n superSearch.setAttribute('data-active-dropdown', currentToggle.id)\r\n } else if (!isWhereSearchBox) {\r\n // Hide all currently active dropdowns\r\n new Helpers(superSearchDropdowns).removeClass(dropdownActiveClass)\r\n new Helpers(superSearchToggles).removeClass(toggleActiveClass)\r\n\r\n new Helpers(superSearch).removeClass(superSearchActiveClass)\r\n new Helpers(body).removeClass('no-scroll')\r\n superSearch.removeAttribute('data-active-dropdown')\r\n }\r\n})\r\n\r\n\r\nnew Helpers(superSearchClose).eventListener((e) => {\r\n e.preventDefault()\r\n\r\n new Helpers(superSearch).removeClass(superSearchActiveClass)\r\n new Reveal(superSearchDropdowns).hide(dropdownActiveClass)\r\n new Helpers(superSearchToggles).removeClass(toggleActiveClass)\r\n new Helpers(document.querySelector('body')).removeClass('no-scroll')\r\n})\r\n\r\n// Remove all super search active classes when\r\n// clicking off super search dropdowns to ensure\r\n// all dropdowns can be closed when no longer wanted.\r\nnew Helpers(superSearchImage).eventListener((e) => {\r\n e.preventDefault()\r\n\r\n new Helpers(superSearch).removeClass(superSearchActiveClass)\r\n new Reveal(superSearchDropdowns).hide(dropdownActiveClass)\r\n new Helpers(superSearchToggles).removeClass(toggleActiveClass)\r\n})\r\n\r\n\r\n// Stop super search group children events\r\n// bubbling after their own handler executes.\r\nnew Helpers(superSearchGroup).eventListener((e) => {\r\n e.stopPropagation()\r\n})\r\n\r\nif (superSearch !== null) {\r\n (() => {\r\n const rootLocationRegionCode = superSearch.querySelector('.super-search__areas').getAttribute(\"data-root-location\")\r\n let activeFetch = -1\r\n let activeGeoFetch = -1\r\n let locationModified = false\r\n const regions = superSearch.getElementsByClassName('location-region')\r\n const allTowns = superSearch.getElementsByClassName('super-search__towns')[0]\r\n const distance = document.getElementsByName('distance')[0]\r\n\r\n new Helpers(superSearch.getElementsByTagName('form')).eventListener((e) => {\r\n e.preventDefault()\r\n\r\n if (destinationUrl() !== '') {\r\n window.location.href = destinationUrl()\r\n }\r\n }, 'submit')\r\n\r\n let destinationUrl = (mapView = false) => {\r\n const params = getParams()\r\n const setParams = Object.keys(params).length\r\n\r\n let destination = ''\r\n if (mapView) {\r\n return `/results.aspx?search=Y&view=map&${queryString(params)}`\r\n }\r\n if (setParams === 1 && params.rgn !== undefined) {\r\n // use region url\r\n destination = Array.from(regions).filter(r => r.getAttribute('data-value') === params.rgn)[0].getElementsByTagName('a')[0].href\r\n } else if (setParams === 2 && params.nlc !== undefined) {\r\n destination = `/holiday-cottages-${params.nlc.toLowerCase()}.html`\r\n } else if (setParams === 0) {\r\n destination = Array.from(regions).filter(r => r.getAttribute('data-value') === rootLocationRegionCode)[0].getElementsByTagName('a')[0].href\r\n } else {\r\n destination = `/results.aspx?search=Y&${queryString(params)}`\r\n }\r\n return destination\r\n\r\n }\r\n\r\n new Helpers(regions).eventListener((e) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n let featuredIds = []\r\n if (e.target.getAttribute('data-toptowns') !== null) {\r\n featuredIds = e.target.getAttribute('data-toptowns').split(\",\")\r\n } else if (e.target.parentElement.getAttribute('data-toptowns') !== null) {\r\n featuredIds = e.target.parentElement.getAttribute('data-toptowns').split(\",\")\r\n }\r\n\r\n new Helpers(regions).setAttributes({ 'aria-current': false })\r\n new Helpers(e.currentTarget).setAttributes({ 'aria-current': true })\r\n\r\n // clear selected town\r\n const allTownsList = allTowns.querySelectorAll('.super-search__towns .location-town')\r\n new Helpers(allTownsList).setAttributes({ 'aria-current': false, 'data-featured': false, 'aria-hidden': true })\r\n\r\n const currentRegion = document.querySelector('.location-region[aria-current=\"true\"]')\r\n\r\n ShowChildLocations(currentRegion.getAttribute('data-id'))\r\n\r\n Array.from(allTownsList).forEach((town) => {\r\n let id = town.getAttribute('data-id').toString()\r\n if (featuredIds.includes(id)) {\r\n new Helpers(town).setAttributes({ 'data-featured': true, 'aria-hidden': false })\r\n }\r\n })\r\n document.getElementsByClassName('map-link')[0].setAttribute('href', destinationUrl(true))\r\n document.getElementsByClassName('super-search__select-location')[0].setAttribute('aria-expanded', false)\r\n\r\n locationModified = true\r\n runSearch()\r\n })\r\n\r\n let ShowChildLocations = (currentID) => {\r\n if (currentID !== '' && currentID !== null && typeof currentID !== 'undefined') {\r\n var childItems = document.querySelectorAll('.location[data-parent-id=\"' + currentID + '\"]')\r\n Array.from(childItems).forEach((ci) => {\r\n if (new Helpers(ci).hasClass('location-town')) {\r\n ci.setAttribute('aria-hidden', false)\r\n }\r\n ShowChildLocations(ci.getAttribute('data-id'))\r\n })\r\n\r\n }\r\n }\r\n\r\n new Helpers(allTowns).eventListener((e) => {\r\n e.preventDefault()\r\n const li = new Helpers(e.target).getClosest('li')\r\n if (li !== undefined) {\r\n new Helpers(allTowns.getElementsByClassName('location-town')).setAttributes({ 'aria-current': false })\r\n new Helpers(li).setAttributes({ 'aria-current': true })\r\n distance.value = li.getAttribute('data-distance')\r\n distance.dispatchEvent(new CustomEvent('sync'))\r\n\r\n locationModified = true\r\n runSearch()\r\n }\r\n })\r\n\r\n let whereSearch = document.getElementsByClassName('super-search__where-search')[0]\r\n let whereResults = document.getElementsByClassName('super-search__where-results')[0]\r\n let geoSearch = new GeoSearch(whereSearch, whereResults, {\r\n placeholderText: {\r\n focus: 'Enter place or cottage name...',\r\n blur: 'Where would you like to go'\r\n }\r\n })\r\n new Helpers(document.getElementsByClassName('super-search__towns-toggle')).eventListener((e) => {\r\n e.preventDefault()\r\n e.stopPropagation()\r\n new Helpers(document.getElementsByClassName('super-search__select-location')).toggleAttribute('aria-expanded')\r\n })\r\n\r\n new Helpers(whereSearch).eventListener((e) => {\r\n e.stopPropagation()\r\n e.preventDefault()\r\n }, 'keyup')\r\n\r\n geoSearch.on('select', (data) => {\r\n switch (data.type) {\r\n case 'Region':\r\n Array.from(regions).find(region => region.getAttribute('data-value') == data.code).click()\r\n break\r\n case 'Location':\r\n Array.from(allTowns.getElementsByClassName('location-town')).find(town => town.getAttribute('data-code') == data.code).click()\r\n break\r\n case 'Cottage':\r\n window.location = data.url\r\n break\r\n default:\r\n // no action\r\n }\r\n geoSearch.clearResults()\r\n })\r\n\r\n geoSearch.on('invalid', (value) => {\r\n if (value.length === 0) {\r\n // set back to UK country\r\n Array.from(regions).filter(r => r.getAttribute('data-value') === rootLocationRegionCode)[0].click()\r\n } else {\r\n // partial value present, reset the buttons\r\n updateButtons(getParams())\r\n }\r\n })\r\n\r\n const guestControls = document.getElementsByClassName('super-search__select-guests-control')\r\n const calendarContainer = document.getElementsByClassName('calendar-range')[0]\r\n const durationInput = document.getElementsByName('duration')[0]\r\n const startDateInput = document.getElementsByName('startDate')[0]\r\n const endDateInput = document.getElementsByName('endDate')[0]\r\n const flexWrap = document.getElementsByClassName('super-search__flex')[0]\r\n const flexInput = document.getElementsByName('flex')[0]\r\n const clearDatesButton = document.getElementsByClassName('super-search__dates-clear')[0]\r\n \r\n // initialise the guest picker controls\r\n Array.from(guestControls).forEach(control => new ButtonCounter(control))\r\n new Helpers(guestControls).eventListener(() => {\r\n runSearch()\r\n }, 'change')\r\n\r\n let calcNoOfCalendars = () => {\r\n return window.outerWidth <= 1050 ? 1 : 2\r\n }\r\n\r\n let noOfCalendars = calcNoOfCalendars()\r\n\r\n window.addEventListener('resize', () => {\r\n if (noOfCalendars !== calcNoOfCalendars()) {\r\n noOfCalendars = calcNoOfCalendars()\r\n calendar.setNoOfCalendars(noOfCalendars).render()\r\n }\r\n })\r\n\r\n // initialise the calendar\r\n let calendar = new CalendarRange(calendarContainer, (startDate, endDate) => {\r\n startDateInput.value = ''\r\n endDateInput.value = ''\r\n if (startDate !== null) {\r\n startDateInput.value = startDate.format('ddd D MMM YYYY')\r\n\r\n let duration = parseInt(durationInput.value, 10)\r\n\r\n if (endDate !== null) {\r\n endDateInput.value = endDate.format('ddd D MMM YYYY')\r\n\r\n if (duration === 0 || endDate.diff(startDate, 'days') < duration) {\r\n // duration hasn't been set or is no longer valid for date range,\r\n // infer it from the dates\r\n duration = Math.min(Math.max(endDate.diff(startDate, 'days'), 2), 7)\r\n durationInput.value = duration\r\n }\r\n\r\n if (endDate.diff(startDate, 'days') < duration) {\r\n calendar.setDates(startDate, moment(startDate).add(duration, 'days'))\r\n }\r\n }\r\n\r\n flexWrap.setAttribute('aria-hidden', false)\r\n clearDatesButton.setAttribute('aria-hidden', false)\r\n }\r\n\r\n runSearch()\r\n })\r\n calendar.setNoOfCalendars(calcNoOfCalendars()).render()\r\n\r\n // duration\r\n new Helpers(durationInput).eventListener(() => {\r\n const startDate = moment(startDateInput.value, 'ddd D MMM YYYY')\r\n const endDate = moment(endDateInput.value, 'ddd D MMM YYYY')\r\n const duration = parseInt(durationInput.value, 10)\r\n\r\n if (!startDate.isValid() || !endDate.isValid()) {\r\n // dates not set, no need to execute validation here\r\n runSearch()\r\n return\r\n } else if (duration === 0) {\r\n // dates are set and no duration provided, do so...\r\n const impliedDuration = Math.max(endDate.diff(startDate, 'days'), 2)\r\n durationInput.value = impliedDuration\r\n }\r\n\r\n if (endDate.diff(startDate, 'days') < duration) {\r\n calendar.setDates(startDate, moment(startDate).add(duration, 'days'))\r\n }\r\n runSearch()\r\n }, 'change')\r\n\r\n new Helpers(flexInput).eventListener(() => {\r\n runSearch()\r\n })\r\n // reset dates input button click\r\n new Helpers(clearDatesButton).eventListener((e) => {\r\n e.preventDefault()\r\n durationInput.value = 0\r\n startDateInput.value = ''\r\n endDateInput.value = ''\r\n calendar.setDates(null, null)\r\n clearDatesButton.setAttribute('aria-hidden', true)\r\n flexWrap.setAttribute('aria-hidden', true)\r\n runSearch()\r\n })\r\n\r\n let getParams = () => {\r\n const params = {}\r\n const region = superSearch.querySelector('.super-search__areas li[aria-current=\"true\"]')\r\n const town = superSearch.querySelector('.super-search__locations li[aria-current=\"true\"]')\r\n const distance = parseInt(document.getElementsByName('distance')[0].value, 10)\r\n const duration = parseInt(document.getElementsByName('duration')[0].value, 10)\r\n const startDate = document.getElementsByName('startDate')[0].value\r\n const endDate = document.getElementsByName('endDate')[0].value\r\n const flex = parseInt(document.getElementsByName('flex')[0].value, 10)\r\n const adults = parseInt(document.getElementsByName('adults')[0].value, 10)\r\n const children = parseInt(document.getElementsByName('children')[0].value, 10)\r\n const babies = parseInt(document.getElementsByName('babies')[0].value, 10)\r\n const pets = parseInt(document.getElementsByName('pets')[0].value, 10)\r\n\r\n if (town !== null) {\r\n params.nlc = town.getAttribute('data-url-name')\r\n params.nlm = distance\r\n } else if (region.getAttribute('data-value') !== rootLocationRegionCode) {\r\n params.rgn = region.getAttribute('data-value')\r\n }\r\n if (duration > 0) {\r\n params.nday = duration\r\n }\r\n if (startDate !== '') {\r\n params.dss = startDate\r\n if (endDate !== '') {\r\n params.des = endDate\r\n } else if (duration > 0) {\r\n params.des = moment(startDate, 'ddd D MMM YYYY').add(duration, 'days').format('ddd D MMM YYYY')\r\n }\r\n if (flex !== 3) {\r\n params.flex = flex\r\n }\r\n }\r\n if (duration > 0 && startDate !== '' && endDate !== '') {\r\n // Store search dates for use on cottage availability form.\r\n try {\r\n const dates = {\r\n dss: moment(startDate, 'ddd D MMM YYYY').toISOString(true),\r\n des: moment(endDate, 'ddd D MMM YYYY').toISOString(true),\r\n nday: duration,\r\n }\r\n sessionStorage.setItem('searchDates', JSON.stringify(dates))\r\n } catch (e) {\r\n console.log(e)\r\n }\r\n }\r\n\r\n if (adults > 1) params.minA = adults\r\n if (children > 0) params.minC = children\r\n if (babies > 0) params.minB = babies\r\n if (pets > 0) params.pet = pets\r\n\r\n return params\r\n }\r\n\r\n let updateButtons = (params) => {\r\n const guests = (params.minA || 1) + (params.minC || 0) + (params.minB || 0)\r\n const pets = params.pet || 0\r\n\r\n // Where\r\n let whereTitle = ''\r\n if (params.nlc !== undefined) {\r\n whereTitle = allTowns.querySelector(`li[data-url-name=\"${params.nlc}\"]`).getAttribute('data-title')\r\n } else if (params.rgn !== undefined) {\r\n whereTitle = Array.from(regions).filter(r => r.getAttribute('data-value') === params.rgn)[0].getAttribute('data-title')\r\n }\r\n geoSearch.setValue(whereTitle)\r\n\r\n // When\r\n const howLongTitle = params.nday > 0 ? document.getElementsByName('duration')[0].querySelector('option:checked').innerText : ''\r\n const howLongSub = params.nday > 0 && params.des !== undefined ? moment(params.dss, 'ddd D MMM YYYY').format('DD/MM/YYYY') + ' - ' + moment(params.des, 'ddd D MMM YYYY').format('DD/MM/YYYY') : ''\r\n setButton('howLongToggle', params.nday > 0, howLongTitle, howLongSub)\r\n\r\n // Guests\r\n const guestsTitle = `${guests} Guest${guests !== 1 ? 's' : ''}`\r\n const guestsSub = `${pets} Dog${pets !== 1 ? 's' : ''}`\r\n setButton('noOfGuestsToggle', guests > 1 || pets > 0, guestsTitle, guestsSub)\r\n }\r\n\r\n\r\n let setButton = (id, set, title, sub) => {\r\n const buttonSetClass = 'super-search__button--set'\r\n const titleClass = 'super-search__button-text-title'\r\n const subClass = 'super-search__button-text-sub'\r\n const button = document.getElementById(id)\r\n if (set) {\r\n new Helpers(button).addClass(buttonSetClass)\r\n button.getElementsByClassName(titleClass)[0].innerText = title\r\n button.getElementsByClassName(subClass)[0].innerText = sub\r\n } else {\r\n new Helpers(button).removeClass(buttonSetClass)\r\n button.getElementsByClassName(titleClass)[0].innerText = ''\r\n button.getElementsByClassName(subClass)[0].innerText = ''\r\n }\r\n }\r\n\r\n let queryString = params => Object.keys(params)\r\n .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)\r\n .join('&')\r\n\r\n let getGeography = () => {\r\n const region = superSearch.querySelector('.super-search__areas li[aria-current=\"true\"]')\r\n const town = superSearch.querySelector('.super-search__locations li[aria-current=\"true\"]')\r\n\r\n let locationId = 0\r\n let locationDistance = -1\r\n\r\n if (town !== null) {\r\n locationId = town.getAttribute('data-id')\r\n locationDistance = parseInt(document.getElementsByName('distance')[0].value, 10)\r\n } else if (region !== null) {\r\n locationId = region.getAttribute('data-id')\r\n }\r\n\r\n if (activeGeoFetch !== -1) {\r\n activeGeoFetch.getXHR().abort()\r\n }\r\n\r\n activeGeoFetch = new XHRPromise()\r\n activeGeoFetch.send({\r\n url: `/feeds/geography.aspx?locationId=${locationId}&distance=${locationDistance}`,\r\n })\r\n .then(response => (new window.DOMParser()).parseFromString(response.responseText, 'text/xml'))\r\n .then((data) => {\r\n const location = data.getElementsByTagName('location')\r\n if (location.length > 0) {\r\n updateMap(location[0].getAttribute('wkt'))\r\n }\r\n activeGeoFetch = -1\r\n }).catch((error) => {\r\n if (error.reason !== 'abort') {\r\n console.log(error)\r\n }\r\n })\r\n }\r\n\r\n let runSearch = () => {\r\n Array.from(superSearch.querySelectorAll('button[name=\"submit\"]')).forEach(b => b.innerHTML === '...')\r\n const params = getParams()\r\n updateButtons(params)\r\n\r\n if (locationModified) {\r\n getGeography()\r\n }\r\n\r\n const feedEndpoint = `/feeds/resultsFeed.aspx?search=Y&${queryString(params)}`\r\n\r\n if (activeFetch !== -1) {\r\n activeFetch.getXHR().abort()\r\n }\r\n\r\n activeFetch = new XHRPromise()\r\n activeFetch.send({\r\n url: feedEndpoint,\r\n })\r\n .then(response => (new window.DOMParser()).parseFromString(response.responseText, 'text/xml'))\r\n .then((data) => {\r\n updateUI(data)\r\n activeFetch = -1\r\n }).catch((error) => {\r\n if (error.reason !== 'abort') {\r\n console.log(error)\r\n }\r\n })\r\n }\r\n\r\n let updateUI = (data) => {\r\n const total = parseInt(data.getElementsByTagName('total')[0].getAttribute('total'), 10)\r\n\r\n // set total\r\n Array.from(superSearch.querySelectorAll('button[name=\"submit\"]')).forEach(b => b.innerHTML = `Search ${total} cottage${total !== 1 ? 's' : ''}`)\r\n\r\n // set regions\r\n Array.from(regions).forEach((region) => {\r\n const code = region.getAttribute('data-value')\r\n const regionTotal = data.getElementsByTagName('regionTotals')[0].getAttribute(code.toLowerCase())\r\n region.getElementsByTagName('span')[0].innerText = regionTotal\r\n })\r\n }\r\n\r\n\r\n let map = null\r\n\r\n let initMap = () => {\r\n if (map === null) {\r\n map = new Map('superSearchMap', {\r\n coords: [50.1016, -5.2750],\r\n zoom: 9,\r\n minZoom: 4,\r\n maxZoom: 14,\r\n zoomControl: false,\r\n attributionControl: false,\r\n dragging: false,\r\n doubleClickZoom: false,\r\n scrollWheelZoom: false,\r\n }, {\r\n LocationMap: streetLayer(),\r\n })\r\n }\r\n }\r\n let updateMap = (wkt) => {\r\n initMap()\r\n map.map.invalidateSize()\r\n map\r\n .clearShapes()\r\n .addShape(wkt, { color: '#29abe3' })\r\n .fitShapes()\r\n }\r\n })()\r\n}\r\n","import Helpers from '../modules/Helpers'\r\n\r\nconst activeClass = 'livechat--active';\r\nconst panels = document.getElementsByClassName('livechat__panel');\r\nconst startChat = document.getElementsByClassName('livechat__start');\r\n\r\nwindow.Tawk_API = window.Tawk_API || {};\r\n\r\n// Show header icon if we have chat agents online\r\nwindow.Tawk_API.onStatusChange = function (status) {\r\n \r\n if (status === 'online') {\r\n new Helpers(panels).addClass(activeClass)\r\n } else {\r\n new Helpers(panels).removeClass(activeClass)\r\n }\r\n};\r\n\r\n// Toggle open/close of the chat widget\r\nnew Helpers(startChat).eventListener(() => {\r\n window.Tawk_API.toggle()\r\n})\r\n\r\n\r\nfunction handleNewsletterSignup(data) {\r\n try {\r\n // If label with \"Newsletter\" is present in the data, we've a positive newsletter sign up\r\n const newsLetterSignUp = data.some(item => item.label === \"Newsletter\");\r\n if (newsLetterSignUp) {\r\n const endpoint = '/ajax/newsletter.aspx';\r\n const formDataObj = {};\r\n\r\n // Iterate through the details array and populate the formDataObj\r\n data.forEach(item => {\r\n formDataObj[item.label.toLowerCase()] = item.answer;\r\n });\r\n\r\n // insert a source property\r\n formDataObj['source'] = 'tawk';\r\n formDataObj['list-name'] = 'chatBoxSignUp';\r\n\r\n // Convert the form data object to a URL-encoded string\r\n const urlEncodedData = new URLSearchParams(formDataObj).toString();\r\n\r\n // Create a URLSearchParams object with a \"data\" field containing the encoded form data\r\n const formBody = new URLSearchParams();\r\n formBody.append('data', urlEncodedData);\r\n\r\n // Fetch POST request to endpoint (we're not really bothered about the response, but commented out for ease of testing)\r\n fetch(endpoint, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/x-www-form-urlencoded'\r\n },\r\n body: formBody.toString()\r\n });\r\n //.then(response => {\r\n // if (!response.ok) {\r\n // throw new Error('Network response was not ok');\r\n // }\r\n // console.log('Newsletter signup details sent successfully');\r\n //})\r\n //.catch(error => {\r\n // console.error('Error sending newsletter signup details:', error.message);\r\n //});\r\n }\r\n } catch (error) {\r\n //console.error(error);\r\n }\r\n}\r\n\r\nwindow.Tawk_API.onPrechatSubmit = function (data) {\r\n handleNewsletterSignup(data);\r\n};\r\n\r\nwindow.Tawk_API.onOfflineSubmit = function (data) {\r\n handleNewsletterSignup(data.questions);\r\n};","import tippy from 'tippy.js'\r\nimport he from 'he'\r\n\r\n//tippy('button[aria-label=\"Information\"]', {\r\n// theme: 'light classic',\r\n// animation: 'fade',\r\n// placement: 'right',\r\n// interactive: true, // able to hover over and click in tooltip\r\n//})\r\n\r\n//// set up for cottage page too.\r\n//tippy('.favourite-button', {\r\n// //theme: 'light classic shortlist',\r\n// theme: 'light',\r\n// animation: 'fade',\r\n// allowHTML: true,\r\n// trigger: 'click',\r\n// interactive: true,\r\n// onShow(reference) {\r\n// let html = ''\r\n// const heart = ''\r\n// const code = reference.reference.getAttribute('data-code')\r\n// if (window.sl.shortlists.length > 0) {\r\n// window.sl.shortlists.forEach((shortlist) => {\r\n// let active = 'false'\r\n// if (shortlist.items.find(i => i.value === code)) { active = 'true' }\r\n// html += `
${he.encode(shortlist.name)} ${heart}
Default ${heart}
Create new shortlist
${he.encode(shortlist.name)} ${heart}
Default ${heart}
Create new shortlist
${cottage.name}
\r\n ${cottage.type === 'X' ? '' : `${cottage.rent}
`}\r\n${cottage.accom}
\r\n${cottage.pets > 0 ? 'Dogs welcome' : 'Regret no pets'}
\r\n