1(function () { 2 const root = document.querySelector("[data-member-directory]"); 3 4 if (!root) { 5 return; 6 } 7 8 const expertiseLabels = { 9 residentieelVastgoed: "Residentieel vastgoed", 10 zonevreemdVastgoed: "Zonevreemd vastgoed", 11 bedrijfsmatigVastgoed: "Bedrijfsmatig vastgoed", 12 commercieelVastgoed: "Commercieel vastgoed", 13 industrieelVastgoed: "Industrieel vastgoed", 14 sevesoVastgoed: "Seveso vastgoed", 15 agrarischVastgoed: "Agrarisch vastgoed", 16 bijzonderVastgoed: "Bijzonder vastgoed", 17 aanvangEnEindeHuur: "Aanvang en einde huur", 18 aanvangEnEindeWerken: "Aanvang en einde werken", 19 verkoopVanVastgoed: "Verkoop van vastgoed", 20 opleveringVanGebouwen: "Oplevering van gebouwen", 21 brandschade: "Brandschade", 22 bouwschade: "Bouwschade", 23 verzekeringen: "Verzekeringen", 24 syndicusMedeEigendom: "Syndicus mede-eigendom", 25 beheerderOG: "Beheerder OG", 26 rentmeester: "Rentmeester", 27 energiedeskundige: "Energiedeskundige", 28 asbestdeskundige: "Asbestdeskundige inventarisatie", 29 veiligheidscoordinator: "Veiligheidscoördinator" 30 }; 31 const form = root.querySelector("[data-member-filters]"); 32 const nameFilter = root.querySelector("[data-member-name]"); 33 const locationFilter = root.querySelector("[data-member-location]"); 34 const expertiseFilter = root.querySelector("[data-member-expertise]"); 35 const loadingState = root.querySelector("[data-member-loading]"); 36 const errorState = root.querySelector("[data-member-error]"); 37 const emptyState = root.querySelector("[data-member-empty]"); 38 const results = root.querySelector("[data-member-results]"); 39 const mapElement = root.querySelector("[data-member-map]"); 40 const mapStatus = root.querySelector("[data-member-map-status]"); 41 const filters = [nameFilter, locationFilter, expertiseFilter]; 42 let members = []; 43 let memberLocations = {}; 44 let map; 45 let markerLayer; 46 47 function value(valueToRead) { 48 return valueToRead === null || valueToRead === undefined ? "" : String(valueToRead).trim(); 49 } 50 51 function element(tagName, className, text) { 52 const node = document.createElement(tagName); 53 54 if (className) { 55 node.className = className; 56 } 57 if (text !== undefined) { 58 node.textContent = text; 59 } 60 61 return node; 62 } 63 64 function contactIcon(type) { 65 const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); 66 const paths = { 67 email: ["M3 5h18v14H3z", "m4 8 5 4 5-4"], 68 phone: ["M7 3H4a1 1 0 0 0-1 1c0 9.4 7.6 17 17 17a1 1 0 0 0 1-1v-3l-4-1-1 2c-4.3-1.2-7.8-4.7-9-9l2-1-2-5Z"], 69 website: ["M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z", "M3 12h18", "M12 3c2.2 2.5 3.3 5.5 3.3 9S14.2 18.5 12 21c-2.2-2.5-3.3-5.5-3.3-9S9.8 5.5 12 3Z"] 70 }; 71 72 svg.setAttribute("viewBox", "0 0 24 24"); 73 svg.setAttribute("aria-hidden", "true"); 74 paths[type].forEach(function (pathData) { 75 const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); 76 path.setAttribute("d", pathData); 77 svg.append(path); 78 }); 79 return svg; 80 } 81 82 function addDetail(container, label, detail) { 83 if (!detail) { 84 return; 85 } 86 87 const row = element("p", "fb-member-card__detail"); 88 row.append(element("strong", "fb-member-card__detail-label", label + ": ")); 89 row.append(document.createTextNode(detail)); 90 container.append(row); 91 } 92 93 function websiteUrl(website) { 94 const enteredUrl = value(website); 95 96 if (!enteredUrl) { 97 return null; 98 } 99 100 try { 101 const parsedUrl = new URL(/^https?:\/\//i.test(enteredUrl) ? enteredUrl : "https://" + enteredUrl); 102 return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:" ? parsedUrl.href : null; 103 } catch (error) { 104 return null; 105 } 106 } 107 108 function renderAdditions(member, card) { 109 const additions = member.vastgoedtechnicus_additions; 110 111 if (!additions || typeof additions !== "object" || Array.isArray(additions)) { 112 return; 113 } 114 115 const entries = Object.entries(additions).filter(function (entry) { 116 return Array.isArray(entry[1]) && entry[1].some(value); 117 }); 118 119 if (!entries.length) { 120 return; 121 } 122 123 const section = element("section", "fb-member-card__additions"); 124 section.append(element("h4", "fb-member-card__subtitle", "Aanvullende gegevens")); 125
126 entries.forEach(function (entry) { 127 const group = element("div", "fb-member-card__addition"); 128 const list = element("ul", "fb-member-card__addition-list"); 129 group.append(element("h5", "fb-member-card__addition-title", value(entry[0]))); 130 entry[1].forEach(function (item) { 131 const itemText = value(item); 132 if (itemText) { 133 list.append(element("li", "fb-member-card__addition-item", itemText)); 134 } 135 }); 136 group.append(list); 137 section.append(group); 138 }); 139 140 card.append(section); 141 } 142 143 function renderMember(member) { 144 const item = element("li", "fb-member-directory__item"); 145 const card = element("article", "fb-member-card"); 146 const heading = element("h2", "fb-member-card__name", [value(member.firstName), value(member.lastName)].filter(Boolean).join(" ")); 147 const memberKey = value(member.email).toLowerCase(); 148 149 item.id = "member-" + memberKey.replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""); 150 151 if (member.credited === true) { 152 heading.append(element("span", "fb-member-card__credited", " â Gecrediteerd")); 153 } 154 card.append(heading); 155 156 const details = element("div", "fb-member-card__details"); 157 addDetail(details, "Kantoor", value(member.company)); 158 159 const street = [value(member.street), value(member.streetNumber), value(member.streetnumberaddition)].filter(Boolean).join(" "); 160 const locality = [value(member.zipcode), value(member.city)].filter(Boolean).join(" "); 161 addDetail(details, "Adres", street); 162 addDetail(details, "Plaats", locality); 163 addDetail(details, "Provincie", value(member.province));
164 card.append(details); 165 166 const expertise = Object.keys(expertiseLabels).filter(function (key) { 167 return member[key] === true; 168 }); 169 170 if (expertise.length) { 171 const expertiseSection = element("section", "fb-member-card__expertise"); 172 const expertiseList = element("ul", "fb-member-card__expertise-list"); 173 expertiseSection.append(element("h3", "fb-member-card__subtitle", "Expertises")); 174 expertise.forEach(function (key) { 175 expertiseList.append(element("li", "fb-member-card__expertise-item", expertiseLabels[key])); 176 }); 177 expertiseSection.append(expertiseList); 178 card.append(expertiseSection); 179 } 180 181 renderAdditions(member, card); 182 183 const contact = element("div", "fb-member-card__contact"); 184 const email = value(member.email); 185 const telephone = value(member.telephone); 186 const website = value(member.website); 187 const safeWebsiteUrl = websiteUrl(website); 188 189 if (email) { 190 const emailLink = element("a", "fb-member-card__link fb-member-card__link--email", email); 191 emailLink.href = "mailto:" + email; 192 emailLink.setAttribute("aria-label", "Stuur een e-mail naar " + email); 193 emailLink.prepend(contactIcon("email")); 194 contact.append(emailLink); 195 } 196 if (telephone) { 197 const telephoneLink = element("a", "fb-member-card__link fb-member-card__link--phone", telephone); 198 telephoneLink.href = "tel:" + telephone; 199 telephoneLink.setAttribute("aria-label", "Bel " + telephone); 200 telephoneLink.prepend(contactIcon("phone")); 201 contact.append(telephoneLink); 202 } 203 if (safeWebsiteUrl) { 204 const websiteLink = element("a", "fb-member-card__link fb-member-card__link--website", website); 205 websiteLink.href = safeWebsiteUrl; 206 websiteLink.target = "_blank"; 207 websiteLink.rel = "noopener"; 208 websiteLink.setAttribute("aria-label", "Bezoek de website van " + value(member.company || member.firstName)); 209 websiteLink.prepend(contactIcon("website")); 210 contact.append(websiteLink); 211 } else if (website) { 212 contact.append(element("span", "fb-member-card__website", website)); 213 } 214 if (contact.childNodes.length) { 215 card.append(contact); 216 } 217 218 item.append(card); 219 return item; 220 } 221 222 function filteredMembers() { 223 const requestedName = nameFilter.value.toLowerCase(); 224 const requestedLocation = locationFilter.value.toLowerCase(); 225 const requestedExpertise = expertiseFilter.value; 226 227 return members.filter(function (member) { 228 const fullName = (value(member.firstName) + " " + value(member.lastName)).toLowerCase(); 229 const hasName = !requestedName || fullName.includes(requestedName); 230 const hasLocation = !requestedLocation || value(member.province).toLowerCase() === requestedLocation; 231 const hasExpertise = !requestedExpertise || member[requestedExpertise] === true; 232 return hasName && hasLocation && hasExpertise; 233 }); 234 } 235 236 function memberLocation(member) { 237 return memberLocations[value(member.email).toLowerCase()]; 238 } 239 240 function renderMap(visibleMembers) { 241 if (!map || !markerLayer) { 242 return; 243 } 244 245 markerLayer.clearLayers(); 246 const bounds = []; 247 let markerCount = 0; 248 249 visibleMembers.forEach(function (member) { 250 const location = memberLocation(member); 251 252 if (!location || !Number.isFinite(location.latitude) || !Number.isFinite(location.longitude)) { 253 return; 254 } 255 256 const coordinates = [location.latitude, location.longitude]; 257 const marker = window.L.circleMarker(coordinates, { 258 radius: 8, 259 weight: 2, 260 color: "#ffffff", 261 fillColor: "#4338ca", 262 fillOpacity: 0.9 263 }); 264 const popup = element("div", "fb-member-map-popup");
265 const name = [value(member.firstName), value(member.lastName)].filter(Boolean).join(" "); 266 const address = [ 267 [value(member.street), value(member.streetNumber)].filter(Boolean).join(" "), 268 [value(member.zipcode), value(member.city)].filter(Boolean).join(" ") 269 ].filter(Boolean).join(", "); 270 const profileLink = element("a", "fb-member-map-popup__link", "Bekijk contactgegevens"); 271 272 popup.append(element("strong", "fb-member-map-popup__name", name)); 273 if (value(member.company)) { 274 popup.append(element("span", "fb-member-map-popup__company", value(member.company))); 275 } 276 popup.append(element("span", "fb-member-map-popup__address", address)); 277 profileLink.href = "#member-" + value(member.email).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""); 278 popup.append(profileLink); 279 marker.bindPopup(popup); 280 marker.addTo(markerLayer); 281 bounds.push(coordinates); 282 markerCount += 1; 283 }); 284 285 if (bounds.length) { 286 map.fitBounds(bounds, {padding: [35, 35], maxZoom: 11}); 287 } else {
288 map.setView([50.85, 4.55], 7); 289 } 290 291 mapStatus.textContent = markerCount === 1 ? "1 vastgoedexpert op de kaart" : markerCount + " vastgoedexperten op de kaart"; 292 } 293 294 function initializeMap() { 295 if (!mapElement || !window.L) { 296 mapStatus.textContent = "De kaart kon niet worden geladen."; 297 return; 298 } 299 300 map = window.L.map(mapElement, {scrollWheelZoom: false}).setView([50.85, 4.55], 7); 301 window.L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { 302 maxZoom: 19, 303 attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' 304 }).addTo(map); 305 markerLayer = window.L.layerGroup().addTo(map); 306 renderMap(filteredMembers()); 307 } 308 309 function render() { 310 const visibleMembers = filteredMembers(); 311 const fragment = document.createDocumentFragment(); 312 313 visibleMembers.forEach(function (member) { 314 fragment.append(renderMember(member)); 315 }); 316 results.replaceChildren(fragment); 317 emptyState.hidden = visibleMembers.length !== 0; 318 renderMap(visibleMembers); 319 } 320 321 function setFiltersDisabled(disabled) { 322 filters.forEach(function (filter) { 323 filter.disabled = disabled; 324 }); 325 } 326 327 form.addEventListener("submit", function (event) { 328 event.preventDefault(); 329 }); 330 nameFilter.addEventListener("input", render); 331 locationFilter.addEventListener("change", render); 332 expertiseFilter.addEventListener("change", render); 333 334 setFiltersDisabled(true); 335 336 fetch(root.dataset.membersUrl, {headers: {Accept: "application/json"}}) 337 .then(function (response) { 338 if (!response.ok) { 339 throw new Error("Members request failed with status " + response.status); 340 } 341 return response.json(); 342 }) 343 .then(function (payload) { 344 const memberData = payload && payload.data && payload.data.users_user_data; 345 346 if (!Array.isArray(memberData)) { 347 throw new Error("Members response has an invalid structure"); 348 } 349 350 members = memberData.filter(function (member) { 351 return member && typeof member === "object" && !Array.isArray(member); 352 }); 353 loadingState.hidden = true; 354 results.setAttribute("aria-busy", "false"); 355 setFiltersDisabled(false); 356 render(); 357 358 return fetch(root.dataset.memberLocationsUrl, {headers: {Accept: "application/json"}}); 359 }) 360 .then(function (response) { 361 if (!response.ok) { 362 throw new Error("Member locations request failed with status " + response.status); 363 } 364 return response.json(); 365 }) 366 .then(function (payload) { 367 memberLocations = payload && payload.locations ? payload.locations : {}; 368 initializeMap(); 369 }) 370 .catch(function () { 371 if (!members.length) { 372 loadingState.hidden = true; 373 errorState.hidden = false; 374 results.setAttribute("aria-busy", "false"); 375 } else { 376 mapStatus.textContent = "De kaart kon niet worden geladen."; 377 } 378 }); 379}());
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.