1jQuery(document).ready(function($) { 2 let map; 3 let markers = []; 4 let markerCluster; 5 let oms; 6 let infoWindow; 7 let geocoder; 8 9 let initialFilters = {}; 10 11 //NEW: wait until Google Maps is actually ready (works with loading=async) 12 function runWhenMapsReady(fn, tries = 0) { 13 if (window.google && google.maps && typeof google.maps.Map === 'function') { 14 fn(); 15 return; 16 } 17 if (tries > 200) return; // ~20s max (200 * 100ms) 18 setTimeout(function() { 19 runWhenMapsReady(fn, tries + 1); 20 }, 100); 21 } 22 23 function initMap() { 24 // Optional extra guard (harmless) 25 const el = document.getElementById('rdd-events-map'); 26 if (!el) return; 27 28 map = new google.maps.Map(el, { 29 center: {lat: 30, lng: 0}, 30 zoom: 2, 31 mapTypeControl: false 32 }); 33 34 oms = new OverlappingMarkerSpiderfier(map, { 35 markersWontMove: true, 36 markersWontHide: true, 37 basicFormatEvents: true 38 }); 39 40 infoWindow = new google.maps.InfoWindow(); 41 geocoder = new google.maps.Geocoder(); 42 43 setFiltersFromUrlParams(); 44 loadEvents(); 45 } 46 47 function setFiltersFromUrlParams() { 48 const urlParams = new URLSearchParams(window.location.search); 49 const country = urlParams.get('Country'); 50 const eventType = urlParams.get('Type'); 51 const showPastEvents = urlParams.get('Past') === 'true'; 52 53 if (country) { 54 $('#filter-country').val(country); 55 } 56 if (showPastEvents) { 57 $('#show-past-events').prop('checked', true); 58 } 59 60 initialFilters = { 61 country: country ? capitalizeWords(country) : '', 62 eventType: eventType || '', 63 showPastEvents: showPastEvents 64 }; 65 } 66 67 function loadEvents() { 68 $('#rdd-events-list').html('<p class="loading-message">Loading...</p>'); 69 70 let selectedCountry = $('#filter-country').val() || ''; 71 let selectedEventType = $('#filter-event-type').val() || ''; 72 let showPastEvents = $('#show-past-events').is(':checked') || false; 73 74 if (initialFilters && (initialFilters.country || initialFilters.eventType || typeof initialFilters.showPastEvents === 'boolean')) { 75 selectedCountry = initialFilters.country || selectedCountry; 76 selectedEventType = initialFilters.eventType || selectedEventType; 77 showPastEvents = (typeof initialFilters.showPastEvents === 'boolean') ? initialFilters.showPastEvents : showPastEvents; 78 initialFilters = null; 79 } 80 81 console.log('Loading events:', { selectedCountry, selectedEventType, showPastEvents }); 82 83 $.ajax({ 84 url: rddEventsMapData.ajaxurl, 85 type: 'POST', 86 data: { 87 action: 'rdd_load_events', 88 nonce: rddEventsMapData.nonce, 89 country: selectedCountry, 90 event_type: selectedEventType, 91 show_past_events: showPastEvents 92 }, 93 success: function(response) { 94 if (response.success) { 95 const normalizedEvents = normalizeEventsForPastFilter(response.data.events, showPastEvents); 96 97 clearMarkers(); 98 addMarkers(normalizedEvents); 99 updateEventsList(normalizedEvents); 100 updateEventTypeOptions(response.data.types, selectedEventType); 101 102 if (selectedCountry) { 103 zoomToCountry(selectedCountry); 104 } else { 105 map.setCenter({lat: 30, lng: 0}); 106 map.setZoom(2); 107 } 108 109 const newUrl = new URL(window.location); 110 111 if (selectedCountry && selectedCountry !== 'undefined' && selectedCountry.trim() !== '') { 112 newUrl.searchParams.set('Country', selectedCountry); 113 } else { 114 newUrl.searchParams.delete('Country'); 115 } 116 117 if (selectedEventType && selectedEventType !== 'undefined' && selectedEventType.trim() !== '') { 118 newUrl.searchParams.set('Type', selectedEventType); 119 } else { 120 newUrl.searchParams.delete('Type'); 121 } 122 123 newUrl.searchParams.set('Past', showPastEvents); 124 window.history.pushState({}, '', newUrl); 125 } else { 126 $('#rdd-events-list').html('<p class="error-message">GreÅ¡ka pri uÄitavanju dogaÄaja.</p>'); 127 } 128 }, 129 error: function() { 130 $('#rdd-events-list').html('<p class="error-message">GreÅ¡ka pri komunikaciji sa serverom.</p>'); 131 } 132 }); 133 } 134 135 function normalizeEventsForPastFilter(events, showPastEvents) { 136 const today = new Date(); 137 today.setHours(0, 0, 0, 0); 138 139 const getComparableDate = function(event) { 140 return parseEventDateValue(event.end_date) || parseEventDateValue(event.date); 141 }; 142 143 const isPastEvent = function(event) { 144 const comparableDate = getComparableDate(event); 145 return comparableDate ? comparableDate < today : false;
146 }; 147 148 const filtered = events.filter(function(event) { 149 return showPastEvents ? isPastEvent(event) : !isPastEvent(event); 150 }); 151 152 filtered.sort(function(a, b) { 153 const dateA = getComparableDate(a); 154 const dateB = getComparableDate(b); 155 156 if (!dateA && !dateB) return 0; 157 if (!dateA) return 1; 158 if (!dateB) return -1; 159 160 return showPastEvents ? (dateB - dateA) : (dateA - dateB); 161 }); 162 163 return filtered; 164 } 165 166 function parseEventDateValue(dateValue) { 167 if (!dateValue || typeof dateValue !== 'string') { 168 return null; 169 } 170 171 const trimmed = dateValue.trim(); 172 const isoMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); 173 174 if (isoMatch) { 175 const year = parseInt(isoMatch[1], 10); 176 const month = parseInt(isoMatch[2], 10) - 1; 177 const day = parseInt(isoMatch[3], 10); 178 return new Date(year, month, day); 179 } 180 181 const parsed = new Date(trimmed); 182 return Number.isNaN(parsed.getTime()) ? null : parsed; 183 } 184 185 function clearMarkers() { 186 for (let i = 0; i < markers.length; i++) { 187 markers[i].setMap(null); 188 } 189 markers = []; 190 if (markerCluster) { 191 markerCluster.clearMarkers(); 192 } 193 oms.clearMarkers(); 194 } 195 196 function addMarkers(events) { 197 console.log(events); 198 199 events.forEach(function(event, index) { 200 if (event.lat && event.lng) { 201 const marker = new google.maps.Marker({ 202 position: { 203 lat: parseFloat(event.lat) + (Math.random() - 0.5) * 0.0001, 204 lng: parseFloat(event.lng) + (Math.random() - 0.5) * 0.0001 205 }, 206 map: map, 207 title: event.title, 208 icon: { 209 path: 'M11 2c-3.9 0-7 3.1-7 7 0 5.3 7 13 7 13 0 0 7-7.7 7-13 0-3.9-3.1-7-7-7Zm0 9.5c-1.4 0-2.5-1.1-2.5-2.5 0-1.4 1.1-2.5 2.5-2.5 1.4 0 2.5 1.1 2.5 2.5 0 1.4-1.1 2.5-2.5 2.5Z', 210 scale: 1.6363636363636365, 211 anchor: new google.maps.Point(11, 22), 212 fillOpacity: 1, 213 fillColor: '#c36', 214 strokeOpacity: 0 215 } 216 }); 217 218 marker.addListener('click', function() { 219 selectEventInList(index); 220 }); 221 222 markers.push(marker); 223 oms.addMarker(marker); 224 } 225 }); 226 227 markerCluster = new MarkerClusterer(map, markers, { 228 imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m' 229 }); 230 } 231 232 function updateEventsList(events) { 233 const $eventsList = $('#rdd-events-list'); 234 $eventsList.empty(); 235 236 if (events.length === 0) { 237 $eventsList.html('<p class="loading-message">Sorry, no events match your filters.</p>'); 238 return; 239 } 240 241 events.forEach(function(event, index) { 242 const eventHtml = 243 '<div class="event-item" data-event-index="' + index + '">' + 244 '<div class="event-image" style="background-image: url(' + event.photo + ')" title="' + event.title + '"></div>' + 245 '<div class="event-info">' + 246 '<a class="event-title" href="' + event.url + '" target="_blank">' + event.title + '</a>' + 247 '<div class="event-location">' + 248 '<span>' + formatDate(event.date) + '</span>, ' + 249 '<a href="'+ event.country_url + '" target="_blank"><span>' + event.city + ', ' + event.country + '</span></a>' + 250 '</div>' + 251 '<p class="event-description">' + event.description + '</p>' + 252 '</div>' + 253 '</div>'; 254 $eventsList.append(eventHtml); 255 }); 256 } 257 258 function selectEventInList(index) { 259 $('.event-item').removeClass('selected'); 260 const $selectedItem = $('.event-item[data-event-index="' + index + '"]'); 261 $selectedItem.addClass('selected'); 262 263 const $list = $('#rdd-events-list'); 264 const itemOffsetTop = $selectedItem.offset().top; 265 const listOffsetTop = $list.offset().top; 266 const scrollTop = itemOffsetTop - listOffsetTop + $list.scrollTop(); 267 268 $list.animate({ scrollTop: scrollTop }, 500); 269 } 270 271 function zoomToCountry(country) { 272 geocoder.geocode({ 'address': country }, function(results, status) { 273 if (status === google.maps.GeocoderStatus.OK && results[0]) { 274 map.fitBounds(results[0].geometry.viewport); 275 } else { 276 console.log('Nije pronaÄena zemlja: ' + country); 277 } 278 }); 279 } 280 281 function updateEventTypeOptions(types, selectedValue) { 282 const $eventTypeSelect = $('#filter-event-type'); 283 const currentValue = $eventTypeSelect.val(); 284 285 $eventTypeSelect.empty(); 286 $eventTypeSelect.append($('<option>', { 287 value: '', 288 text: 'All' 289 })); 290
291 types.forEach(function(type) { 292 $eventTypeSelect.append($('<option>', { 293 value: type, 294 text: type 295 })); 296 }); 297 298 if (selectedValue) { 299 $eventTypeSelect.val(selectedValue); 300 } else { 301 $eventTypeSelect.val(currentValue); 302 } 303 } 304 305 $('#filter-country, #filter-event-type, #show-past-events').on('change', function(event) { 306 const isShowPastEvents = (event.target.id === 'show-past-events'); 307 const isCountryFilter = (event.target.id === 'filter-country'); 308 309 if (isCountryFilter || (isShowPastEvents && !$(this).is(':checked'))) { 310 $('#filter-event-type').val(''); 311 312 if (isShowPastEvents && !$(this).is(':checked')) { 313 $('#filter-country').val(''); 314 } 315 316 const urlParams = new URLSearchParams(window.location.search); 317 urlParams.delete('Type'); 318 window.history.replaceState({}, '', `${window.location.pathname}?${urlParams}`); 319 } 320 321 loadEvents(); 322 }); 323 324 //CHANGED: don't call initMap() immediately 325 runWhenMapsReady(initMap); 326}); 327 328function formatDate(dateString) { 329 const date = new Date(dateString); 330 const day = date.getDate();
331 const month = date.toLocaleString('default', { month: 'short' }); 332 const year = date.getFullYear(); 333 return `${day} ${month}, ${year}`; 334} 335 336function capitalizeWords(str) { 337 return str.replace(/\b\w/g, function(char) { 338 return char.toUpperCase(); 339 }); 340}
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.