{"version":3,"file":"Src_Scripts_components_events-filterable-map_js.12c0f760f29718dff390.js","sources":["webpack://haveselskabet/./Src/Scripts/components/events-filterable-map.js","webpack://haveselskabet/./Src/Scripts/functionality-classes/filterable-map.js"],"sourcesContent":["import FilterableMap from '../functionality-classes/filterable-map';\nimport ComponentRepository from '../architecture/componentRepository';\n\n/*\n This class is an example of how you could use the FilterableMap base component specifically for the client site we are building.\n This class implements some domain specific logic and passes it as arguments to a FilterableMap instance.\n*/\n\nexport default class EventsFilterableMap {\n constructor(elm, args) {\n // We can specify our own component repository for map locations and pass to a map.\n // If not the map will use the default component repository initialized in index.js\n // Specifying the repository like this is useful if we don't want to pollute the global repository with map specific stuff.\n const mapArgs = { ...args };\n // mapArgs.mapClassRepository = new ComponentRepository({\n // 'eventPage': () => import('../models/map-location-types/map-location.js'),\n // });\n\n // Required function for the FilterableMap with the filtering logic\n // This method is used by the FilterableMap to determine if a MapLocation matches the filter everytime the filter changes\n const isLocationMatch = function (locationItem, filterData) {\n return locationItem.filterMatch ? locationItem.filterMatch(filterData, mapArgs.inputNameMapping, mapArgs.sizeMapping) : false;\n };\n\n // Required function for the FilterableMap that specifies how list items are rendered in the FilterableMap list\n const renderListItem = function (locationItem) {\n return new Promise(resolve => {\n fetch(`/umbraco/surface/maps/RenderListView?node=${locationItem.nodeId}`)\n .then(x => x.text())\n .then(text => {\n const boxText = document.createElement('div');\n boxText.classList.add('col-12');\n boxText.classList.add('col-md-6');\n boxText.innerHTML = text;\n resolve(boxText);\n });\n });\n };\n\n // Optional onFilterChange function that can be passed to the FilterableMap to override the default change event handler for the form.\n // This can be beneficial if we want to throttle or debounce the event handler or otherwise have more control.\n /*\n const onFilterChange = function (e, formElem, renderFilterResult) {\n console.log('onFormChange', e);\n let filteredLocations = [];\n let filterData = new FormData(formElem);\n\n args.locations.forEach(l => {\n if (isLocationMatch(l, filterData)) {\n filteredLocations.push(l);\n }\n });\n\n renderFilterResult(filteredLocations);\n }\n */\n\n this.filterableMap = new FilterableMap(elm, {\n map: mapArgs,\n isLocationMatch: isLocationMatch,\n renderListItem: renderListItem,\n // onFilterChange: onFilterChange, // A onFilterChange property can be specified for further control of how the filteredLocations list is generated.\n });\n\n const backdrop = elm.querySelector('.filterable-map__backdrop');\n if (backdrop) {\n backdrop.addEventListener('click', () => {\n const activeDropdown = document.querySelector('.generic-dropdown--active');\n if (activeDropdown) {\n backdrop.classList.remove('filterable-map__backdrop--active');\n activeDropdown.classList.remove('generic-dropdown--active');\n }\n });\n }\n }\n}\n","import Map from '../components/map';\nimport MediaSwiper from '../components/media-swiper';\n\n/*\n The FilterableMap class implements filtering logic and applies it to an instance of the Map class.\n This class is NEVER MEANT to be instantiated as a component itthis, but should be utilized by a\n component with domain specific logic like the DomainFilterableMap in the example-components folder.\n It expects the markup to consist of a list of locations, a form with filtering inputs, and a map.\n Updating the form, filters the locations in the list and the map.\n\n ARGS:\n -map : object that contains args for Map component\n -inputNameMapping Mapping of if of input fields to the name that should be showed in the tag.\n -sizeMapping Mapping of selectable string sizes to the range they hold\n -isLocationMatch : function(locationItem, filterData) that is invoked for each location whenever the filter form changes. The function must return either true or false to indicate wether the location matches the filter or not.\n -renderListItem : function(locationItem) is invoked for each location when the list of matching locations is rendered. Should return the markup to be rendered to the current location.\n -onFilterChange (optional) : function(event, formElem, renderFilterResult) is invoked when the form changes. If this callback function is specified the isLocationMatch method is not invoked and it is up to the function this to loop all locations, find those who match the filter, and then invoke renderFilterResult with the result.\n*/\nexport default class FilterableMap {\n constructor(elm, args) {\n this.args = args;\n this.map = args.map;\n this.isLocationMatch = args.isLocationMatch;\n this.renderListItem = args.renderListItem;\n this.el = elm;\n this.listElem = elm.querySelector('[data-filterable-map=\"list\"]');\n this.formElem = elm.querySelector('[data-filterable-map=\"form\"]');\n this.mapElem = elm.querySelector('[data-filterable-map=\"map\"]');\n\n this.allLocations = this.map.typedLocations || this.map.locations;\n this.amountContainer = elm.querySelector('[data-filter-amount]');\n this.tagContainer = elm.querySelector('[data-tag-container]');\n this.resetFilter = elm.querySelector('[data-reset-filter]');\n this.pagination = elm.querySelector('[data-filterable-map-pagination]');\n this.tagOuterContainer = elm.querySelector('.filter-bar__tag-container');\n this.latestLocations = null;\n this.pageSize = 6;\n this.saveAndCloseButton = elm.querySelector('.filter-bar__save-and-close--bottom');\n this.formElem.addEventListener('change', this.onFormEvent.bind(this));\n this.formElem.addEventListener('submit', this.onFormEvent.bind(this));\n\n this.saveAndCloseButton.addEventListener('click', () => {\n this.scrollToFilterBar();\n });\n\n this.pagination.addEventListener('click', x => {\n const pageNumber = x.target.closest('[data-page]');\n if (pageNumber) {\n this.renderList(this.latestLocations, pageNumber.dataset.page);\n }\n this.scrollToFilterBar();\n });\n\n // If we are not passed a list of typedLocations, construct a list of typed locations, and use that from now on.\n if (!this.map.typedLocations) {\n const untypedLocations = this.map.locations;\n // Instantiate a map without locations\n this.map.locations = null;\n this.map.updateListView = this.renderList;\n this.mapInstance = new Map(this.mapElem, this.map);\n\n // Use the map instance to construct instances of all map locations\n this.mapInstance.instantiateAllMapLocations(untypedLocations)\n .then(typedLocations => {\n // Save the constructed typed locations, and render them to map and list\n this.allLocations = typedLocations;\n this.renderTypedLocations(typedLocations, args);\n });\n } else {\n this.mapInstance = new Map(this.mapElem, this.map);\n this.renderList(this.allLocations);\n }\n // todo - this is a hack to make the map update when the filter changes. It should be done in a better way.\n window.addEventListener('click', x => {\n const closest = x.target.closest('[data-input-id]');\n if (closest) {\n this.resetInput(closest);\n this.defaultOnFilterChange();\n }\n });\n\n this.resetFilter.addEventListener('click', () => {\n let tag = this.tagContainer.querySelector('[data-input-id]');\n while (tag) {\n this.resetInput(tag);\n tag = this.tagContainer.querySelector('[data-input-id]');\n }\n\n this.defaultOnFilterChange();\n });\n }\n\n getTagName(name, value) {\n switch (name) {\n case 'from-range':\n return `Min ${Math.round(value)} m2`;\n case 'to-range':\n return `Max ${Math.round(value)} m2`;\n case 'from-period':\n return `Fra d. ${value}`;\n case 'to-period':\n return `Til d. ${value}`;\n default:\n return this.map.inputNameMapping[name].Name;\n }\n }\n\n updateTags(filterData) {\n let tagHtml = '';\n this.tagOuterContainer.classList.toggle('filter-bar__tag-container--empty', filterData.length === 0);\n for (const pair of filterData) {\n const name = this.getTagName(pair[0], pair[1]);\n tagHtml += `
${name}\n \n \n \n \n
`;\n }\n // If a period picker tag was deleted, remove the styling from the period picker input field\n const filledFromPicker = document.querySelector('.period-picker__from-period--filled');\n const filledToPicker = document.querySelector('.period-picker__to-period--filled');\n const filledFromCloseIcon = document.querySelector('.period-picker__input-container--from .period-picker__close-button--filled');\n const filledToCloseIcon = document.querySelector('.period-picker__input-container--to .period-picker__close-button--filled');\n // Check if the period pickers has been active previously\n if (filledFromPicker && filledFromCloseIcon && !filterData.find(x => x.includes('from-period'))) {\n filledFromPicker.classList.remove('period-picker__from-period--filled');\n filledFromCloseIcon.classList.remove('period-picker__close-button--filled');\n\n }\n if (filledToPicker && filledToCloseIcon && !filterData.find(x => x.includes('to-period'))) {\n filledToPicker.classList.remove('period-picker__to-period--filled');\n filledToCloseIcon.classList.remove('period-picker__close-button--filled');\n }\n\n this.tagContainer.innerHTML = tagHtml;\n }\n\n // Whenever the form changes, invoke isLocationMatch on each location and render the matching locations on the map and in the list.\n defaultOnFilterChange() {\n const filteredLocations = [];\n const filterData = new FormData(this.formElem);\n this.updateTags([...filterData.entries()].filter(x => x[1] !== ''));\n\n this.allLocations.forEach(l => {\n if (this.isLocationMatch && this.isLocationMatch(l, filterData)) {\n filteredLocations.push(l);\n }\n });\n\n this.renderTypedLocations(filteredLocations, this.args); // todo\n }\n\n onFormEvent(e) {\n e.preventDefault();\n\n if (this.onFilterChange) {\n this.onFilterChange(e, this.formElem, this.renderFilterResult);\n } else {\n this.defaultOnFilterChange(e);\n }\n }\n resetInput(inputTag) {\n const input = this.el.querySelector(`[id=\"${inputTag.dataset.inputId}\"]`);\n\n if (!input) { return; }\n\n if (input.type === 'checkbox') {\n input.checked = false;\n input.dispatchEvent(new Event('change', { bubbles: true }));\n }\n if (input.type === 'text') {\n input.value = '';\n input.dispatchEvent(new Event('change', { bubbles: true }));\n input.dispatchEvent(new Event('reset', { bubbles: false }));\n }\n\n if (!inputTag.parentNode) { return; }\n\n inputTag.parentNode.removeChild(inputTag);\n }\n\n\n renderList(newLocations, page = 1) {\n const parsedPage = parseInt(page, 10);\n this.latestLocations = newLocations;\n if (this.renderListItem) {\n const startIndex = this.pageSize * (parsedPage - 1);\n const endIndex = this.pageSize * parsedPage;\n\n this.fetchAllListViews(newLocations, startIndex, endIndex, this.listElem);\n // const pages = Array(Math.ceil(newLocations.length / pageSize)).fill();\n\n if (newLocations.length > 0) {\n fetch(`/umbraco/api/pagination/GetPagination?totalItems=${newLocations.length}&pageSize=${this.pageSize}&paginationParametrer=123¤tPageIndex=${parsedPage}&indexText=${this.map.type.toLowerCase()}`)\n .then(x => x.json())\n .then(json => this.pagination.innerHTML = json.paginationHtml);\n } else {\n this.pagination.innerHTML = '
Ingen resultater inden for denne filtrering
';\n }\n }\n }\n /*\n Render an array of instances of MapLocation or subclasses thereof.\n */\n renderTypedLocations(locations, args) {\n this.amountContainer.innerHTML = locations.length;\n this.renderList(locations);\n this.mapInstance.setTypedLocations(locations, args);\n }\n\n getOffset(el) {\n const rect = el.getBoundingClientRect();\n return {\n left: rect.left + window.scrollX,\n top: rect.top + window.scrollY,\n };\n }\n scrollToFilterBar() {\n const filterBar = this.el.querySelector('.filter-bar');\n const filterBarPos = this.getOffset(filterBar);\n if (document.body.offsetWidth < 992) {\n window.scrollTo(0, filterBarPos.top - 130);\n } else {\n window.scrollTo(0, filterBarPos.top - 90);\n }\n }\n\n async fetchAllListViews(newLocations, startIndex, endIndex, listElem) {\n const resultArray = await Promise.all([...newLocations.slice(startIndex, endIndex).map(i => this.renderListItem(i))]);\n listElem.innerHTML = null;\n\n for (let i = 0; i < resultArray.length; i++) {\n listElem.appendChild(resultArray[i]);\n }\n listElem.querySelectorAll('[data-component=\"media-swiper\"]').forEach((x) => {\n new MediaSwiper(x, { style: 2 });\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;;A;;;;;;;;;;;;;;;;;;;;AC3EA;AACA;AAAA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAAA;AAAA;AAEA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AACA;AAEA;AAAA;AAAA;AAEA;AACA;AAGA;AAAA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AAAA;AAAA;AACA;AACA;AACA;AAAA;;A;;A","sourceRoot":""}