PageSourceSearch

https://alfredbakker.nl/wp-content/plugins/tp-extensions/assets/script/makelaars_plugin.js?ver=1.0

js alfredbakker.nl collected 2026-10-08 05:01:55 UTC 21,379 bytes, 663 lines download raw bytes

1var woningGrid;
2var isLoading = false;
3var hasMoreData = true;
4var page = 1;
5var loadingType = 'load';
6// hardcoded for now. Can be dynamic based on user selection
7var searchType = 'koop';
8var isMapMode = false;
9var leafletMap = null;
10
11var geoData = [];
12var markers = {};
13
14var markerCluster = L.markerClusterGroup({
15    removeOutsideVisibleBounds: true,
16    spiderfyOnMaxZoom: false,
17    showCoverageOnHover: false,
18    zoomToBoundsOnClick: true,
19    animate: false,
20    animateAddingMarkers: false,
21    maxClusterRadius: 80,
22});
23
24/**
25 * Default model for loading or filtering properties
26 */
27var loadPropertiesData = {
28    'action': 'load_properties',
29    'http_referer': window.location.href,
30    'args': {
31        'page': page,
32        'filters': [
33            {
34                'key': 'overdracht',
35                'action': 'BETWEEN_AND',
36                'node': (searchType == 'koop') ?  'koopprijs' : 'huurprijs',
37                'value': {
38                    'from': 0,
39                    'to': 10000000
40                }
41            },
42            {
43                'key': 'overdracht',
44                'node': 'status',
45                'action': '=',
46                'value': ['BESCHIKBAAR','VERKOCHT_ONDER_VOORBEHOUD']
47            }
48        ],
49        'sortby': {
50            'overdracht': {
51                'node': 'koopprijs',
52                'order': 'DESC',
53                'type': 'number'
54            },
55        }
56    },
57};
58
59document.addEventListener('DOMContentLoaded', () => {
60    console.log('DOM fully loaded and parsed');
61
62    const menu = document.querySelector('.header-custom');
63    let lastScrollY   = window.scrollY;
64    window.addEventListener('scroll', () => {
65        const y = window.scrollY;
66        const treshhold     = 8;
67        const topGuard      = 50;
68
69        if (y <= topGuard) {
70            menu.classList.remove('hide');
71            lastScrollY = y;
72            return;
73        }
74
75        if (window.scrollY > lastScrollY) {
76            // scrolling down
77            menu.classList.add('hide');
78            menu.parentElement.style.boxShadow = 'unset';
79        } else {
80            // scrolling up
81            menu.classList.remove('hide');
82        }
83
84        lastScrollY = window.scrollY;
85    });
86    
87    // Ensure we are on the correct page
88    const initPage = document.querySelector('body.page-id-129950');
89    if (!initPage) {
90        console.log('Not the properties page, exiting script.');
91        return;
92    }
93
94    setPriceSteps(jQuery);
95    filterPropertiesByOrder(jQuery);
96
97    woningGrid = document.querySelector('.woning-grid');
98    if (woningGrid) {
99        loadProperties(jQuery, 1);
100    }
101
102    const search = document.querySelector('.apply-search');
103    const propertiesContainer = document.querySelector('.properties-container');
104    search && search.addEventListener('click', function() {
105        prepareFilters(jQuery, 1);
106        if (propertiesContainer) {
107            const y = propertiesContainer.getBoundingClientRect().top + window.pageYOffset;
108            const scroller = document.scrollingElement || document.documentElement;
109
110            scroller.scrollTo?.({ top: y, behavior: 'smooth' }) ?? (scroller.scrollTop = y);
111            document.body.scrollTop = y;
112        }
113    });
114
115    /**
116     * Handle AJAX errors globally.
117     * Generic error handler for AJAX requests. Memory safe.
118     */
119    jQuery(document).ajaxError(function(event, jqXHR, ajaxSettings, thrownError) {
120        switch(jqXHR.status) {
121            case 404:
122                jQuery(woningGrid).html(JSON.parse(jqXHR.responseText));
123                break;
124        }
125    });
126
127    const viewMapButton = document.getElementById('view-map');
128    const viewGridButton = document.getElementById('view-grid');
129    //const propertiesContainer = document.querySelector('.properties-container');
130
131    /**
132     * Toggle Display mode
133     */
134    viewMapButton && propertiesContainer && viewMapButton.addEventListener('click', function() {
135        isMapMode = !isMapMode;
136        if (isMapMode) {
137            // No need to keep observing when in map mode
138            observer.disconnect();
139
140            propertiesContainer.innerHTML = `<div id="map"></div>`;
141            initMap();
142            prepareFilters(jQuery, 1);
143
144            viewMapButton.classList.toggle('_hidden');
145            viewGridButton.classList.toggle('_hidden');
146        }
147    });
148
149    viewGridButton && viewGridButton.addEventListener('click', function() {
150        isMapMode = false;
151        console.log('List Mode');
152        propertiesContainer.innerHTML = `<div class="woning-grid"></div>`;
153        woningGrid = document.querySelector('.woning-grid');
154        if (! woningGrid) return;
155        prepareFilters(jQuery, 1);
156        initPropertiesContainerSwiper();
157
158        viewMapButton.classList.toggle('_hidden');
159        viewGridButton.classList.toggle('_hidden');
160    });
161});
162
163function initPropertiesObserver() {
164    const gridContainer = document.querySelector('.woning-grid');
165    if (gridContainer) {
166        console.log('Init Properties Observer');
167        propertiesObserver.observe(gridContainer, {
168            childList: true,
169        });
170    }
171}
172
173const propertiesObserver = new MutationObserver((mutations) => {
174    mutations.forEach((mutation) => {
175        if (mutation.type !== "childList" || mutation.addedNodes.length === 0) return;
176        mutation.addedNodes.forEach((node) => {
177            if (!(node instanceof HTMLElement)) return;
178            if (node.classList.contains("woningblokv2")) {
179                // Attach handler to swiper buttons specifically - let them work
180                const swiperButtons = node.querySelectorAll('.archive-swiper-button-prev, .archive-swiper-button-next');
181                swiperButtons.forEach((button) => {
182                    button.addEventListener('click', (e) => {
183                        console.log('Swiper button clicked');
184                        e.stopPropagation();
185                    });
186                });
187                
188                node.addEventListener('mouseover', () => {
189                    console.log('Property block mouseover');
190                    swiperButtons.forEach((button) => {
191                        button.style.display = 'flex';
192                    });
193                });
194
195                node.addEventListener('mouseleave', () => {
196                    console.log('Property block mouseleave');
197                    swiperButtons.forEach((button) => {
198                        button.style.display = 'none';
199                    });
200                });
201            }
202        });
203    });
204});
205
206/**
207 * Initialize Intersection Observer for infinite scrolling
208 * Only initializes if not in map mode and if the scroller element exists.
209 */
210function initObserver() {
211    const scroller = document.getElementById('scroller');
212    if (!isMapMode && scroller) {
213        console.log('Init Observer');
214        observer.observe(scroller);
215    }
216}
217
218/**
219 * Intersection Observer for lazy loading properties on scroll
220 */
221const observer = new IntersectionObserver(entries => {
222    entries.forEach(entry => {
223        const intersecting = entry.isIntersecting;
224        if (intersecting && !isLoading && hasMoreData) {
225            isLoading = true;
226            setTimeout(function() {
227                (page == 1) ? page = 2 : page ++;
228                (loadingType == 'load') ? loadProperties(jQuery, page, true) : prepareFilters(jQuery, page, true);
229            }, 200);
230        }
231    });
232});
233
234/**
235 * Set the price steps for the property filters
236 * @param {jQuery} $ 
237 * @param {string} search_type = 'koop'
238 * @deprecated
239 * @returns 
240 */
241function setPriceSteps($, search_type = 'koop') {
242    if (search_type == undefined) {
243        return;
244    }
245
246    let max_index = 0;
247    let step = 0;
248
249    (search_type == 'huur') ? max_index = 100 : max_index = 50;
250    (search_type == 'koop') ? step = 50000 : step = 100;
251
252    const data = {
253        'action': 'the_price_steps',
254        'data': {
255            'start_index': 0,
256            'max_index': max_index,
257            'step': step,
258            'search_type': search_type
259        }
260    };
261
262    $.post(ajax_object.ajax_url, data, function (data, status, xhr) { 
263        $('#property-min-price').html(data);
264        $('#property-max-price').html(data);
265
266        let first_val = $('#property-min-price option:first-child').val();
267        $('#property-min-price').val(first_val);
268
269        let last_val = $('#property-max-price option:last-child').val();
270        $('#property-max-price').val(last_val);
271
272        //When the price steps are set, we can load the properties
273        prepareFilters($, 1);
274    });
275}
276
277/**
278 * Filter properties by order
279 * @param {jQuery} $ 
280 */
281function filterPropertiesByOrder($) {
282    loadingType = 'filter';
283
284    const sortMenuTrigger = document.querySelector('#_sort');
285    const orderMenu       = document.querySelector('.order-menu');
286
287    if (sortMenuTrigger && orderMenu) {
288        sortMenuTrigger.addEventListener('click', () => {
289            orderMenu.classList.toggle('_hidden');
290        });
291
292        orderMenu.addEventListener('click', (menu) => {
293            if (menu.target.matches('div[order]') && !menu.target.classList.contains('selected')) {
294                document.querySelector('div[order].selected')?.classList.remove('selected');
295
296                const target = menu.target;
297                const key   = target.getAttribute('key');
298                const node  = target.getAttribute('node');
299                const order = target.getAttribute('order');
300
301                loadPropertiesData['args']['sortby'] = {
302                    [key]: {
303                        node: node,
304                        order: order,
305                        type: 'number'
306                    }
307                };
308
309                console.log('Sorting by: ', loadPropertiesData['args']['sortby']);
310
311                filterProperties(jQuery, 1);
312
313                sortMenuTrigger.querySelector('span').innerHTML = target.innerHTML;
314                orderMenu.classList.add('_hidden');
315                target.classList.add('selected');
316            }
317        });
318    }
319}
320
321/**
322 * Prepare filters for property search. 
323 * Modifies the global loadPropertiesData object based on selected filters.
324 * @param {jQuery} $ 
325 * @param {number} page_number 
326 */
327function prepareFilters($, page_number) {
328    console.log('Prepare Filters');
329
330    // Normalize filters
331    loadPropertiesData.args.filters = [];
332
333    page = page_number;
334    loadPropertiesData['args']['page'] = page_number;
335    // The method we use to load properties depending on the mode
336    (isMapMode) ? loadPropertiesData['action'] = 'load_geoData_properties' : loadPropertiesData['action'] = 'load_properties';
337
338    // Debug info
339    console.log(loadPropertiesData);
340    console.log('Map mode: ', isMapMode);
341
342    const minPrice = parseInt(jQuery('#property-min-price').find(':selected').attr('value'));
343    const maxPrice = parseInt(jQuery('#property-max-price').find(':selected').attr('value'));
344    if (minPrice > 0 && maxPrice > 0 && minPrice <= maxPrice) {
345        loadPropertiesData['args']['filters'].push({
346            key: 'overdracht',
347            action: 'BETWEEN_AND',
348            node: (searchType == 'koop') ?  'koopprijs' : 'huurprijs',
349            value: {
350                from: minPrice,
351                to: maxPrice
352            },
353            type: 'number'
354        });
355    }
356
357    let overdrachtStatus = $('#property-type-select').find(':selected').attr('value');
358    if (overdrachtStatus != 'all') {
359        const overdrachtStatusValue = overdrachtStatus.includes(',') ? overdrachtStatus.split(',') : overdrachtStatus;
360        loadPropertiesData['args']['filters'].push({
361            key: 'overdracht',
362            node: 'status',
363            action: '=',
364            value: overdrachtStatusValue
365        });
366    }
367
368    let searchValue = $('#alfred-ajax-search').val();
369    if (searchValue && searchValue.trim() !== '') {
370        loadPropertiesData['args']['filters'].push({
371            key: 'adres',
372            node: 'ANY',
373            action: 'CONTAINS',
374            value: searchValue,
375            type: 'string'
376        });
377    }
378
379    if (!isMapMode) {
380        filterProperties($, page_number, true);
381    } else {
382        getFilteredGeoProperties(() => {
383            prepareMarkers();
384        });
385    }
386};
387
388/**
389 * Loads properties on initial page load, with propertiesData as is.
390 * @param {jQuery} $ 
391 * @param {boolean} scrolling 
392 */
393function loadProperties($, scrolling = false) {
394    loadingType = 'load';
395    console.log('Load Properties');
396
397    $.post(ajax_object.ajax_url, loadPropertiesData, (data, status, xhr) => {
398        if (scrolling) {
399            if ( !data || data.trim() === '' || $(data).find('.no-results').length ) {
400                hasMoreData = false;
401            } else {
402                $(woningGrid).append(data);
403            }
404        } else {
405            $(woningGrid).html(data);
406        }
407        
408        isLoading = false;
409        initObserver();
410        initPropertiesObserver();
411    });
412}
413
414/**
415 * Fetches filtered properties using the (modified) loadPropertiesData object.
416 * Depending on the scrolling behavior, it either appends or replaces the content.
417 * @param {jQuery} $ 
418 * @param {number} page_number 
419 * @param {boolean} scrolling 
420 */
421function filterProperties($, page_number, scrolling = false) {
422    loadingType = 'filter';
423
424    if (scrolling) loadPropertiesData['args']['page'] = page_number;
425
426    $.post(ajax_object.ajax_url, loadPropertiesData, function (result, status, xhr) {
427        if (scrolling && page_number > 1) {
428            if (!result || result.trim() === '' || $(result).find('.no-results').length) {
429                hasMoreData = false;
430            } else {
431                $(woningGrid).append(result);
432            }
433        } else {
434            $(woningGrid).html(result);
435        }
436        
437        isLoading = false;
438        initObserver();
439    });
440}
441
442/**
443 * Initialize the Leaflet map.
444 * This function sets up the map container and initializes the map.
445 * Handles the display of properties on the map.
446 */
447function initMap() {
448    const mapContainer = document.querySelector('#map');
449    if (!mapContainer) {
450        console.log('Map container not found, exiting map initialization.');
451        return;
452    }
453
454    // Initialize the map
455    leafletMap = L.map(mapContainer, {
456        center: [52.7842085, 6.065566],
457        zoom: 9.25
458    });
459
460    const cartoApiKey = (typeof tpMapConfig !== 'undefined' && tpMapConfig.cartoApiKey) ? tpMapConfig.cartoApiKey : '';
461    const tileUrl = cartoApiKey
462        ? `https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png?key=${cartoApiKey}`
463        : 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
464    L.tileLayer(tileUrl, {
465        attribution: cartoApiKey
466            ? '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>'
467            : '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
468    }).addTo(leafletMap);
469
470    /*getInitialGeoData(() => {
471        prepareMarkers();
472    });*/
473
474    markerCluster.on('mouseover', (markerEvent) => {
475        displayPropertyPopupBox(markerEvent);
476    });
477
478    /*==================================================================*/
479    /* DEPRECATED - No need to load markers based on bounding box,      */
480    /* MarkerClustering handles both visibility and spidering.          */
481    /*==================================================================*/
482
483    /*getInitialGeoData(() => {
484        displayPropertiesByBoundingBox();
485    });
486
487    leafletMap.on('moveend zoomend', () => {
488        displayPropertiesByBoundingBox();
489    });*/
490}
491
492/**
493 * Fetch initial geographic data for properties.
494 * @param {Function} callback
495 * @deprecated Use getFilteredGeoProperties instead.
496 */
497function getInitialGeoData(callback) {
498    jQuery.ajax({
499        url: ajax_object.ajax_url,
500        method: 'POST',
501        data: {
502            action: 'load_geoData_properties',
503        },
504        success: (response) => {
505            let data = typeof response === 'string' ? JSON.parse(response) : response;
506            if (data && data.length) {
507                geoData = data;
508                if (typeof callback === 'function') {
509                    callback();
510                }
511            }
512        },
513        error: (xhr, status, error) => {
514            console.error('Error loading properties:', error);
515        }
516    });
517}
518
519/**
520 * Fetch geographic data for properties based on current filters.
521 * This function is called when filters are applied.
522 * @param {Function} callback
523 */
524function getFilteredGeoProperties(callback) {
525    geoData = [];
526    markers = {};
527    markerCluster.clearLayers();
528
529    console.log('Fetch filtered geo properties');
530    loadPropertiesData['action'] = 'load_geoData_properties';
531    jQuery.post(ajax_object.ajax_url, loadPropertiesData, (result) => {
532        let data = typeof result === 'string' ? JSON.parse(result) : result;
533        if (data && data.length) {
534            geoData = data;
535            if (typeof callback === 'function') {
536                callback();
537            }
538        }
539    });
540}
541
542/**
543 * Fetch geographic data for properties based on current filters.
544 * This function is called when filters are applied.
545 * @param {Array} geoData
546 */
547function prepareMarkers() {
548    Array.from(geoData).forEach(property => {
549        if (property.geoData) {
550            const { lat, lon } = property.geoData;
551            var marker = L.marker([lat, lon]);
552            marker.wordpressId = property.wordpress_id;
553            marker.realworksId = property.realworks_id;
554            marker.html = property.html;
555            markerCluster.addLayer(marker);
556        }
557    });
558
559    leafletMap.addLayer(markerCluster);
560}
561
562/**
563 * Display a popup box with property details.
564 * @param {string} htmlContent - The HTML content to display in the popup.
565 * @param {Object} markerEvent - The marker event containing location data.
566 */
567function displayPropertyPopupBox(markerEvent) {
568    L.popup({
569        maxWidth: 400,
570        minWidth: 200,
571        maxHeight: 600,
572        closeButton: true,
573        autoClose: true,
574    })
575    .setLatLng(markerEvent.latlng)
576    .setContent(markerEvent.sourceTarget.html)
577    .openOn(leafletMap);
578
579    console.log(markerEvent.sourceTarget.html);
580
581    setTimeout(() => {
582        const el = document.querySelector('.properties-archive-swiper');
583        if (!el) return;
584        const paginationEl = el.querySelector('.pas-swiper-pagination');
585        new Swiper(el, {
586            slidesPerView: 1,
587            pagination: paginationEl ? { el: paginationEl } : {},
588        });
589    }, 50);
590
591    var popupContainer = document.querySelector('.leaflet-popup');
592    if (popupContainer) {
593        popupContainer.addEventListener('mouseleave', function () {
594            leafletMap.closePopup();
595        }, { once: true });
596    }
597}
598
599/**
600 * Gets the current bounding box of the map.
601 * @deprecated
602 */
603function displayPropertiesByBoundingBox() {
604    if (!geoData || geoData.length === 0) {
605        return;
606    }
607    
608    const mapBounds = leafletMap.getBounds();
609    const northEast = mapBounds.getNorthEast();
610    const southWest = mapBounds.getSouthWest();
611
612    const boundingBox = {
613        minLat: southWest.lat,
614        minLng: southWest.lng,
615        maxLat: northEast.lat,
616        maxLng: northEast.lng
617    };
618
619    addDefaultMarker(boundingBox);
620}
621
622/**
623 * Add a default marker to the map at the specified latitude and longitude.
624 * @param {Object} mapBoundingBox - The current bounding box of the map.
625 * @deprecated
626 */
627function addDefaultMarker(mapBoundingBox) {
628    if (!leafletMap) return;
629
630    Object.values(markers).forEach(marker => marker.remove());
631    markers = {};
632
633    Array.from(geoData).forEach(property => {
634        if (property.geoData) {
635            const { lat, lon } = property.geoData;
636            if (lat >= mapBoundingBox.minLat && lat <= mapBoundingBox.maxLat &&
637                lon >= mapBoundingBox.minLng && lon <= mapBoundingBox.maxLng) {
638                const key = `${lat},${lon}`;
639                if (!markers[key]) {
640                    const marker = L.marker([lat, lon]);
641                    markers[key] = marker;
642                }
643            }
644        }
645    });
646
647    clusterMarkers();
648}
649
650/**
651 * Cluster and display markers on the map.
652 * @deprecated
653 */
654function clusterMarkers() {
655    markerCluster.clearLayers();
656    Object.values(markers).forEach(marker => {
657        if (!markerCluster.hasLayer(marker)) {
658            markerCluster.addLayer(marker);
659        }
660    });
661
662    leafletMap.addLayer(markerCluster);
663}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.