1<!doctype html> 2<html lang="nl"> 3<head > 4 <meta charset="utf-8"/> 5<meta name="title" content="Homepage | Uitgeverij Pluim"/> 6<meta name="description" content="Uitgeverij Pluim"/> 7<meta name="keywords" content="boeken,uitgeverij,pluim"/> 8<meta name="robots" content="INDEX,FOLLOW"/> 9<meta name="viewport" content="width=device-width, initial-scale=1"/> 10<title>Homepage | Uitgeverij Pluim</title> 11 <link rel="stylesheet" type="text/css" defer="defer" href="https://uitgeverijpluim.nl/static/version1790668939/frontend/Pluim/Hyva/nl_NL/css/styles.css" />
12<script type="text/javascript" src="https://uitgeverijpluim.nl/static/version1790668939/frontend/Pluim/Hyva/nl_NL/Smile_ElasticsuiteTracker/js/tracking.js"></script>
12 13<link rel="icon" type="image/x-icon" href="https://uitgeverijpluim.nl/media/favicon/websites/1/logo_sxfncc_1.jpg" /> 14<link rel="shortcut icon" type="image/x-icon" href="https://uitgeverijpluim.nl/media/favicon/websites/1/logo_sxfncc_1.jpg" /> 15<link rel="dns-prefetch" href="https://acdn.adnxs.com"> 16<link rel="dns-prefetch" href="https://ib.adnxs.com"> 17 18<!-- Xandr Universal Pixel - Optimized -->
19<script async defer> 20(function() { 21 // Initialize pixie stub immediately 22 !function(e,i){ 23 if(!e.pixie){ 24 var n=e.pixie=function(e,i,a){ 25 n.actionQueue.push({action:e,actionValue:i,params:a}) 26 }; 27 n.actionQueue=[]; 28 var a=i.createElement("script"); 29 a.async=!0; 30 a.src="https://acdn.adnxs.com/dmp/up/pixie.js"; 31 var t=i.getElementsByTagName("head")[0]; 32 t.insertBefore(a,t.firstChild) 33 } 34 }(window,document); 35 36 // Queue consent and initialization 37 pixie('consent', 'default', { 'ad_storage': 'granted', 'wait_for_update': 1000}); 38 pixie('init', 'cf677036-49f6-4ee6-8255-72e7d7a8944c'); 39 pixie('event', 'PageView'); 40})(); 41</script>
41
42<script nonce="MzJpbnJpdDZjbzFsOHF3bG5pazUydXh3b3RsYnU3bHk="> 43 window.YIREO_GOOGLETAGMANAGER2_ENABLED = true; 44 (function (events) { 45 const initYireoGoogleTagManager2 = function () { 46 events.forEach(function (eventType) { 47 window.removeEventListener(eventType, initYireoGoogleTagManager2); 48 }); 49 50 (function (w, d, s, l, i) { 51 w[l] = w[l] || []; 52 w[l].push({'gtm.start': new Date().getTime(), event: 'gtm.js'}); 53 var f = d.getElementsByTagName(s)[0], 54 j = d.createElement(s), 55 dl = l != 'dataLayer' ? '&l=' + l : ''; 56 j.async = true; 57 j.src = 'https\u003A\u002F\u002Fwww.googletagmanager.com' + '/gtm.js?id=' + i + dl; 58 f.parentNode.insertBefore(j, f); 59 })(window, document, 'script', 'dataLayer', 'GTM\u002DWDGVFB9'); 60 }; 61 62 events.forEach(function (eventType) { 63 window.addEventListener(eventType, initYireoGoogleTagManager2, {once: true, passive: true}) 64 }); 65 })(['load', 'keydown', 'mouseover', 'scroll', 'touchstart', 'wheel']); 66 67 </script>
vendor: 1 bytes, line 67
67
68<script> 69 (function( hyva, undefined ) { 70 71 function lifetimeToExpires(options, defaults) { 72 73 const lifetime = options.lifetime || defaults.lifetime; 74 75 if (lifetime) { 76 const date = new Date; 77 date.setTime(date.getTime() + lifetime * 1000); 78 return date; 79 } 80 81 return null; 82 } 83 84 function generateRandomString() { 85 86 const allowedCharacters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 87 length = 16; 88 89 let formKey = '', 90 charactersLength = allowedCharacters.length; 91 92 for (let i = 0; i < length; i++) { 93 formKey += allowedCharacters[Math.round(Math.random() * (charactersLength - 1))] 94 } 95 96 return formKey; 97 } 98 99 const sessionCookieMarker = {noLifetime: true} 100 101 const cookieTempStorage = {}; 102 103 const internalCookie = { 104 get(name) { 105 const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); 106 return v ? v[2] : null; 107 }, 108 set(name, value, days, skipSetDomain) { 109 let expires, 110 path, 111 domain, 112 secure, 113 samesite; 114 115 const defaultCookieConfig = { 116 expires: null, 117 path: '/', 118 domain: null, 119 secure: false, 120 lifetime: null, 121 samesite: 'lax' 122 }; 123 124 const cookieConfig = window.COOKIE_CONFIG || {}; 125 126 expires = days && days !== sessionCookieMarker 127 ? lifetimeToExpires({lifetime: 24 * 60 * 60 * days, expires: null}, defaultCookieConfig) 128 : lifetimeToExpires(window.COOKIE_CONFIG, defaultCookieConfig) || defaultCookieConfig.expires; 129 130 path = cookieConfig.path || defaultCookieConfig.path; 131 domain = !skipSetDomain && (cookieConfig.domain || defaultCookieConfig.domain); 132 secure = cookieConfig.secure || defaultCookieConfig.secure; 133 samesite = cookieConfig.samesite || defaultCookieConfig.samesite; 134 135 document.cookie = name + "=" + encodeURIComponent(value) + 136 (expires && days !== sessionCookieMarker ? '; expires=' + expires.toGMTString() : '') + 137 (path ? '; path=' + path : '') + 138 (domain ? '; domain=' + domain : '') + 139 (secure ? '; secure' : '') + 140 (samesite ? '; samesite=' + samesite : 'lax'); 141 }, 142 isWebsiteAllowedToSaveCookie() { 143 const allowedCookies = this.get('user_allowed_save_cookie'); 144 if (allowedCookies) { 145 const allowedWebsites = JSON.parse(unescape(allowedCookies)); 146 147 return allowedWebsites[CURRENT_WEBSITE_ID] === 1; 148 } 149 return false; 150 }, 151 getGroupByCookieName(name) { 152 const cookieConsentConfig = window.cookie_consent_config || {}; 153 let group = null; 154 for (let prop in cookieConsentConfig) { 155 if (!cookieConsentConfig.hasOwnProperty(prop)) continue; 156 if (cookieConsentConfig[prop].includes(name)) { 157 group = prop; 158 break; 159 } 160 } 161 return group; 162 }, 163 isCookieAllowed(name) { 164 const cookieGroup = this.getGroupByCookieName(name); 165 return cookieGroup 166 ? window.cookie_consent_groups[cookieGroup] 167 : this.isWebsiteAllowedToSaveCookie(); 168 }, 169 saveTempStorageCookies() { 170 for (const [name, data] of Object.entries(cookieTempStorage)) { 171 if (this.isCookieAllowed(name)) { 172 this.set(name, data['value'], data['days'], data['skipSetDomain']); 173 delete cookieTempStorage[name]; 174 } 175 } 176 } 177 }; 178 179 hyva.getCookie = (name) => { 180 const cookieConfig = window.COOKIE_CONFIG || {}; 181 182 if (cookieConfig.cookie_restriction_enabled && ! internalCookie.isCookieAllowed(name)) { 183 return cookieTempStorage[name] ? cookieTempStorage[name]['value'] : null; 184 } 185 186 return internalCookie.get(name); 187 } 188 189 hyva.setCookie = (name, value, days, skipSetDomain) => { 190 const cookieConfig = window.COOKIE_CONFIG || {}; 191 192 if (cookieConfig.cookie_restriction_enabled && ! internalCookie.isCookieAllowed(name)) { 193 cookieTempStorage[name] = {value, days, skipSetDomain}; 194 return; 195 } 196 return internalCookie.set(name, value, days, skipSetDomain); 197 } 198 199 200 hyva.setSessionCookie = (name, value, skipSetDomain) => { 201 return hyva.setCookie(name, value, sessionCookieMarker, skipSetDomain) 202 } 203 204 hyva.getBrowserStorage = () => { 205 const browserStorage = window.localStorage || window.sessionStorage; 206 if (!browserStorage) { 207 console.warn('Browser Storage is unavailable'); 208 return false;
209 } 210 try { 211 browserStorage.setItem('storage_test', '1'); 212 browserStorage.removeItem('storage_test'); 213 } catch (error) { 214 console.warn('Browser Storage is not accessible', error); 215 return false; 216 } 217 return browserStorage; 218 } 219 220 hyva.postForm = (postParams) => { 221 const form = document.createElement("form"); 222 223 let data = postParams.data; 224 225 if (! postParams.skipUenc && ! data.uenc) { 226 data.uenc = btoa(window.location.href); 227 } 228 form.method = "POST"; 229 form.action = postParams.action; 230 231 Object.keys(postParams.data).map(key => { 232 const field = document.createElement("input"); 233 field.type = 'hidden' 234 field.value = postParams.data[key]; 235 field.name = key; 236 form.appendChild(field); 237 }); 238 239 const form_key = document.createElement("input"); 240 form_key.type = 'hidden'; 241 form_key.value = hyva.getFormKey(); 242 form_key.name="form_key"; 243 form.appendChild(form_key); 244 245 document.body.appendChild(form); 246 247 form.submit(); 248 } 249 250 hyva.getFormKey = function () { 251 let formKey = hyva.getCookie('form_key'); 252 253 if (!formKey) { 254 formKey = generateRandomString(); 255 hyva.setCookie('form_key', formKey); 256 } 257 258 return formKey; 259 } 260 261 hyva.formatPrice = (value, showSign, options = {}) => { 262 const groupSeparator = options.groupSeparator; 263 const decimalSeparator = options.decimalSeparator 264 delete options.groupSeparator; 265 delete options.decimalSeparator; 266 const formatter = new Intl.NumberFormat( 267 'nl\u002DNL', 268 Object.assign({ 269 style: 'currency', 270 currency: 'EUR', 271 signDisplay: showSign ? 'always' : 'auto' 272 }, options) 273 ); 274 return (typeof Intl.NumberFormat.prototype.formatToParts === 'function') ? 275 formatter.formatToParts(value).map(({type, value}) => { 276 switch (type) { 277 case 'currency': 278 return '\u20AC' || value; 279 case 'minusSign': 280 return '- '; 281 case 'plusSign': 282 return '+ '; 283 case 'group': 284 return groupSeparator !== undefined ? groupSeparator : value; 285 case 'decimal': 286 return decimalSeparator !== undefined ? decimalSeparator : value; 287 default : 288 return value; 289 } 290 }).reduce((string, part) => string + part) : 291 formatter.format(value); 292 } 293 294 const formatStr = function (str, nStart) { 295 const args = Array.from(arguments).slice(2); 296 297 return str.replace(/(%+)([0-9]+)/g, (m, p, n) => { 298 const idx = parseInt(n) - nStart; 299 300 if (args[idx] === null || args[idx] === void 0) { 301 return m; 302 } 303 return p.length % 2 304 ? p.slice(0, -1).replace('%%', '%') + args[idx] 305 : p.replace('%%', '%') + n; 306 }) 307 } 308 309 hyva.str = function (string) { 310 const args = Array.from(arguments); 311 args.splice(1, 0, 1); 312 313 return formatStr.apply(undefined, args); 314 } 315 316 hyva.strf = function () { 317 const args = Array.from(arguments); 318 args.splice(1, 0, 0); 319 320 return formatStr.apply(undefined, args); 321 } 322 323 /** 324 * Take a html string as `content` parameter and 325 * extract an element from the DOM to replace in 326 * the current page under the same selector, 327 * defined by `targetSelector` 328 */ 329 hyva.replaceDomElement = (targetSelector, content) => { 330 // Parse the content and extract the DOM node using the `targetSelector` 331 const parser = new DOMParser(); 332 const doc = parser.parseFromString(content, 'text/html');
333 const contentNode = doc.querySelector(targetSelector); 334 335 // Bail if content or target can't be found 336 if (!contentNode || !document.querySelector(targetSelector)) { 337 return; 338 } 339 340 hyva.activateScripts(contentNode); 341 342 // Replace the old DOM node with the new content 343 document.querySelector(targetSelector).replaceWith(contentNode); 344 345 // Reload customerSectionData and display cookie-messages if present 346 window.dispatchEvent(new CustomEvent("reload-customer-section-data")); 347 hyva.initMessages(); 348 } 349 350 hyva.removeScripts = (contentNode) => { 351 const scripts = contentNode.getElementsByTagName('script'); 352 for (let i = 0; i < scripts.length; i++) { 353 scripts[i].parentNode.removeChild(scripts[i]); 354 } 355 const templates = contentNode.getElementsByTagName('template'); 356 for (let i = 0; i < templates.length; i++) { 357 const container = document.createElement('div'); 358 container.innerHTML = templates[i].innerHTML; 359 hyva.removeScripts(container); 360 templates[i].innerHTML = container.innerHTML; 361 } 362 } 363 364 hyva.activateScripts = (contentNode) => { 365 // Create new array from HTMLCollection to avoid mutation of collection while manipulating the DOM. 366 const scripts = Array.from(contentNode.getElementsByTagName('script')); 367 368 // Iterate over all script tags to duplicate+inject each into the head 369 for (const original of scripts) { 370 const script = document.createElement('script'); 371 original.type && (script.type = original.type); 372 script.innerHTML = original.innerHTML; 373 374 // Remove the original (non-executing) script from the contentNode 375 original.parentNode.removeChild(original) 376 377 // Add script to head 378 document.head.appendChild(script); 379 } 380 381 return contentNode; 382 } 383 384 const replace = {['+']: '-', ['/']: '_', ['=']: ','}; 385 hyva.getUenc = () => btoa(window.location.href).replace(/[+/=]/g, match => replace[match]); 386 387 let currentTrap; 388 389 const focusableElements = (rootElement) => { 390 const selector = 'button, [href], input, select, textarea, details, [tabindex]:not([tabindex="-1"]'; 391 return Array.from(rootElement.querySelectorAll(selector)) 392 .filter(el => { 393 return el.style.display !== 'none' 394 && !el.disabled 395 && el.tabIndex !== -1 396 && (el.offsetWidth || el.offsetHeight || el.getClientRects().length) 397 }) 398 } 399 400 const focusTrap = (e) => { 401 const isTabPressed = e.key === 'Tab' || e.keyCode === 9; 402 if (!isTabPressed) return; 403 404 const focusable = focusableElements(currentTrap) 405 const firstFocusableElement = focusable[0] 406 const lastFocusableElement = focusable[focusable.length - 1] 407 408 e.shiftKey 409 ? document.activeElement === firstFocusableElement && (lastFocusableElement.focus(), e.preventDefault()) 410 : document.activeElement === lastFocusableElement && (firstFocusableElement.focus(), e.preventDefault()) 411 }; 412 413 hyva.releaseFocus = (rootElement) => { 414 if (currentTrap && (!rootElement || rootElement === currentTrap)) { 415 currentTrap.removeEventListener('keydown', focusTrap) 416 currentTrap = null 417 } 418 } 419 hyva.trapFocus = (rootElement) => { 420 if (!rootElement) return; 421 hyva.releaseFocus() 422 currentTrap = rootElement 423 rootElement.addEventListener('keydown', focusTrap) 424 const firstElement = focusableElements(rootElement)[0] 425 firstElement && firstElement.focus() 426 } 427 428 hyva.safeParseNumber = (rawValue) => { 429 const number = rawValue ? parseFloat(rawValue) : null; 430 431 return Array.isArray(number) || isNaN(number) ? rawValue : number; 432 } 433 434 const toCamelCase = s => s.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(''); 435 hyva.createBooleanObject = (name, value = false, additionalMethods = {}) => { 436 437 const camelCase = toCamelCase(name);
438 const key = '__hyva_bool_' + name 439 440 return new Proxy(Object.assign( 441 additionalMethods, 442 { 443 [key]: !!value, 444 [name]() {return !!this[key]}, 445 ['!' + name]() {return !this[key]}, // @deprecated This does not work with non-CSP Alpine 446 ['not' + camelCase]() {return !this[key]}, 447 ['toggle' + camelCase]() {this[key] = !this[key]}, 448 [`set${camelCase}True`]() {this[key] = true}, 449 [`set${camelCase}False`]() {this[key] = false}, 450 } 451 ), { 452 set(target, prop, value) { 453 return prop === name 454 ? (target[key] = !!value) 455 : Reflect.set(...arguments); 456 } 457 }) 458 } 459 460 hyva.alpineInitialized = (fn) => window.addEventListener('alpine:initialized', fn, {once: true}) 461 window.addEventListener('alpine:init', () => Alpine.data('{}', () => ({})), {once: true}); 462 window.addEventListener('user-allowed-save-cookie', () => internalCookie.saveTempStorageCookies()) 463 464 }( window.hyva = window.hyva || {} )); 465 //# sourceURL=hyva.js 466</script>
vendor: 1 bytes, line 466
466
467<script> 468 if (HTMLScriptElement.supports && HTMLScriptElement.supports('speculationrules') && document.body) { 469 const specScript = document.createElement('script'); 470 specScript.type = 'speculationrules'; 471 specRules = { 472 'prerender': [{ 473 'where': { 474 'and': [ 475 { 'href_matches': '/*' }, 476 { "not": { "href_matches": "/(customer|checkout|wishlist)*" } }, 477 { 'not': { 'selector_matches': '.do-not-prerender'}}, 478 { 'not': { 'selector_matches': '[rel~=nofollow]' } } 479 ] 480 }, 481 'eagerness': 'moderate' 482 }] 483 }; 484 specScript.textContent = JSON.stringify(specRules); 485 document.body.append(specScript); 486 } 487</script>
487 488 489<link rel="canonical" href="https://uitgeverijpluim.nl/" /> 490</head> 491<body id="html-body" class="prismicio-direct-singleton page-layout-3columns">
vendor: 68 bytes, lines 491-492
491 492<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
492GTM-WDGVFB9
vendor: 83 bytes, line 492
492" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
493<script> 494 var BASE_URL = 'https://uitgeverijpluim.nl/'; 495 var THEME_PATH = 'https://uitgeverijpluim.nl/static/version1790668939/frontend/Pluim/Hyva/nl_NL'; 496 var COOKIE_CONFIG = { 497 "expires": null, 498 "path": "\u002F", 499 "domain": ".uitgeverijpluim.nl", 500 "secure": false, 501 "lifetime": "3600", 502 "cookie_restriction_enabled": false }; 503 var CURRENT_STORE_CODE = 'default'; 504 var CURRENT_WEBSITE_ID = '1'; 505 506 window.hyva = window.hyva || {} 507 508 window.cookie_consent_groups = window.cookie_consent_groups || {} 509 window.cookie_consent_groups['necessary'] = true; 510 511 window.cookie_consent_config = window.cookie_consent_config || {}; 512 window.cookie_consent_config['necessary'] = [].concat( 513 window.cookie_consent_config['necessary'] || [], 514 [ 515 'user_allowed_save_cookie', 516 'form_key', 517 'mage-messages', 518 'private_content_version', 519 'mage-cache-sessid' 520 ] 521 ); 522</script>
522 523<input name="form_key" type="hidden" value="K8rVxMaAEFHP72U6" />
523<script nonce="MzJpbnJpdDZjbzFsOHF3bG5pazUydXh3b3RsYnU3bHk="> 524 function yireoGoogleTagManager2Pusher(eventData, message, callback) { 525 window.YIREO_GOOGLETAGMANAGER2_PAST_EVENTS = window.YIREO_GOOGLETAGMANAGER2_PAST_EVENTS || []; 526 527 function doCallback(cb) { 528 if (undefined === cb) { 529 return; 530 } 531 532 cb(); 533 } 534 535 536 const copyEventData = Object.assign({}, eventData); 537 let metaData = {}; 538 if (copyEventData.meta) { 539 metaData = copyEventData.meta; 540 delete copyEventData.meta; 541 } 542 543 const eventHash = btoa(encodeURIComponent(JSON.stringify(copyEventData))); 544 if (window.YIREO_GOOGLETAGMANAGER2_PAST_EVENTS.includes(eventHash)) { 545 yireoGoogleTagManager2Logger('Warning: Event already triggered', eventData); 546 doCallback(callback); 547 return; 548 } 549 550 if (metaData && metaData.allowed_pages && metaData.allowed_pages.length > 0 551 && false === metaData.allowed_pages.some(page => window.location.pathname.includes(page))) { 552 yireoGoogleTagManager2Logger('Warning: Skipping event, not in allowed pages', window.location.pathname, eventData); 553 doCallback(callback); 554 return; 555 } 556 557 if (metaData && metaData.allowed_events && metaData.allowed_events.length > 0) { 558 for (const [allowedEventKey, allowedEvent] of Object.entries(metaData.allowed_events)) { 559 window.addEventListener(allowedEvent, function () { 560 const eventDataCopy = Object.assign({}, eventData); 561 eventDataCopy.meta.allowed_events = false; 562 yireoGoogleTagManager2Pusher(eventDataCopy, 'push (allowed event "' + eventData.event + '") [script-pusher]'); 563 }); 564 } 565 566 yireoGoogleTagManager2Logger('Warning: Skipping event, not in allowed events', window.location.pathname, eventData); 567 doCallback(callback); 568 return; 569 } 570 571 if (!message) { 572 message = 'push (unknown) [unknown]'; 573 } 574 575 yireoGoogleTagManager2Logger(message, eventData); 576 window.dataLayer = window.dataLayer || []; 577 578 if (eventData.ecommerce) { 579 window.dataLayer.push({ecommerce: null}); 580 } 581 582 try { 583 window.dataLayer.push(eventData); 584 window.YIREO_GOOGLETAGMANAGER2_PAST_EVENTS.push(eventHash); 585 } catch(error) { 586 doCallback(callback); 587 } 588 589 doCallback(callback); 590 } 591</script>
vendor: 1 bytes, line 591
591
592<script nonce="MzJpbnJpdDZjbzFsOHF3bG5pazUydXh3b3RsYnU3bHk="> 593 function yireoGoogleTagManager2Logger(...args) { 594 const debug = window.YIREO_GOOGLETAGMANAGER2_DEBUG || false; 595 if (false === debug) { 596 return; 597 } 598 599 var color = 'gray'; 600 if (args[0].toLowerCase().startsWith('push')) { 601 color = 'green'; 602 } 603 604 if (args[0].toLowerCase().startsWith('warning')) { 605 color = 'orange'; 606 } 607 608 var css = 'color:white; background-color:' + color + '; padding:1px;' 609 console.log('%cYireo_GoogleTagManager2', css, ...args); 610 } 611</script>
611 612
613<script> 614 (function(hyva) { 615 const formValidationRules = { 616 required(value, options, field, context) { 617 const el = field.element.type === 'hidden' ? createTextInputFrom(field.element) : field.element, 618 fieldName = getFieldName(field), 619 msg = fieldName 620 ? hyva.str('Het\u0020veld\u0020\u00251\u0020is\u0020verplicht.', fieldName) 621 : 'Dit\u0020is\u0020een\u0020verplicht\u0020veld.'; 622 623 if (el.type === 'radio' || el.type === 'checkbox') { 624 return (value === undefined || value.length === 0) ? msg : true; 625 } 626 627 el.setAttribute('required', ''); 628 el.checkValidity(); 629 630 return el.validity.valueMissing ? msg : true; 631 }, 632 maxlength(value, options, field, context) { 633 const n = Number(options) 634 if (value.length > n) { 635 const fieldName = getFieldName(field); 636 return fieldName 637 ? n === 1 638 ? hyva.str('Het\u0020veld\u0020\u00251\u0020mag\u0020niet\u0020langer\u0020zijn\u0020dan\u00201\u0020teken.', fieldName) 639 : hyva.str('Het\u0020veld\u0020\u00251\u0020mag\u0020niet\u0020langer\u0020zijn\u0020dan\u0020\u00252\u0020tekens.', fieldName, options) 640 : n === 1 641 ? 'Voer\u0020niet\u0020meer\u0020dan\u00201\u0020teken\u0020in.' 642 : hyva.strf('Voer\u0020niet\u0020meer\u0020dan\u0020\u00250\u0020tekens\u0020in.', options); 643 } 644 return true; 645 }, 646 minlength(value, options, field, context) { 647 const n = Number(options) 648 if (value.length > 0 && value.length < n) { 649 const fieldName = getFieldName(field); 650 return fieldName 651 ? n === 1 652 ? hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020minimaal\u00201\u0020teken\u0020lang\u0020zijn.', fieldName) 653 : hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020minimaal\u0020\u00252\u0020tekens\u0020lang\u0020zijn.', fieldName, options) 654 : n === 1 655 ? 'Voer\u0020minimaal\u00201\u0020teken\u0020in.' 656 : hyva.strf('Voer\u0020minimaal\u0020\u00250\u0020tekens\u0020in.', options); 657 } 658 return true; 659 }, 660 max(value, options, field, context) { 661 field.element.setAttribute('max', options); 662 field.element.checkValidity(); 663 if (field.element.validity.rangeOverflow) { 664 const fieldName = getFieldName(field); 665 return fieldName 666 ? hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020een\u0020waarde\u0020kleiner\u0020dan\u0020of\u0020gelijk\u0020aan\u0020\u0022\u00252\u0022\u0020bevatten.', fieldName, options) 667 : hyva.strf('Voer\u0020een\u0020waarde\u0020kleiner\u0020dan\u0020of\u0020gelijk\u0020aan\u0020\u0022\u00250\u0022\u0020in.', options); 668 } 669 return true; 670 }, 671 min(value, options, field, context) { 672 field.element.setAttribute('min', options); 673 field.element.checkValidity(); 674 if (field.element.validity.rangeUnderflow) { 675 const fieldName = getFieldName(field); 676 return fieldName 677 ? hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020een\u0020waarde\u0020groter\u0020dan\u0020of\u0020gelijk\u0020aan\u0020\u0022\u00252\u0022\u0020bevatten.', fieldName, options) 678 : hyva.strf('Voer\u0020een\u0020waarde\u0020groter\u0020dan\u0020of\u0020gelijk\u0020aan\u0020\u0022\u00250\u0022\u0020in.', options); 679 } 680 return true; 681 }, 682 step(value, options, field, context) { 683 field.element.setAttribute('step', options); 684 field.element.checkValidity(); 685 if (field.element.validity.stepMismatch) { 686 const val = Number(value), 687 step = Number(options), 688 fieldName = getFieldName(field), 689 nearestLowerValue = Math.floor(val / step) * step,
690 nearestUpperValue = Math.ceil(val / step) * step; 691 return fieldName 692 ? hyva.str('Het\u0020veld\u0020\u00251\u0020is\u0020ongeldig.\u0020De\u0020twee\u0020dichtstbijzijnde\u0020geldige\u0020waarden\u0020zijn\u0020\u0022\u00252\u0022\u0020en\u0020\u0022\u00253\u0022.', fieldName, nearestLowerValue, nearestUpperValue) 693 : hyva.strf('Voer\u0020een\u0020geldige\u0020waarde\u0020in.\u0020De\u0020twee\u0020dichtstbijzijnde\u0020geldige\u0020waarden\u0020zijn\u0020\u0022\u00250\u0022\u0020en\u0020\u0022\u00251\u0022.', nearestLowerValue, nearestUpperValue); 694 } 695 return true; 696 }, 697 pattern(value, options, field, context) { 698 field.element.setAttribute('pattern', options); 699 field.element.checkValidity(); 700 if (field.element.validity.patternMismatch) { 701 const fieldName = getFieldName(field); 702 return fieldName 703 ? field.element.pattern 704 ? hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020overeenkomen\u0020met\u0020het\u0020gevraagde\u0020formaat\u003A\u0020\u00252.', fieldName, field.element.pattern) 705 : hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020overeenkomen\u0020met\u0020het\u0020gevraagde\u0020formaat.', fieldName) 706 : field.element.pattern 707 ? hyva.strf('Houd\u0020je\u0020aan\u0020het\u0020gevraagde\u0020formaat\u003A\u0020\u00250.', field.element.pattern) 708 : 'Houd\u0020je\u0020aan\u0020het\u0020gevraagde\u0020formaat.'; 709 } 710 return true; 711 }, 712 email(value, options, field, context) { 713 const rule = /^([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9,!\#\$%&'\*\+\/=\?\^_`\{\|\}~-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*@([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z0-9-]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*\.(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]){2,})$/i; 714 if (value.length > 0 && !rule.test(value)) { 715 const fieldName = getFieldName(field); 716 return fieldName 717 ? hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020een\u0020geldig\u0020e\u002Dmailadres\u0020bevatten\u0020\u0028bijv.\u0020naam\u0040domein.nl\u0029.', fieldName) 718 : 'Voer\u0020een\u0020geldig\u0020e\u002Dmailadres\u0020in\u0020\u0028Bijv.\u003A\u0020johndoe\u0040domain.com\u0029.'; 719 } 720 return true; 721 }, 722 password(value, options, field, context) { 723 const rule = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/; 724 if (value.length > 0 && !rule.test(value)) { 725 const fieldName = getFieldName(field); 726 return fieldName 727 ? hyva.str('Het\u0020veld\u0020\u00251\u0020moet\u0020minimaal\u0020\u00E9\u00E9n\u0020hoofdletter,\u0020\u00E9\u00E9n\u0020kleine\u0020letter,\u0020\u00E9\u00E9n\u0020cijfer\u0020en\u0020\u00E9\u00E9n\u0020speciaal\u0020teken\u0020bevatten\u0020\u0028\u0023\u003F\u0021\u0040\u0024\u0025\u005E\u0026\u002A\u002D\u0029', fieldName) 728 : 'Voer\u0020minimaal\u0020\u00E9\u00E9n\u0020hoofdletter,\u0020\u00E9\u00E9n\u0020kleine\u0020letter,\u0020\u00E9\u00E9n\u0020cijfer\u0020en\u0020\u00E9\u00E9n\u0020speciaal\u0020teken\u0020in\u0020\u0028\u0023\u003F\u0021\u0040\u0024\u0025\u005E\u0026\u002A\u002D\u0029'; 729 } 730 return true; 731 }, 732 equalTo(value, options, field, context) { 733 const dependencyField = context.fields[options].element; 734 if (value !== dependencyField.value) { 735 const dependencyFieldName = 736 dependencyField.label || 737 dependencyField.title || 738 (dependencyField.labels && dependencyField.labels[0] && dependencyField.labels[0].innerText) || 739 dependencyField.name, 740 fieldName = getFieldName(field); 741 return fieldName 742 ? hyva.str('De\u0020waarde\u0020van\u0020\u00251\u0020moet\u0020gelijk\u0020zijn\u0020aan\u0020\u0022\u00252\u0022.', fieldName, dependencyFieldName) 743 : hyva.strf('De\u0020waarde\u0020van\u0020dit\u0020veld\u0020moet\u0020gelijk\u0020zijn\u0020aan\u0020\u0022\u00250\u0022.', dependencyFieldName); 744 } 745 return true; 746 } 747 }; 748 749 function raceSome(promises, pred) { 750 return new Promise((resolve, reject) => { 751 752 if (promises.length === 0) { 753 return resolve(); 754 } 755 756 let settled = false, nDone = 0; 757 758 const resolveIf = v => { 759 if (!settled && (pred(v) || ++nDone === promises.length)) { 760 settled = true; 761 resolve(v); 762 } 763 return v; 764 } 765 766 promises.map(promise => {
767 promise.then(resolveIf).catch(reason => { 768 settled = true; 769 reject(reason) 770 }); 771 return promise; 772 }); 773 }); 774 } 775 776 const INPUT_ATTRIBUTE_RULES = {min: 'min', max: 'max', required: 'required', minlength: 'minlength', maxlength: 'maxlength', step: 'step', pattern: 'pattern'} 777 const INPUT_TYPE_RULES = {email: 'email'} 778 779 function getRules(element) { 780 let rules = {}; 781 Object.keys(INPUT_ATTRIBUTE_RULES).forEach(attrName => { 782 if (element.hasAttribute(attrName)) { 783 rules[INPUT_ATTRIBUTE_RULES[attrName]] = element.getAttribute(attrName); 784 } 785 }) 786 if (INPUT_TYPE_RULES[element.type]) { 787 rules[INPUT_TYPE_RULES[element.type]] = true; 788 } 789 790 if (element.dataset.validate) { 791 try { 792 Object.assign(rules, JSON.parse(element.dataset.validate)); 793 } catch (error) { 794 console.error('Validator error. Cannot parse data-validate attribute of element:\n', element); 795 } 796 } 797 798 return rules; 799 } 800 801 function isInvalidRuleResult(ruleState) { 802 return typeof ruleState === 'string' || !ruleState || (ruleState.type && ruleState.content); 803 } 804 805 async function runValidateFn(rule, options, value, field) { 806 return formValidationRules[rule](value, options, field, this); 807 } 808 809 function generateId() { 810 let id; 811 do { 812 id = `${this.idPrefix}-${++this.idSeq}`; 813 } while (document.getElementById(id)); 814 return id; 815 } 816 817 function isVisible(element) { 818 const el = element.type !== 'hidden' ? element : (element.parentElement || {}); 819 return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length) 820 } 821 822 function elementWillValidate(element) { 823 return (element.willValidate || element.type === 'hidden') 824 && element.tagName !== 'BUTTON' 825 && element.disabled === false 826 && !(element.tagName === 'INPUT' && element.type === 'submit') 827 && (element.hasAttribute('data-validate-hidden') || isVisible(element)) 828 } 829 830 function createMessageContainer(el, fieldWrapperClassName) { 831 if (! el.parentElement) { 832 return; 833 } 834 const refocus = document.activeElement === el; 835 const wrapper = document.createElement('div'); 836 wrapper.classList.add.apply(wrapper.classList, fieldWrapperClassName.split(' ')); 837 el.parentElement.insertBefore(wrapper, el); 838 wrapper.appendChild(el); 839 refocus && document.activeElement !== el && el.focus(); 840 return wrapper; 841 } 842 843 function containerNotFound(selector, el) { 844 const msg = `Cannot find message container element ${selector} of ${el.name}`; 845 console.error(msg, el); 846 throw msg; 847 } 848 849 function createTextInputFrom(el) { 850 const text = document.createElement('INPUT'); 851 text.type = 'text'; 852 text.value = el.value; 853 return text; 854 } 855 856 function classNamesToSelector(classNames) { 857 return classNames.split(' ') 858 .filter(className => className.length > 0) 859 .map(className => `.${className}`) 860 .join('') 861 } 862 863 function hasMessagesWrapper(field, messagesWrapperClassName) { 864 return this.getMessageContainer(field).querySelector(classNamesToSelector(messagesWrapperClassName)); 865 } 866 867 function getMessagesWrapper(field, messagesWrapperClassName) { 868 if (hasMessagesWrapper.call(this, field, messagesWrapperClassName)) { 869 return this.getMessageContainer(field).querySelector(classNamesToSelector(messagesWrapperClassName)); 870 } 871 872 const msgWrapper = document.createElement('ul'); 873 const msgId = generateId.call(this); 874 msgWrapper.id = msgId; 875 field.element.setAttribute('aria-errormessage', msgId); 876 field.element.setAttribute('aria-describedby', msgId); 877 msgWrapper.classList.add.apply(msgWrapper.classList, messagesWrapperClassName.split(' ')); 878 if (field.validateOnChange) { 879 msgWrapper.setAttribute('aria-live', 'polite'); 880 } 881 this.getMessageContainer(field).appendChild(msgWrapper); 882 883 return msgWrapper; 884 } 885 886 function getCheckedValues(field) { 887 const name = field.element.name.replace(/([\\"])/g, '\\$1'); 888 const elements = field.element.form.querySelectorAll('input[name="' + name + '"]:checked'); 889 return Array.from(elements).map(el => el.value); 890 } 891 892 function escapeHtml(s) { 893 const div = document.createElement('div') 894 div.innerText = s; 895 return div.innerHTML; 896 } 897 898 899 function formValidation(form, options = {}) { 900 const formElement = form || this.$el; 901 902 if (formElement.dataset && formElement.dataset.options) { 903 try { 904 options = Object.assign(options, JSON.parse(formElement.dataset.options || '{}')); 905 } catch (e) { 906 throw new Error('Cannot read the form options from the data-options attribute: not val
906id JSON'); 907 } 908 } 909 910 // Disable browser default validation 911 if (formElement.tagName === 'FORM') { 912 formElement.setAttribute('novalidate', ''); 913 } else { 914 console.error('formValidation can be initialized only on FORM element', formElement); 915 return; 916 } 917 918 options = Object.assign({ 919 fieldWrapperClassName: 'field field-reserved', 920 messagesWrapperClassName: 'messages', 921 validClassName: 'field-success', 922 invalidClassName: 'field-error', 923 pageMessagesWrapperSelector: null, 924 scrollToFirstError: true, 925 }, options || {}); 926 927 return { 928 state: { 929 valid: false, 930 }, 931 isSubmitting: false, 932 fields: {}, 933 idSeq: 0, 934 idPrefix: formElement.id || 'vld-msg', 935 setupFields(elements) { 936 this.fields = {}; 937 Array.from(elements).forEach(element => { 938 if (elementWillValidate(element)) { 939 this.setupField(element); 940 } 941 }); 942 }, 943 setupField(element) { 944 if (! element) return; 945 const onChange = !!element.dataset.onChange; 946 if (elementWillValidate(element)) { 947 const rules = getRules(element); 948 if (Object.keys(rules).length > 0) { 949 if (this.fields[element.name]) { 950 Object.assign(this.fields[element.name].rules, rules); 951 } else { 952 this.fields[element.name] = { 953 element, 954 rules: rules, 955 validateOnChange: onChange, 956 state: { 957 valid: null, 958 rules: {} 959 } 960 } 961 } 962 } 963 } else { 964 console.error('Element will not validate', element); 965 } 966 }, 967 onSubmit(event) { 968 if (event.target.tagName === 'FORM') { 969 event.preventDefault(); 970 971 if (this.isSubmitting) return; 972 this.isSubmitting = true; 973 974 this.validate() 975 .then(() => event.target.submit()) 976 .catch(invalidElements => { 977 this.isSubmitting = false; 978 }); 979 } 980 }, 981 onChange(event) { 982 event.target.dataset.onChange = 'true'; 983 if (!Object.keys(this.fields).length) { 984 this.setupFields(formElement.elements); 985 } 986 if (!Object.keys(this.fields).includes(event.target.name)) { 987 this.setupField(event.target); 988 } 989 const field = this.fields[event.target.name]; 990 991 this.validateField(field); 992 field && field.element.removeAttribute('data-on-change') 993 }, 994 validateSafe() { 995 return new Promise(resolve => this.validate().then(() => resolve(true)).catch(() => {})) 996 }, 997 validate() { 998 if (!Object.keys(this.fields).length || !Object.keys(this.fields).length !== formElement.elements.length) { 999 this.setupFields(formElement.elements); 1000 } 1001 return new Promise(async (resolve, reject) => { 1002 if (formElement.elements) { 1003 await raceSome(this.validateFields(), result => result !== true) 1004 const invalidFields = Object.values(this.fields).filter(field => !field.state.valid); 1005 this.state.valid = invalidFields.length === 0; 1006 if (this.state.valid) { 1007 resolve(); 1008 } else { 1009 if (options.scrollToFirstError && invalidFields.length > 0) { 1010 invalidFields[0].element.focus() 1011 invalidFields[0].element.select && invalidFields[0].element.select(); 1012 } 1013 reject(invalidFields.map(field => field.element)); 1014 } 1015 } 1016 }); 1017 }, 1018 validateFields() { 1019 const fields = Object.values(this.fields); 1020 1021 fields.forEach(field => { 1022 this.getMessageContainer(field).classList.remove(options.validClassName, options.invalidClassName) 1023 }); 1024 return fields.map(field => this.validateField(field)) 1025 }, 1026 validateField(field) { 1027 if (! field || ! elementWillValidate(field.element)) { 1028 return new Promise(resolve => resolve(true)) 1029 } 1030 1031 let value; 1032 if (field.element.type === 'checkbox') { 1033 value = getCheckedValues(field); 1034 } else if (field.element.type === 'radio') { 1035 value = getCheckedValues(field)[0] || undefined; 1036 } else if (field.element.tagName === 'SELECT' && field.element.multiple) { 1037 value = Array.from(field.element.selectedOptions).map(opt => opt.value); 1038 } else { 1039 value = field.element.value; 1040 } 1041 1042 const rules = field.rules || {}; 1043 1044 field.state.valid = true;
1045 this.showFieldState(field); 1046 1047 1048 const fieldValidations = Object.keys(rules).filter(rule => formValidationRules[rule]).map(async rule => { 1049 return runValidateFn.call(this, rule, rules[rule], value, field).then(result => { 1050 field.state.rules[rule] = result; 1051 return result; 1052 }) 1053 }); 1054 1055 return new Promise(resolve => { 1056 Promise.all(fieldValidations).then(results => { 1057 field.state.valid = !elementWillValidate(field.element) || rules.length === 0 || !results.some(isInvalidRuleResult) 1058 this.showFieldState(field); 1059 resolve(field.state.valid); 1060 }) 1061 }); 1062 }, 1063 getMessagesByField(field) { 1064 const messages = []; 1065 const invalidRules = Object.keys(field.state.rules).filter(rule => isInvalidRuleResult(field.state.rules[rule])); 1066 1067 field.rules && Object.keys(field.rules).forEach((rule) => { 1068 if (invalidRules.includes(rule)) { 1069 const customMessage = field.element.getAttribute('data-msg-' + rule); 1070 const message = customMessage ? customMessage : field.state.rules[rule]; 1071 const ruleOptions = JSON.parse(JSON.stringify(field.rules[rule])); 1072 1073 if (typeof message === 'undefined' || message === null || (typeof message !== 'string' && ! message.type)) { 1074 messages.push(hyva.strf('Validation rule "%0" failed.', rule)); 1075 } else if (Array.isArray(ruleOptions)) { 1076 ruleOptions.unshift(message.type ? message.content : message); 1077 const content = hyva.strf.apply(null, ruleOptions); 1078 messages.push(message.type ? {type: message.type, content} : content); 1079 } else { 1080 const content = hyva.strf(message.type ? message.content : message, ruleOptions) 1081 messages.push(message.type ? {type: message.type, content} : content); 1082 } 1083 } 1084 }); 1085 return messages; 1086 }, 1087 /** @deprecated */ 1088 getFieldWrapper(field) { 1089 return this.getMessageContainer(field) 1090 }, 1091 getMessageContainer(field) { 1092 let container; 1093 const pageSelector = field.element.getAttribute('data-validation-container') || options.pageMessagesContainerSelector; 1094 if (pageSelector) { 1095 container = document.querySelector(pageSelector) 1096 || containerNotFound(pageSelector, field.element) 1097 } else { 1098 const containerSelector = classNamesToSelector(options.fieldWrapperClassName); 1099 container = field.element.closest(containerSelector) 1100 || createMessageContainer(field.element, options.fieldWrapperClassName) 1101 || containerNotFound(containerSelector, field.element); 1102 } 1103 1104 return container; 1105 }, 1106 showFieldState(field) { 1107 const container = this.getMessageContainer(field), 1108 hasErrorMessages = hasMessagesWrapper.call(this, field, options.messagesWrapperClassName), 1109 messages = this.getMessagesByField(field).map(m => { 1110 return m.type !== 'html' ? escapeHtml(m.type ? m.content : m) : m.content; 1111 }); 1112 container.classList.toggle(options.validClassName, field.state.valid && ! hasErrorMessages); 1113 container.classList.toggle(options.invalidClassName, !field.state.valid || hasErrorMessages); 1114 this.createHtmlErrorMessage(field, messages); 1115 1116 if (field.state.valid) { 1117 field.element.removeAttribute('aria-invalid'); 1118 } else { 1119 field.element.setAttribute('aria-invalid', 'true'); 1120 if (! document.activeElement) { 1121 field.element.focus(); 1122 } 1123 } 1124 }, 1125 removeMessages(field, messagesClass) { 1126 if (! hasMessagesWrapper.call(this, field, messagesClass || options.messagesWrapperClassName)) { 1127 return; 1128 } 1129 1130 const msgWrapper = getMessagesWrapper.call(this, field, messagesClass || options.messagesWrapperClassName); 1131 const messages = msgWrapper.querySelectorAll(`[data-msg-field='${field.element.name}']`);
1132 Array.from(messages).forEach(msg => msg.remove()); 1133 if (msgWrapper && msgWrapper.childElementCount === 0) { 1134 field.element.removeAttribute('aria-errormessage'); 1135 field.element.removeAttribute('aria-describedby'); 1136 msgWrapper.remove(); 1137 } 1138 }, 1139 createErrorMessage(field, messages) { 1140 const htmlMessages = (Array.isArray(messages) ? messages : [messages]).map(escapeHtml) 1141 this.createHtmlErrorMessage(field, htmlMessages); 1142 }, 1143 createHtmlErrorMessage(field, messages) { 1144 this.removeMessages(field, options.messagesWrapperClassName); 1145 field.element.removeAttribute('aria-errormessage'); 1146 field.element.removeAttribute('aria-describedby'); 1147 1148 if (!field.state.valid) { 1149 const msgWrapper = this.addHtmlMessages(field, options.messagesWrapperClassName, messages); 1150 field.element.setAttribute('aria-errormessage', msgWrapper.id); 1151 field.element.setAttribute('aria-describedby', msgWrapper.id); 1152 } 1153 }, 1154 /** @deprecated */ 1155 createMessage(field, message) { 1156 return this.addMessages(field, options.messagesWrapperClassName, message); 1157 }, 1158 addMessages(field, messagesClass, messages) { 1159 const htmlMessages = (Array.isArray(messages) ? messages : [messages]).map(escapeHtml) 1160 return this.addHtmlMessages(field, messagesClass, htmlMessages); 1161 }, 1162 addHtmlMessages(field, messagesClass, htmlMessages) { 1163 const msgWrapper = getMessagesWrapper.call(this, field, messagesClass); 1164 1165 (Array.isArray(htmlMessages) ? htmlMessages : [htmlMessages]).forEach((htmlMessage) => { 1166 const li = document.createElement('li'); 1167 li.innerHTML = htmlMessage; 1168 li.setAttribute('data-msg-field', field.element.name); 1169 msgWrapper.appendChild(li); 1170 }); 1171 1172 return msgWrapper; 1173 }, 1174 setField(name, value) { 1175 this.fields[name].element.value = value; 1176 this.fields[name].element.dispatchEvent((new Event('input'))); 1177 this.validateField(this.fields[name]); 1178 } 1179 } 1180 } 1181 1182 function getFieldName(field) { 1183 const fieldName = field.element?.getAttribute('title') 1184 || document.querySelector(`label[for="${field.element?.id}"]`)?.innerText; 1185 1186 return fieldName 1187 ? fieldName.trim() 1188 : ''; 1189 } 1190 1191 hyva.formValidation = formValidation; 1192 hyva.formValidation.rules = formValidationRules; 1193 hyva.formValidation.setInputAttributeRuleName = (attrName, ruleName) => INPUT_ATTRIBUTE_RULES[attrName] = ruleName || attrName; 1194 hyva.formValidation.setInputTypeRuleName = (typeName, ruleName) => INPUT_TYPE_RULES[typeName] = ruleName || typeName; 1195 hyva.formValidation.addRule = (name, validator) => formValidationRules[name] = validator; 1196 hyva.formValidation.getFieldName = getFieldName; 1197 }(window.hyva = window.hyva || {})); 1198 1199 window.addEventListener('alpine:init', () => { 1200 Alpine.data('hyva.formValidation', hyva.formValidation); 1201 Alpine.data('hyva.formValidation($el)', hyva.formValidation); 1202 }, {once: true}); 1203 //# sourceURL=hyva-advanced-form-validation.js 1204</script>
1204 1205<div class="page-wrapper">
1205<script> 1206 function initHeader () { 1207 return { 1208 menuOpen: false, 1209 cart: {}, 1210 getData: function getData(data) { 1211 if (data.cart) { this.cart = data.cart } 1212 } 1213 } 1214 } 1215</script>
1215 1216 1217<main :class="{'translate-x-64 transform': menuOpen}" class="duration-500 transition-all main" role="main" x-data="initHeader()" @toggle-mobile-menu.window="menuOpen = !menuOpen" @private-content-loaded.window="getData(event.detail.data)"> 1218 <div> 1219 <div class="container mx-auto px-3 sm:px-4"> 1220 <div class="grid grid-cols-12"> 1221 <header class="col-span-12 lg:col-span-3 xl:col-span-2"> 1222 <h1 class="pt-4 pb-2 lg:pb-6 mt-4 lg:my-6 flex justify-center lg:justify-start"> 1223 <div class="order-1 md:order-1 w-full sm:w-auto pb-2 sm:pb-0"> 1224 <a class="pt-4 flex items-center justify-center tracking-wide no-underline hover:no-underline font-medium font-title text-gray-800 text-xl leading-normal" 1225 href="https://uitgeverijpluim.nl/" 1226 title="Uitgeverij Pluim" 1227 aria-label="store logo"> 1228 <img src="https://uitgeverijpluim.nl/media/logo/websites/1/logo_sxfncc_1.jpg" 1229 title="Uitgeverij Pluim" 1230 alt="Uitgeverij Pluim" 1231 width="200" height="130" /> 1232 </a> 1233</div> 1234 </h1> 1235 <div x-cloak @click="$dispatch('toggle-mobile-menu')" :class="{'hidden': !menuOpen}" class="fixed top-0 left-0 w-full h-full z-40 bg-opacity-25 bg-black transform transition-all duration-500"></div> 1236 1237<nav x-cloak class="-translate-x-64 fixed left-0 top-0 w-64 lg:w-full h-screen overflow-y-auto z-40 transform transition-all lg:translate-x-0 duration-500 bg-white p-5 lg:p-0 lg:relative"> 1238 <ul> 1239 <li> 1240 <a href="/" class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Home</a> 1241 </li> 1242 <li> 1243 <a href="/schrijvers" class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Schrijvers</a> 1244 </li> 1245 <li> 1246 <a href="https://uitgeverijpluim.nl/boeken" 1247 title="Boeken" 1248 class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Boeken</a> 1249 </li> 1250 <li> 1251 <ul class="pt-1 pb-4"> 1252 <li><a href="https://uitgeverijpluim.nl/boeken/fictie" title="Fictie" class="block py-1 px-2 leading-snug text-gray-900 hover:text-gray-1400 transition-colors duration-400">Fictie</a></li> 1253 <li><a href="https://uitgeverijpluim.nl/boeken/poezie" title="Poëzie" class="block py-1 px-2 leading-snug text-gray-900 hover:text-gray-1400 transition-colors duration-400">Poëzie</a></li> 1254 <li><a href="https://uitgeverijpluim.nl/boeken/non-fictie" title="Non-fictie" class="block py-1 px-2 leading-snug text-gray-900 hover:text-gray-1400 transition-colors duration-400">Non-fictie</a></li> 1255 <li><a href="https://uitgeverijpluim.nl/boeken/verwacht" title="Verwacht" class="block py-1 px-2 leading-snug text-gray-900 hover:text-gray-1400 transition-colors duration-400">Verwacht</a></li> 1256 </ul> 1257 </li> 1258 <li> 1259 <a href="/nieuws" class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Nieuws</a> 1260 </li> 1261 <li> 1262 <a href="/agenda" class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Agenda</a> 1263 </li> 1264 <li> 1265 <a href="/over-ons" class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Over ons</a> 1266 </li> 1267 <li> 1268 <a href="/contact" class="block py-1 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400">Contact</a> 1269 </li> 1270 </ul> 1271</nav> 1272 </header> 1273 <div class="col-span-12 lg:col-span-9 xl:col-span-10"> 1274
1275<script> 1276 function initCartSummary() { 1277 return { 1278 searchOpen: false, 1279 cart: {}, 1280 getSummary: function (data) { 1281 if (data.cart) { 1282 this.cart = data.cart 1283 } 1284 } 1285 } 1286 } 1287</script>
1287 1288<div id="cart-drawer" class="relative flex justify-between py-4 lg:py-6"> 1289 <div class="flex items-center"> 1290 <button x-data @click="$dispatch('toggle-mobile-menu')" aria-label="Menu openen" 1291 name="menu" 1292 class="-ml-2 sm:-ml-4 text-gray-1400 px-2 sm:px-4 py-4 lg:hidden focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-1400"> 1293 <svg viewBox="0 0 120 80" fill="currentColor" stroke="currentColor" 1294 stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-4 h-4 ml-2" > 1295 <rect width="120" height="15"></rect> 1296 <rect y="35" width="120" height="15"></rect> 1297 <rect y="70" width="120" height="15"></rect> 1298 </svg> 1299 </button> 1300 </div> 1301 <nav class="flex-1 navbar" @keydown.window.escape="searchOpen = false;" x-data="initCartSummary()" 1302 @private-content-loaded.window="getSummary(event.detail.data)"> 1303 <ul class="flex justify-end -mr-2 sm:-mr-4"> 1304 <li class="justify-end w-full lg:w-auto flex lg:relative"> 1305 <a href="#" aria-label="Search" class="order-2 flex items-center h-full px-2 sm:px-4 text-gray-900 text-base leading-normal sm:text-lg leading-normal" 1306 @click.prevent="searchOpen = !searchOpen; $nextTick(function () { $refs.searchInput.select(); });" id="menu-search-icon"> 1307 <svg class="h-5 w-5 -mt-1" stroke="currentColor" fill="none" viewBox="0 0 24 24"> 1308 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 1309 d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path> 1310 </svg> 1311 </a> 1312 <div 1313 :class="{ 'block': searchOpen, 'hidden': !searchOpen }" 1314 @click.outside="searchOpen = false" 1315 x-show="searchOpen" 1316 class="hidden order-1 flex-1 lg:flex-0 w-full top-full sm:h-full sm:w-64 lg:w-96"> 1317 <form class="pt-3" id="search_mini_form" action="https://uitgeverijpluim.nl/catalogsearch/result/" 1318 method="get"> 1319 <input x-ref="searchInput" 1320 type="search" 1321 placeholder="Doorzoek de hele winkel" 1322 id="search-menu" 1323 name="q" 1324 value="" 1325 maxlength="128" 1326 class="text-gray-1000 placeholder-gray-800 w-full px-3 h-2md focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-1400" 1327 style="background-color: #fafafa; border-color: #fafafa;"> 1328 </form> 1329 </div> 1330 </li> 1331 <li> 1332 <a title="Mijn account" 1333 href="https://uitgeverijpluim.nl/customer/account/index/" 1334 class="block px-2 sm:px-4 py-4 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400"> 1335 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 1336 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 1337 d="M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4z"></path> 1338 </svg> 1339 </a> 1340 </li> 1341 <li class="relative"> 1342 <template x-if="cart && cart.summary_count > 0"> 1343 <div class="right-0 top-0 mt-2 absolute bg-gray-800 text-white h-5 w-5 rounded-full flex items-center justify-center text-xs leading-normal" x-html="cart.summary_count"></div> 1344 </template> 1345 <a title="Winkelwagen" 1346 href="https://uitgeverijpluim.nl/checkout/cart/index/" 1347 class="block px-2 sm:px-4 py-4 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400"> 1348 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 1349 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 1350 d="M576 216v16c0 13.255-10.745 24-24 24h-8l-26.113 182.788C514.509 462.435 494.257 480 470.37 480H105.63c-23.887 0-44.139-17.565-47.518-41.212L32 256h-8c-13.255 0-24-10.745-24-24v-16c0-13.255 10.745-24 24-24h67.341l106.78-146.821c10.395-14.292 30.407-17.453 44.701-7.058 14.293 10.395 17.453 30.408 7.058 44.701L170.477 192h235.046L326.12 82.821c-10.395-14.292-7.234-34.306 7.059-44.701 14.291-10.395 34.306-7.235 44.701 7.058L484.659 192H552c13.255 0 24 10.745 24 24zM312 392V280c0-13.255-10.745-24-24-24s-24 10.745-24 24v112c0 13.255 10.745 24 24 24s24-10.745 24-24zm112 0V280c0-13.255-10.745-24-24-24s-24 10.745-24 24v112c0 13.255 10.745 24 24 24s24-10.745 24-24zm-224 0V280c0-13.255-10.745-24-24-24s-24 10.745-24 24v112c0 13.255 10.745 24 24 24s24-10.745 24-24z"></path> 1351 </svg> 1352 </a> 1353 </li> 1354 <li class="hidden md:block"> 1355 <div class="p-4 text-lg leading-normal font-bold">/</div> 1356 </li> 1357 <li class="hidden md:block"> 1358 <a title="Instagram" target="_new" href="https://www.instagram.com/uitgeverijpluim/" 1359 class="block px-2 sm:px-4 py-4 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400"> 1360 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 1361 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 1362 d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z"></path> 1363 </svg> 1364 </a> 1365 </li> 1366 <li class="hidden md:block"> 1367 <a title="Facebook" target="_new" href="https://www.facebook.com/uitgeverijpluim" 1368 class="block px-2 sm:px-4 py-4 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400"> 1369 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 1370 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 1371 d="M279.14 288l14.22-92.66h-88.91v
1371-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z"></path> 1372 </svg> 1373 </a> 1374 </li> 1375 <li class="hidden md:block"> 1376 <a title="YouTube" target="_new" 1377 href="https://www.youtube.com/channel/UCRr07oGz4RdkdjrntFZ76nw" 1378 class="block px-2 sm:px-4 py-4 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400"> 1379 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 1380 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 1381 d="M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.78 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z"></path> 1382 </svg> 1383 </a> 1384 </li> 1385 <li class="hidden md:block"> 1386 <a title="Soundcloud" target="_new" href="https://soundcloud.com/uitgeverijpluim" 1387 class="block px-2 sm:px-4 py-4 uppercase text-gray-1400 hover:text-gray-900 transition-colors duration-400"> 1388 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 1389 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 1390 d="M256 32C114.52 32 0 146.496 0 288v48a32 32 0 0 0 17.689 28.622l14.383 7.191C34.083 431.903 83.421 480 144 480h24c13.255 0 24-10.745 24-24V280c0-13.255-10.745-24-24-24h-24c-31.342 0-59.671 12.879-80 33.627V288c0-105.869 86.131-192 192-192s192 86.131 192 192v1.627C427.671 268.879 399.342 256 368 256h-24c-13.255 0-24 10.745-24 24v176c0 13.255 10.745 24 24 24h24c60.579 0 109.917-48.098 111.928-108.187l14.382-7.191A32 32 0 0 0 512 336v-48c0-141.479-114.496-256-256-256z" 1391 "></path> 1392 </svg> 1393 </a> 1394 </li> 1395 </ul> 1396 </nav> 1397</div>
1398 <!--Cart Drawer--> 1399
1399<script> 1400 function initCartDrawer() { 1401 return { 1402 open: false, 1403 cart: {}, 1404 getData: function (data) { 1405 if (data.cart) { 1406 this.cart = data.cart 1407 } 1408 }, 1409 getCartItems: function getCartItems () { 1410 return this.cart && this.cart.items.sort(function(a,b) { return a.item_id - b.item_id }) || {} 1411 }, 1412 deleteItemFromCart: function deleteItemFromCart (itemId) { 1413 var formKey = document.querySelector('input[name=form_key]').value; 1414 fetch(BASE_URL+"checkout/sidebar/removeItem/", { 1415 "headers": { 1416 "content-type": "application/x-www-form-urlencoded; charset=UTF-8", 1417 }, 1418 "body": "form_key="+ formKey + "&item_id="+itemId, 1419 "method": "POST", 1420 "mode": "cors", 1421 "credentials": "include" 1422 }).then(function (response) { 1423 if (response.redirected) { 1424 window.location.href = response.url; 1425 } else if (response.ok) { 1426 return response.json(); 1427 } else { 1428 typeof window.dispatchMessages !== "undefined" && window.dispatchMessages( 1429 [{ 1430 type: "warning", 1431 text: "Kan item niet uit offerte verwijderen." 1432 }], 5000 1433 ); 1434 } 1435 }).then(function (response) { 1436 typeof window.dispatchMessages !== "undefined" && window.dispatchMessages( 1437 [{ 1438 type: response.success ? "success" : "error", 1439 text: response.success 1440 ? "Je hebt het product verwijderd." 1441 : response.error_message 1442 }], 5000 1443 ); 1444 var reloadCustomerDataEvent = new CustomEvent("reload-customer-section-data"); 1445 window.dispatchEvent(reloadCustomerDataEvent); 1446 }); 1447 } 1448 } 1449 } 1450</script>
1450 1451<section id="cart-drawer" 1452 x-data="initCartDrawer()" 1453 @private-content-loaded.window="getData(event.detail.data)" 1454 @toggle-cart.window="open=true;" 1455 @keydown.window.escape="open=false" 1456> 1457 <template x-if="cart && cart.summary_count"> 1458 <div role="dialog" 1459 aria-labelledby="cart-drawer-title" 1460 aria-modal="true" 1461 @click.outside="open = false" 1462 class="fixed inset-y-0 right-0 max-w-full flex"> 1463 <div class="relative w-screen max-w-md" 1464 x-show="open" 1465 x-transition:enter="transform transition ease-in-out duration-500 sm:duration-700" 1466 x-transition:enter-start="translate-x-full" 1467 x-transition:enter-end="translate-x-0" 1468 x-transition:leave="transform transition ease-in-out duration-500 sm:duration-700" 1469 x-transition:leave-start="translate-x-0" 1470 x-transition:leave-end="translate-x-full" 1471 > 1472 <div 1473 x-show="open" 1474 x-transition:enter="ease-in-out duration-500" 1475 x-transition:enter-start="opacity-0" 1476 x-transition:enter-end="opacity-100" 1477 x-transition:leave="ease-in-out duration-500" 1478 x-transition:leave-start="opacity-100" 1479 x-transition:leave-end="opacity-0" class="absolute top-0 right-0 mt-2 p-2 flex"> 1480 <button @click="open = false;" aria-label="Close panel" class="text-gray-300 hover:text-black transition ease-in-out duration-150 p-2"> 1481 <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 1482 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path> 1483 </svg> 1484 </button> 1485 </div> 1486 <div class="h-full flex flex-col space-y-6 py-6 bg-white shadow-xl"> 1487 <header class="px-4 sm:px-6"> 1488 <h2 id="cart-drawer-title" class="text-lg leading-normal leading-7 font-medium text-gray-900"> 1489 Winkelwagen </h2> 1490 </header> 1491 <div class="relative grid gap-6 bg-white py-6 sm:gap-8 px-4 sm:px-6 border-b border-container overflow-y-auto"> 1492 <template x-for="item in getCartItems()"> 1493 <div class="-m-3 p-3 flex items-start space-x-4 rounded-lg hover:bg-gray-100 transition ease-in-out duration-150"> 1494 <a :href="item.product_url" class="w-1/4"> 1495 <img 1496 :src="item.product_image.src" 1497 :alt="item.product_name" 1498 :width="item.product_image.width" 1499 :height="item.product_image.height" 1500 loading="lazy" 1501 /> 1502 </a> 1503 <div class="space-y-2 w-3/4"> 1504 <div> 1505 <p class="text-xl leading-normal"><span x-html="item.qty"></span> x <span x-html="item.product_name"></span></p> 1506 <p class="text-sm leading-normal"><span x-html="item.product_sku"></p> 1507 </div> 1508 <template x-for="option in item.options"> 1509 <div class="pt-2"> 1510 <p class="font-semibold" x-text="option.label + ':'"></p> 1511 <p class="text-secondary" x-text="option.value"></p> 1512 </div> 1513 </template> 1514 <p><span x-html="item.product_price"></span></p> 1515 <div class="pt-4"> 1516 <a :href="item.configure_url" 1517 class="btn btn-primary inline-flex mr-2 p-2"> 1518 <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" size="16" class="h-5 w-5"> 1519 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"> 1520 1521 </path> 1522 </svg> 1523 </a> 1524 <button class="btn btn-primary inline-flex p-2" 1525 @click="deleteItemFromCart(item.item_id)" 1526 > 1527 <svg fill="none" viewBox="0 0 24 24" stroke="currentColor" size="16" class="h-5 w-5"> 1528 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path> 1529 </svg> 1530 </button> 1531 </div> 1532 1533 </div> 1534 </div> 1535 </template> 1536 </div> 1537 <div class="relative grid gap-6 bg-white py-6 sm:gap-8 px-4 sm:px-6"> 1538 <div class="w-full -m-3 p-3 space-x-4 roun
1538ded-lg hover:bg-gray-100 transition ease-in-out duration-150"> 1539 <p>Subtotal: <span x-html="cart.subtotal"></span></p> 1540 </div> 1541 <div class="w-full -m-3 p-3 space-x-4 rounded-lg hover:bg-gray-100 transition ease-in-out duration-150"> 1542 <a href="https://uitgeverijpluim.nl/checkout/index/" class="btn btn-primary inline-flex">Checkout</a> 1543 <span>or</span> <a href="https://uitgeverijpluim.nl/checkout/cart/" class="underline">Go to Cart</a> 1544 </div> 1545 </div> 1546 </div> 1547 </div> 1548 </div> 1549 </template> 1550</section> 1551<main id="maincontent" class="page-main"><div class="page messages">
1551<script> 1552 function initMessages() { 1553 "use strict"; 1554 return { 1555 messages: window.mageMessages || [], 1556 isEmpty() { 1557 return this.messages.reduce( 1558 function (isEmpty, message) { 1559 return isEmpty && message === undefined 1560 }, true 1561 ) 1562 }, 1563 hasMessages() { 1564 return !this.isEmpty(); 1565 }, 1566 hasMessage() { 1567 return !!this.message; 1568 }, 1569 removeMessage() { 1570 this.messages[this.index] = undefined; 1571 }, 1572 addMessages(messages, hideAfter) { 1573 messages.map((message) => { 1574 this.messages = this.messages.concat(message); 1575 if (hideAfter === undefined && message.type === 'success' && window.defaultSuccessMessageTimeout) { 1576 hideAfter = window.defaultSuccessMessageTimeout; 1577 } 1578 if (hideAfter) { 1579 this.setHideTimeOut(this.messages.length -1, hideAfter); 1580 } 1581 }); 1582 }, 1583 setHideTimeOut(messageIndex, hideAfter) { 1584 setTimeout((messageIndex) => { 1585 this.removeMessage(messageIndex); 1586 }, hideAfter, messageIndex); 1587 }, 1588 eventListeners: { 1589 ['@messages-loaded.window'](event) { 1590 this.addMessages(event.detail.messages, event.detail.hideAfter) 1591 }, 1592 ['@private-content-loaded.window'](event) { 1593 const data = event.detail.data; 1594 if ( 1595 data.messages && 1596 data.messages.messages && 1597 data.messages.messages.length 1598 ) { 1599 this.addMessages(data.messages.messages); 1600 } 1601 }, 1602 ['@clear-messages.window']() { 1603 this.messages = []; 1604 } 1605 }, 1606 getMessageUiId() { 1607 return 'message-' + this.message.type; 1608 } 1609 } 1610 } 1611 1612 window.addEventListener('alpine:init', () => Alpine.data('initMessages', initMessages), {once: true}) 1613</script>
1613 1614<section id="messages" 1615 x-data="initMessages" 1616 x-bind="eventListeners" 1617 aria-live="assertive" 1618 role="alert" 1619> 1620 <template x-if="hasMessages"> 1621 <div class="w-full"> 1622 <div class="messages container mx-auto py-3"> 1623 <template x-for="(message, index) in messages" :key="index"> 1624 <div> 1625 <template x-if="hasMessage"> 1626 <div class="message" 1627 :class="message.type" 1628 :ui-id="getMessageUiId" 1629 > 1630 <span x-html="message.text"></span> 1631 <button 1632 type="button" 1633 class="text-gray-600 hover:text-black" 1634 aria-label="Bericht sluiten" 1635 @click.prevent="removeMessage" 1636 > 1637 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="stroke-current" width="18" height="18" aria-hidden="true"> 1638 <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/> 1639</svg> 1640 </button> 1641 </div> 1642 </template> 1643 </div> 1644 </template> 1645 </div> 1646 </div> 1647 </template> 1648</section> 1649</div><div class="row"><div><div class="row"><div class="columns"><div class="column main"><section class="content-wrap box-border block font-normal text-sm leading-normal leading-normal mb-2 lg:mb-24 text-black"> 1650 <div class="grid lg:grid-cols-6"> 1651 1652 1653 1654<div class="bg-green-700 mb-2 ml-1 lg:col-span-6 order-1"> 1655 <a aria-label="omslag In alle verscheidenheid van Marli Huijer " href="/boeken/non-fictie/de-ander-tegengif-tegen-vijanddenken"> <img 1656 src="https://images.prismic.io/uitgeverijpluim/tDzPyVfkg3rVIBvY_bannergrootMarliHuijer-1-.png?auto=format,compress&ar=1%3A0.33&fit=crop&w=1400" 1657 width="1400" 1658 height="462" 1659 srcset="https://images.prismic.io/uitgeverijpluim/tDzPyVfkg3rVIBvY_bannergrootMarliHuijer-1-.png?auto=format,compress&ar=1%3A0.33&fit=crop&w=1400 1024w, 1660 https://images.prismic.io/uitgeverijpluim/tDzPyVfkg3rVIBvY_bannergrootMarliHuijer-1-.png?auto=format,compress&ar=1%3A0.33&fit=crop&w=1400 700w" 1661 sizes="(min-width: 1024px) 1024w, 700w" 1662 alt="omslag In alle verscheidenheid van Marli Huijer " 1663 class="w-full transition duration-500 ease-in-out hover:opacity-50"> 1664 </a></div> 1665 1666 1667 1668<div class="bg-green-700 mb-2 ml-1 lg:col-span-2 order-3 lg:order-2"> 1669 <a aria-label="boekomslag Red" href="/boeken/fictie/red"> <img 1670 src="https://images.prismic.io/uitgeverijpluim/oJ2PUlgldqU2c4U6_Redbannerlinksonder.png?auto=format,compress&ar=1%3A1.33&fit=crop&w=1400" 1671 width="1400" 1672 height="1862" 1673 srcset="https://images.prismic.io/uitgeverijpluim/oJ2PUlgldqU2c4U6_Redbannerlinksonder.png?auto=format,compress&ar=1%3A1.33&fit=crop&w=1400 1024w, 1674 https://images.prismic.io/uitgeverijpluim/oJ2PUlgldqU2c4U6_Redbannerlinksonder.png?auto=format,compress&ar=1%3A0.5&fit=crop&crop=edges&w=700 700w" 1675 sizes="(min-width: 1024px) 1024w, 700w" 1676 alt="boekomslag Red" 1677 class="w-full transition duration-500 ease-in-out hover:opacity-50"> 1678 </a></div> 1679 1680 1681 1682<div class="bg-green-700 mb-2 ml-1 lg:col-span-4 order-2 lg:order-3"> 1683 <a aria-label="omslag Massih Hutak met een aanbeveling van Karin Amatmoekrim" href="/boeken/fictie/voor-issa"> <img 1684 src="https://images.prismic.io/uitgeverijpluim/hOOcb97_XCldbHcq_Bannerwebsiterechtsonder-1-.png?auto=format,compress&ar=1%3A0.66&fit=crop&w=1400" 1685 width="1400" 1686 height="924" 1687 srcset="https://images.prismic.io/uitgeverijpluim/hOOcb97_XCldbHcq_Bannerwebsiterechtsonder-1-.png?auto=format,compress&ar=1%3A0.66&fit=crop&w=1400 1024w, 1688 https://images.prismic.io/uitgeverijpluim/hOOcb97_XCldbHcq_Bannerwebsiterechtsonder-1-.png?auto=format,compress&ar=1%3A0.66&fit=crop&w=1400 700w" 1689 sizes="(min-width: 1024px) 1024w, 700w" 1690 alt="omslag Massih Hutak met een aanbeveling van Karin Amatmoekrim" 1691 class="w-full transition duration-500 ease-in-out hover:opacity-50"> 1692 </a></div> 1693 </div> 1694</section> 1695</div></div></div></div></div></main><footer class="page-footer"><div class="footer content"><footer class="mt-16 lg:mt-32"> 1696 <div class="grid grid-cols-12 gap-4"> 1697 <div class="col-span-12 sm:col-span-3"> 1698 <h2 class="uppercase text-gray-1400 mb-md text-3xl">Uitgeverij Pluim</h2> 1699 <ul class="mb-5"> 1700 <li><a href="/privacybeleid" 1701 class="block uppercase text-secondary text-sm leading-normal hover:text-primary transition-colors duration-400">Privacybeleid</a></li> 1702 <li class="mt-4 lg:mt-0"><a href="/over-ons" 1703 class="block uppercase text-secondary text-sm leading-normal hover:text-primary transition-colors duration-400">Over 1704 ons</a></li> 1705 <li class="mt-4 lg:mt-0"><a href="/contact" 1706 class="block uppercase text-secondary text-sm leading-normal hover:text-primary transition-colors duration-400">Contact</a> 1707 </li> 1708 </ul> 1709 </div> 1710 <div class="col-span-12 sm:col-span-5 lg:col-span-4 xl:col-span-3"> 1711 <div class="lg:w-1/4 md:w-1/2 w-full lg:px-4"> 1712 <form 1713 class="form subscribe mb-2md" 1714 action="https://uitgeverijpluim.nl/newsletter/subscriber/new/" 1715 method="post" 1716 x-data="initNewsletterForm" 1717 @submit.prevent="submitForm" 1718 id="newsletter-validate-detail" 1719 aria-label="Abonneren op de nieuwsbrief" 1720 > 1721 <h2 class="uppercase text-gray-1400 mb-md text-3xl">Nieuwsbrief</h2> 1722 <div class="flex justify-center gap-2 md:justify-start"> 1723 <label for="newsletter-subscribe" class="sr-only"> 1724 E-mailadres </label> 1725 <input 1726 name="email" 1727 type="email" 1728 required 1729 id="newsletter-subscribe" 1730 class="text-gray-1000 h-2md flex-grow border-2 border-gray-300 px-3 mr-1" 1731 placeholder="E-mailadres" 1732 autocomplete="email" 1733 aria-describedby="footer-newsletter-heading" 1734 > 1735 <input name="form_key" type="hidden" value="K8rVxMaAEFHP72U6" /> <button type="submit" class="bg-primary h-2md block px-3 text-gray-900 uppercase"> 1736 OK </button> 1737 </div> 1738 <div> 1739 <template x-if="displayErrorMessage"> 1740 <p class="flex items-center text-red">
1741 <span class="inline-block w-8 h-8 mr-3"> 1742 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" width="24" height="24" aria-hidden="true"> 1743 <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/> 1744</svg> 1745 </span> 1746 <template x-for="errorMessage in errorMessages"> 1747 <span x-html="errorMessage"></span> 1748 </template> 1749 </p> 1750 </template> 1751 </div> 1752 </form> 1753 <div class="w-full"> 1754 </div> 1755
1755<script> 1756 function initNewsletterForm() { 1757 return { 1758 errors: 0, 1759 hasCaptchaToken: 0, 1760 displayErrorMessage: false, 1761 errorMessages: [], 1762 setErrorMessages(messages) { 1763 this.errorMessages = [messages] 1764 this.displayErrorMessage = this.errorMessages.length 1765 }, 1766 submitForm() { 1767 // Do not rename $form, the variable is expected to be declared in the recaptcha output 1768 const $form = document.querySelector('#newsletter-validate-detail'); 1769 1770 if (this.errors === 0) { 1771 $form.submit(); 1772 } 1773 } 1774 } 1775 } 1776 1777 window.addEventListener('alpine:init', () => Alpine.data('initNewsletterForm', initNewsletterForm), {once: true}) 1778 </script>
1778 1779 </div> 1780 <ul class="flex md:hidden"> 1781 <li class="pr-4"> 1782 <a class="text-gray-700 text-xl leading-normal" href="https://www.instagram.com/uitgeverijpluim/" 1783 target="_blank" title="Instagram" rel="noreferrer"> 1784 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 1785 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 1786 d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z"></path> 1787 </svg> 1788 </a> 1789 </li> 1790 <li class="pr-4"> 1791 <a class="text-gray-700 text-xl leading-normal" href="https://www.facebook.com/uitgeverijpluim" 1792 target="_blank" title="Facebook" rel="noreferrer"> 1793 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 1794 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 1795 d="M279.14 288l14.22-92.66h-88.91v-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z"></path> 1796 </svg> 1797 </a> 1798 </li> 1799 <li class="pr-4"> 1800 <a class="text-gray-700 text-xl leading-normal" 1801 href="https://www.youtube.com/channel/UCRr07oGz4RdkdjrntFZ76nw" target="_blank" title="YouTube" 1802 rel="noreferrer"> 1803 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 1804 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 1805 d="M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.78 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z"></path> 1806 </svg> 1807 </a> 1808 </li> 1809 <li class="pr-4"> 1810 <a class="text-gray-700 text-xl leading-normal" href="https://twitter.com/uitgeverijpluim" 1811 target="_blank" title="Twitter" rel="noreferrer"> 1812 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 1813 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 1814 d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path> 1815 </svg> 1816 </a> 1817 </li> 1818 <li class="pr-4"> 1819 <a class="text-gray-700 text-xl leading-normal" href="https://soundcloud.com/uitgeverijpluim" 1820 target="_blank" title="Soundcloud" rel="noreferrer"> 1821 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 1822 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 1823 d="M256 32C114.52 32 0 146.496 0 288v48a32 32 0 0 0 17.689 28.622l14.383 7.191C34.083 431.903 83.421 480 144 480h24c13.255 0 24-10.745 24-24V280c0-13.255-10.745-24-24-24h-24c-31.342 0-59.671 12.879-80 33.627V288c0-105.869 86.131-192 192-192s192 86.131 192 192v1.627C427.671 268.879 399.342 256 368 256h-24c-13.255 0-24 10.745-24 24v176c0 13.255 10.745 24 24 24h24c60.579 0 109.917-48.098 111.928-108.187l14.382
1823-7.191A32 32 0 0 0 512 336v-48c0-141.479-114.496-256-256-256z" 1824 "></path> 1825 </svg> 1826 </a> 1827 </li> 1828 </ul> 1829 </div> 1830 </div> 1831 <p class="text-xs leading-normal mt-12 mb-5 lg:text-right">© Uitgeverij Pluim</p> 1832</footer> 1833</div> 1834</div> 1835</div> 1836</div> 1837</main> 1838</body> 1839</html> 1840</div></footer>
1840<script type="speculationrules">{"prefetch":[{"source":"document","where":{"and":[{"href_matches":"\/*"},{"not":{"href_matches":"\/*(customer|login|logout|auth|cart|checkout|search|download|redirect|rewrite|store|productalert)\/*"}},{"not":{"href_matches":"*.pdf"}},{"not":{"href_matches":"*.zip"}},{"not":{"selector_matches":".do-not-prerender"}},{"not":{"selector_matches":"[rel=nofollow]"}},{"not":{"selector_matches":"[target=_blank]"}},{"not":{"selector_matches":"[target=_parent]"}},{"not":{"selector_matches":"[target=_top]"}}]},"eagerness":"moderate"}]}</script>
vendor: 1 bytes, line 1840
1840
1841<script> 1842(() => { 1843 class BFCacheHandler { 1844 constructor() { 1845 this.options = { 1846 isCustomerLoggedIn: false, 1847 enableUserInteractionRefreshMiniCart: true, 1848 autoCloseMenuMobile: true }; 1849 1850 this.userInteractionEvents = ['touchstart', 'mouseover', 'wheel', 'scroll', 'keydown']; 1851 this._reloadTriggered = false; 1852 this.dom = {}; 1853 1854 // Centralized selectors for reuse 1855 this.selectors = { 1856 cartDrawer: "[x-data^='initCartDrawer']", 1857 mobileMenu: "[x-data^='initMenuMobile']", 1858 desktopMenu: "[x-data^='initMenuDesktop']" 1859 }; 1860 } 1861 1862 getElement(key) { 1863 const cached = this.dom[key]; 1864 if (cached && document.contains(cached)) return cached; 1865 const el = document.querySelector(this.selectors[key]); 1866 if (el) this.dom[key] = el; 1867 return el; 1868 } 1869 1870 init() { 1871 this.refreshMiniCart(); 1872 this.reloadCustomerLoginPage(); 1873 this.actionAutoCloseMenu(this.options.autoCloseMenuMobile); 1874 } 1875 1876 refreshMiniCart() { 1877 if (this.options.enableUserInteractionRefreshMiniCart) { 1878 this.refreshMiniCartOnUserInteraction(); 1879 } else { 1880 this.actionRefreshMiniCart(); 1881 } 1882 } 1883 1884 refreshMiniCartOnUserInteraction() { 1885 const refreshMiniCart = () => { 1886 this.userInteractionEvents.forEach(eventType => 1887 window.removeEventListener(eventType, refreshMiniCart) 1888 ); 1889 this.actionRefreshMiniCart(); 1890 }; 1891 const opts = { once: true, passive: true }; 1892 this.userInteractionEvents.forEach(eventType => 1893 window.addEventListener(eventType, refreshMiniCart, opts) 1894 ); 1895 } 1896 1897 reloadCustomerLoginPage() { 1898 const backendLoggedInState = this.options.isCustomerLoggedIn; 1899 1900 let customerData = null; 1901 try { 1902 const cacheStorage = localStorage.getItem('mage-cache-storage'); 1903 if (cacheStorage) { 1904 customerData = JSON.parse(cacheStorage).customer || null; 1905 } 1906 } catch { 1907 // Ignore parse errors silently 1908 } 1909 1910 const frontendLoggedInState = Boolean(customerData?.firstname); 1911 1912 if (frontendLoggedInState !== backendLoggedInState && !this._reloadTriggered) { 1913 this._reloadTriggered = true; 1914 window.location.reload(); 1915 } 1916 } 1917 1918 actionRefreshMiniCart() { 1919 window.dispatchEvent(new CustomEvent('reload-customer-section-data')); 1920 } 1921 1922 actionAutoCloseMenu(autoCloseMenuMobile) { 1923 window.dispatchEvent(new Event('clear-messages')); 1924 1925 const cartDrawer = this.getElement('cartDrawer'); 1926 if (cartDrawer && typeof Alpine !== 'undefined') { 1927 requestAnimationFrame(() => { 1928 const cartDrawerData = Alpine.$data(cartDrawer); 1929 if (cartDrawerData?.open !== undefined) { 1930 cartDrawerData.open = false; 1931 document.body.style.overflow = ''; 1932 } 1933 }); 1934 } 1935 1936 if (autoCloseMenuMobile) { 1937 this.handleMenuClosure(); 1938 } 1939 } 1940 1941 handleMenuClosure() { 1942 if (typeof Alpine === 'undefined' || !Alpine.$data) return; 1943 1944 const mobileMenu = this.getElement('mobileMenu'); 1945 if (mobileMenu) { 1946 const mobileMenuData = Alpine.$data(mobileMenu); 1947 if (mobileMenuData?.open !== undefined) { 1948 mobileMenuData.open = false; 1949 } 1950 } 1951 1952 const desktopMenu = this.getElement('desktopMenu'); 1953 if (desktopMenu) { 1954 const desktopMenuData = Alpine.$data(desktopMenu); 1955 if (desktopMenuData?.hoverPanelActiveId !== undefined) { 1956 desktopMenuData.hoverPanelActiveId = 0; 1957 } 1958 } 1959 } 1960 } 1961 1962 window.addEventListener('pageshow', (event) => { 1963 if (event.persisted) { 1964 (window._bfcacheHandler ??= new BFCacheHandler()).init(); 1965 } 1966 }, { passive: true }); 1967})(); 1968</script>
1968 1969
1970<script> 1971 (() => { 1972 function src_default(Alpine) { 1973 Alpine.directive("intersect", Alpine.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater, cleanup }) => { 1974 let evaluate = evaluateLater(expression); 1975 let options = { 1976 rootMargin: getRootMargin(modifiers), 1977 threshold: getThreshold(modifiers) 1978 }; 1979 let observer = new IntersectionObserver((entries) => { 1980 entries.forEach((entry) => { 1981 if (entry.isIntersecting === (value === "leave")) 1982 return; 1983 evaluate(); 1984 modifiers.includes("once") && observer.disconnect(); 1985 }); 1986 }, options); 1987 observer.observe(el); 1988 cleanup(() => { 1989 observer.disconnect(); 1990 }); 1991 })); 1992 } 1993 function getThreshold(modifiers) { 1994 if (modifiers.includes("full")) 1995 return 0.99; 1996 if (modifiers.includes("half")) 1997 return 0.5; 1998 if (!modifiers.includes("threshold")) 1999 return 0; 2000 let threshold = modifiers[modifiers.indexOf("threshold") + 1]; 2001 if (threshold === "100") 2002 return 1; 2003 if (threshold === "0") 2004 return 0; 2005 return Number(`.${threshold}`); 2006 } 2007 function getLengthValue(rawValue) { 2008 let match = rawValue.match(/^(-?[0-9]+)(px|%)?$/); 2009 return match ? match[1] + (match[2] || "px") : void 0; 2010 } 2011 function getRootMargin(modifiers) { 2012 const key = "margin"; 2013 const fallback = "0px 0px 0px 0px"; 2014 const index = modifiers.indexOf(key); 2015 if (index === -1) 2016 return fallback; 2017 let values = []; 2018 for (let i = 1; i < 5; i++) { 2019 values.push(getLengthValue(modifiers[index + i] || "")); 2020 } 2021 values = values.filter((v) => v !== void 0); 2022 return values.length ? values.join(" ").trim() : fallback; 2023 } 2024 2025 document.addEventListener("alpine:init", () => { 2026 window.Alpine.plugin(src_default); 2027 }); 2028 })(); 2029 //# sourceURL=alpine-intersect.js 2030</script>
2030 2031
2032<script> 2033 let SnapSlider = class { 2034 constructor(el, { 2035 labelSepparator = 'van', 2036 autoPager, 2037 groupPager 2038 } = {}) { 2039 this.el = el; 2040 this.track = this.el.querySelector("[data-track]"); 2041 if (!this.track) { 2042 console.warn( 2043 "No Slider track defined, reverting back to CSS slider.\nPlease create a wrapper for your slides with the attribute data-track", 2044 this.el 2045 ); 2046 return; 2047 } 2048 this.initialLoad = true; 2049 this.slides = []; 2050 this.inViewObserver; 2051 this.mutationObserver; 2052 this.resizeObserver; 2053 this.pager = this.el.querySelector("[data-pager]"); 2054 this.navBtns = Array.from( 2055 this.el.querySelectorAll("[data-next], [data-prev]") 2056 ); 2057 this.slideLabelSepparator = this.el.dataset.slideLabelSepparator || labelSepparator; 2058 this.useAutoPager = autoPager || this.el.hasAttribute("data-auto-pager") || false; 2059 this.useGroupPager = groupPager || this.el.hasAttribute("data-group-pager") || false; 2060 this.sliderLabel = this.el.hasAttribute("aria-label") && this.el.getAttribute("aria-label").toLowerCase().trim().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, ""); 2061 this.sliderId = this.el.id || this.sliderLabel || "slider"; 2062 this.markerIdName = "data-target-id"; 2063 this.pagerClasses = this.el.dataset.pagerClass || "snap-pager"; 2064 this.markerClasses = this.el.dataset.markerClass || "snap-marker"; 2065 this.init(); 2066 } 2067 init() { 2068 this.setupSlides(); 2069 this.setupNav(); 2070 if (this.useAutoPager) { 2071 this.createPager(); 2072 } else { 2073 this.setupPager(); 2074 } 2075 this.setupObservers(); 2076 this.setupMutationObserver(); 2077 this.setupResizeObserver(); 2078 this.el.addEventListener("click", this.eventHandler.bind(this)); 2079 this.el.addEventListener("keydown", this.eventHandler.bind(this)); 2080 } 2081 destroy() { 2082 this.inViewObserver?.disconnect(); 2083 this.mutationObserver?.disconnect(); 2084 this.resizeObserver?.disconnect(); 2085 this.el.removeEventListener("click", this.eventHandler.bind(this)); 2086 this.el.removeEventListener("keydown", this.eventHandler.bind(this)); 2087 } 2088 roundUpIfGreaterThan(number, min = 8) { 2089 const decimalPart = number - Math.floor(number); 2090 return decimalPart >= min ? Math.ceil(number) : Math.floor(number); 2091 } 2092 getInViewItems() { 2093 const inViewSlides = this.track.querySelectorAll("[data-in-view]"); 2094 const firstInViewSlide = inViewSlides[0] || null; 2095 const lastInViewSlide = inViewSlides[inViewSlides.length - 1] || null; 2096 const isAtStart = firstInViewSlide === this.slides[0]; 2097 const isAtEnd = lastInViewSlide === this.slides[this.slides.length - 1]; 2098 return { 2099 inViewSlides, 2100 totalInViewSlides: inViewSlides.length, 2101 firstInViewSlide, 2102 lastInViewSlide, 2103 isAtStart, 2104 isAtEnd, 2105 hasNoOverflow: isAtStart && isAtEnd 2106 }; 2107 } 2108 groupPagerMarkers() { 2109 if (this.slides.length === 0 || !this.pager || !this.useGroupPager) return; 2110 const totalVisibleSlides = this.roundUpIfGreaterThan( 2111 this.track.offsetWidth / this.slides[0].offsetWidth 2112 ); 2113 const markers = Array.from(this.pager.querySelectorAll("a, button")); 2114 markers.forEach((marker, index) => { 2115 marker.style.display = index % totalVisibleSlides === 0 ? null : "none"; 2116 }); 2117 } 2118 handleInView(entries) { 2119 entries.forEach((entry) => { 2120 const marker = this.pager && (this.pager.querySelector(`[href="#${entry.target.id}"]`) || this.pager.querySelector( 2121 `[${this.markerIdName}="${entry.target.id}"]` 2122 )); 2123 entry.target.toggleAttribute("data-in-view", entry.isIntersecting); 2124 entry.target.toggleAttribute("inert", !entry.isIntersecting); 2125 marker?.setAttribute("aria-current", entry.isIntersecting); 2126 marker?.setAttribute("tabindex", entry.isIntersecting ? "0" : "-1"); 2127 }); 2128 const { 2129 isAtStart, 2130 isAtEnd, 2131 hasNoOverflow 2132 } = this.getInViewItems(); 2133 this.navBtns.forEach((btn) => { 2134 if (btn.hasAttribute("data-next")) { 2135 btn.style.visibility = hasNoOverflow ? "hidden" : null; 2136 isAtEnd ? btn.setAttribute("disabled", "") : btn.removeAttribute("disabled"); 2137 } 2138 if (btn.hasAttribute("data-prev")) { 2139 btn.style.visibility = hasNoOverflow ? "hidden" : null; 2140 isAtStart ? btn.setAttribute("disabled", "") : btn.removeAttribute("disabled"); 2141 } 2142 }); 2143 if (this.pager) { 2144 this.pager.style.visibility = hasNoOverflow ? "hidden" : null; 2145 } 2146 if (!this.initialLoad) { 2147 this.el.dispatchEvent( 2148 new CustomEvent("slideChange", { 2149 detail: this.getInViewItems() 2150 }) 2151 ); 2152 } else { 2153 this.initialLoad = false;
2154 } 2155 if (document.activeElement.parentElement.hasAttribute("data-pager")) { 2156 const activeItems = this.pager.querySelectorAll('[tabindex="0"]'); 2157 if (activeItems.length) { 2158 activeItems[0].focus({ preventScroll: true }); 2159 } 2160 } 2161 } 2162 getSlides() { 2163 if (!this.track) return []; 2164 return Array.from(this.track.children).filter( 2165 (child) => child.tagName.toLowerCase() !== "template" && child.tagName.toLowerCase() !== "style" && child.tagName.toLowerCase() !== "script" 2166 ); 2167 } 2168 refreshSlides() { 2169 this.initialLoad = true; 2170 this.slides = this.getSlides(); 2171 this.setupSlides(); 2172 if (this.useAutoPager) { 2173 this.createPager(); 2174 } else { 2175 this.setupPager(); 2176 } 2177 this.groupPagerMarkers(); 2178 this.setupObservers(); 2179 } 2180 setupSlides() { 2181 this.slides = this.getSlides(); 2182 this.slides.forEach((slide, index) => { 2183 const totalSlides = this.slides.length; 2184 const currentSlide = index + 1; 2185 const existingLabel = slide.getAttribute("aria-label") || ""; 2186 const isImage = slide.tagName.toLowerCase() === "img" || slide.tagName.toLowerCase() === "picture"; 2187 const slideLabelTag = isImage ? "alt" : "aria-label"; 2188 const hasAutoLabel = existingLabel.startsWith( 2189 `${currentSlide} ${this.slideLabelSepparator} ` 2190 ); 2191 if (!slide.hasAttribute("id")) { 2192 slide.setAttribute( 2193 "id", 2194 `${this.sliderId}-item-${currentSlide}` 2195 ); 2196 } 2197 if (!slide.hasAttribute(slideLabelTag) || slide.getAttribute(slideLabelTag) === "" || hasAutoLabel) { 2198 slide.setAttribute( 2199 slideLabelTag, 2200 `${currentSlide} ${this.slideLabelSepparator} ${totalSlides}` 2201 ); 2202 } 2203 if (slide.tagName.toLowerCase() === "div" && !slide.hasAttribute("role")) { 2204 slide.setAttribute("role", "group"); 2205 } 2206 if (this.pager && !isImage) { 2207 slide.setAttribute("role", "tabpanel"); 2208 } 2209 if (slide.getAttribute("role") === "group") { 2210 slide.setAttribute("aria-roledescription", "item"); 2211 } 2212 }); 2213 if (!this.el.hasAttribute("role") && this.el.tagName !== "SECTION") { 2214 this.el.setAttribute("role", "region"); 2215 } 2216 if (!this.el.hasAttribute("aria-roledescription")) { 2217 this.el.setAttribute("aria-roledescription", "carousel"); 2218 } 2219 this.track.setAttribute("tabindex", 0); 2220 this.track.setAttribute("aria-live", "polite"); 2221 } 2222 setupNav() { 2223 this.navBtns.forEach((btn) => { 2224 btn.setAttribute("disabled", ""); 2225 btn.removeAttribute("hidden"); 2226 btn.classList.remove("invisible"); 2227 btn.style.visibility = null; 2228 }); 2229 } 2230 createPager() { 2231 const newPager = document.createElement("nav"); 2232 newPager.setAttribute("data-pager", ""); 2233 newPager.setAttribute("role", "tablist"); 2234 newPager.classList.add(...this.pagerClasses.split(" ")); 2235 this.slides.forEach((slide, index) => { 2236 const marker = document.createElement("button"); 2237 const slideId = slide.id || `${this.sliderId}-item-${index + 1}`; 2238 marker.setAttribute(this.markerIdName, slideId); 2239 marker.classList.add(...this.markerClasses.split(" ")); 2240 this.setupPagerMarker(marker, index); 2241 newPager.appendChild(marker); 2242 if (slide.tagName.toLowerCase() !== "img" && slide.tagName.toLowerCase() !== "picture") { 2243 slide.removeAttribute("aria-roledescription"); 2244 slide.setAttribute("role", "tabpanel"); 2245 } 2246 }); 2247 if (this.pager) { 2248 this.pager.setAttribute("role", "tablist"); 2249 this.pager.replaceChildren(...newPager.children); 2250 } else { 2251 this.track.after(newPager); 2252 this.pager = this.el.querySelector("[data-pager]"); 2253 } 2254 } 2255 setupPager() { 2256 if (!this.pager) return; 2257 this.pager.setAttribute("role", "tablist"); 2258 const items = Array.from(this.pager.querySelectorAll("a, button"));
2259 items.forEach((marker, index) => this.setupPagerMarker(marker, index)); 2260 } 2261 setupPagerMarker(marker, index) { 2262 const markerId = marker.getAttribute("href")?.slice(1) || marker.getAttribute(this.markerIdName); 2263 const slideId = markerId || `${this.sliderId}-item-${index + 1}`; 2264 if (!markerId) { 2265 marker.setAttribute(this.markerIdName, slideId); 2266 } 2267 marker.setAttribute("role", "tab"); 2268 marker.setAttribute("aria-controls", slideId); 2269 marker.setAttribute("aria-posinset", index + 1); 2270 marker.setAttribute("aria-setsize", this.slides.length); 2271 marker.setAttribute("tabindex", "-1"); 2272 if (!marker.hasAttribute("aria-label")) { 2273 marker.setAttribute("aria-label", `Slide ${index + 1}`); 2274 } 2275 } 2276 setupObservers() { 2277 if (this.inViewObserver) { 2278 this.inViewObserver.disconnect(); 2279 } 2280 this.inViewObserver = new IntersectionObserver( 2281 this.handleInView.bind(this), { 2282 root: this.track, 2283 threshold: 0.8 2284 } 2285 ); 2286 this.slides.forEach((slide) => this.inViewObserver.observe(slide)); 2287 } 2288 setupMutationObserver() { 2289 this.mutationObserver = new MutationObserver( 2290 this.refreshSlides.bind(this) 2291 ); 2292 this.mutationObserver.observe(this.track, { 2293 childList: true, 2294 subtree: false 2295 }); 2296 } 2297 setupResizeObserver() { 2298 this.resizeObserver = new ResizeObserver( 2299 this.groupPagerMarkers.bind(this) 2300 ); 2301 this.resizeObserver.observe(this.el); 2302 } 2303 goToSlideDir(dir = "next") { 2304 const { 2305 firstInViewSlide, 2306 lastInViewSlide 2307 } = this.getInViewItems(); 2308 const isPrev = dir === "prev"; 2309 let targetSlide = isPrev ? firstInViewSlide?.previousElementSibling : lastInViewSlide?.nextElementSibling; 2310 if (!targetSlide) return; 2311 if (targetSlide.tagName.toLowerCase() === "template") { 2312 targetSlide = isPrev ? targetSlide?.previousElementSibling : targetSlide?.nextElementSibling; 2313 } 2314 targetSlide.scrollIntoView({ block: "nearest", inline: isPrev ? "end" : "start" }); 2315 } 2316 pagerToSlide(event) { 2317 if (!event.target.closest("[data-pager]")) return; 2318 const marker = event.target.closest("a, button"); 2319 if (!marker) return; 2320 event.preventDefault(); 2321 const slideId = marker.getAttribute("href")?.slice(1) || marker.getAttribute(this.markerIdName); 2322 const targetSlide = this.track.querySelector(`#${slideId}`); 2323 targetSlide?.scrollIntoView({ block: "nearest", inline: "start" }); 2324 } 2325 eventHandler(event) { 2326 const target = event.target.closest( 2327 "[data-next], [data-prev], [data-pager]" 2328 ); 2329 if (!target) return; 2330 if (event.type === "click") { 2331 if (target.hasAttribute("data-next")) { 2332 this.goToSlideDir("next"); 2333 } else if (target.hasAttribute("data-prev")) { 2334 this.goToSlideDir("prev"); 2335 } else if (target.closest("[data-pager]")) { 2336 this.pagerToSlide(event); 2337 } 2338 } 2339 if (event.type === "keydown" && target.closest("[data-pager]")) { 2340 if (event.key === "ArrowRight") { 2341 this.goToSlideDir("next"); 2342 } else if (event.key === "ArrowLeft") { 2343 this.goToSlideDir("prev"); 2344 } 2345 } 2346 } 2347 }; 2348 2349 (() => { 2350 function alpineSnapSlider(Alpine) { 2351 Alpine.directive("snap-slider", (el, { 2352 modifiers 2353 }, { 2354 cleanup 2355 }) => { 2356 const autoPager = modifiers.includes("auto-pager");
2357 const groupPager = modifiers.includes("group-pager"); 2358 const slider = new SnapSlider(el, { 2359 autoPager, 2360 groupPager 2361 }); 2362 cleanup(() => { 2363 slider.destroy(); 2364 }); 2365 }); 2366 } 2367 2368 document.addEventListener("alpine:init", () => { 2369 window.Alpine.plugin(alpineSnapSlider); 2370 }); 2371 })(); 2372</script>
2372 2373
2374<script type="module" 2375 src="https://uitgeverijpluim.nl/static/version1790668939/frontend/Pluim/Hyva/nl_NL/Hyva_Theme/js/alpine3.min.js" 2376 defer 2377 crossorigin 2378></script>
vendor: 1 bytes, line 2378
2378
2379<script> 2380 function dispatchMessages(messages, hideAfter) { 2381 const messagesEvent = new CustomEvent("messages-loaded", { 2382 detail: { 2383 messages: messages, 2384 hideAfter: hideAfter 2385 } 2386 }); 2387 window.dispatchEvent(messagesEvent); 2388 } 2389 2390 if (typeof hyva === 'undefined' || (!hyva.getBrowserStorage || !hyva.getCookie || !hyva.setCookie)) { 2391 console.warn("Hyvä helpers are not loaded yet. Make sure they are included before this script"); 2392 } 2393 2394 (function( hyva, undefined ) { 2395 2396 hyva.initFormKey = () => { 2397 if (typeof hyva.getFormKey !== 'function') return; 2398 2399 const formKey = hyva.getFormKey(); 2400 if (!formKey) return; 2401 2402 document.querySelectorAll('input[name="form_key"]').forEach((input) => { 2403 input.value = formKey; 2404 }); 2405 } 2406 2407 hyva.initMessages = () => { 2408 try { 2409 const messages = hyva.getCookie('mage-messages'); 2410 window.mageMessages = messages ? JSON.parse(decodeURIComponent(messages).replace(/\+/g, ' ')) : []; 2411 2412 dispatchMessages(window.mageMessages); 2413 2414 // empty `mage-messages` cookie 2415 const skipSetDomain = true; 2416 hyva.setCookie('mage-messages','', -1, skipSetDomain); 2417 2418 } catch (error) { 2419 console.warn('Error parsing Cookie Messages:', error); 2420 } 2421 } 2422 2423 window.addEventListener('DOMContentLoaded', hyva.initFormKey); 2424 2425 window.addEventListener('pageshow', (event) => { 2426 if (event.persisted) { 2427 hyva.initFormKey(); 2428 } 2429 }); 2430 2431 hyva.alpineInitialized(hyva.initMessages) 2432 }( window.hyva = window.hyva || {} )); 2433 //# sourceURL=hyva-cookies.js 2434</script>
vendor: 1 bytes, line 2434
2434
2435<script> 2436 (() => { 2437 const private_content_key = 'mage-cache-storage'; 2438 const private_content_expire_key = 'mage-cache-timeout'; 2439 const private_content_version_key = 'private_content_version'; 2440 const section_data_ids_key = 'section_data_ids'; 2441 const mage_cache_session_id_key = 'mage-cache-sessid'; 2442 const last_visited_store_key = 'last_visited_store'; 2443 2444 const ttl = 3600; 2445 2446 if (typeof hyva === 'undefined' || (!hyva.getBrowserStorage || !hyva.getCookie || !hyva.setCookie)) { 2447 console.warn("Hyvä helpers are not loaded yet. Make sure they are included before this script"); 2448 } 2449 2450 function loadSectionData () { 2451 const browserStorage = hyva.getBrowserStorage(); 2452 if (!browserStorage) { 2453 typeof window.dispatchMessages !== "undefined" && window.dispatchMessages( 2454 [{ 2455 type: "warning", 2456 text: "Schakel LocalStorage in in je browser." 2457 }] 2458 ); 2459 return; 2460 } 2461 try { 2462 let isInvalid = false; 2463 2464 if (hyva.getCookie(last_visited_store_key) !== CURRENT_STORE_CODE) { 2465 isInvalid = true; 2466 } 2467 hyva.setCookie(last_visited_store_key, CURRENT_STORE_CODE, false, false); 2468 2469 if (!hyva.getCookie(mage_cache_session_id_key)) { 2470 isInvalid = true; 2471 browserStorage.removeItem(private_content_key); 2472 const skipSetDomain = true; 2473 const days = false; 2474 hyva.setCookie(mage_cache_session_id_key, true, days, skipSetDomain) 2475 } 2476 2477 const cookieVersion = hyva.getCookie(private_content_version_key); 2478 const storageVersion = browserStorage.getItem(private_content_version_key); 2479 2480 if (cookieVersion && !storageVersion || cookieVersion !== storageVersion) { 2481 isInvalid = true; 2482 } 2483 2484 const privateContentExpires = browserStorage.getItem(private_content_expire_key); 2485 if (privateContentExpires && new Date(privateContentExpires) < new Date()) { 2486 browserStorage.removeItem(private_content_key); 2487 } 2488 2489 if (isInvalid && cookieVersion) { 2490 fetchPrivateContent([]); 2491 } else if (cookieVersion && storageVersion && cookieVersion === storageVersion) { 2492 const privateContent = JSON.parse(browserStorage.getItem(private_content_key)); 2493 if ( 2494 privateContent && 2495 privateContentExpires && 2496 privateContent.cart && 2497 privateContent.customer 2498 ) { 2499 dispatchPrivateContent(privateContent); 2500 } else { 2501 fetchPrivateContent([]); 2502 } 2503 } else { 2504 if (document.getElementById('default-section-data')) { 2505 const privateContent = JSON.parse(document.getElementById('default-section-data').textContent.trim()); 2506 dispatchPrivateContent(privateContent); 2507 } else { 2508 dispatchPrivateContent({}); 2509 } 2510 } 2511 2512 } catch (error) { 2513 console.warn('Error retrieving Private Content:', error); 2514 } 2515 } 2516 2517 hyva.alpineInitialized(loadSectionData) 2518 window.addEventListener('reload-customer-section-data', loadSectionData); 2519 2520 window.processSectionDataBeforeDispatch = window.processSectionDataBeforeDispatch || (data => data); 2521 2522 function dispatchPrivateContent(data) { 2523 2524 const privateContentEvent = new CustomEvent("private-content-loaded", { 2525 detail: { 2526 data: window.processSectionDataBeforeDispatch(data) 2527 } 2528 }); 2529 window.dispatchEvent(privateContentEvent); 2530 } 2531 2532 function fetchPrivateContent(sections) { 2533 fetch(`${BASE_URL}customer/section/load/?sections=${encodeURIComponent(sections.join(','))}`, { 2534 method: 'GET', 2535 headers: { 2536 'Content-Type': 'application/json', 2537 'X-Requested-With': 'XMLHttpRequest' 2538 } 2539 }) 2540 .then(response => response.ok && response.json()) 2541 .then( 2542 data => { 2543 if (data) { 2544 try { 2545 const browserStorage = hyva.getBrowserStorage(); 2546 2547 // merge new data preserving non-invalidated sections 2548 const oldSectionData = JSON.parse(browserStorage.getItem(private_content_key) || '{}') || {}; 2549 2550 if ((! data.cart || ! data.cart.cartId) && oldSectionData['checkout-data']) { 2551 delete oldSectionData['checkout-data']; 2552 } 2553 const newSectionData = Object.assign(oldSectionData, data); 2554 2555 dispatchPrivateContent(newSectionData); 2556 2557 // don't persist messages, they've been dispatched already 2558 if (newSectionData.messages && newSectionData.messages.messages ) { 2559 newSectionData.messages.messages = []; 2560 } 2561 2562 browserStorage.setItem(private_content_key, JSON.stringify(newSectionData)); 2563 2564 const expiresAt = new Date(Date.now() + (ttl * 1000)).toISOString(); 2565 browserStorage.setItem(private_content_expire_key, expiresAt); 2566 2567 const newCookieVersion = hyva.getCookie(private_content_version_key); 2568 browserStorage.setItem(private_content_version_key, newCookieVersion); 2569 2570 // We don't need the section_data_ids in Hyvä, but we store them for compatibility 2571 // with Luma Fallback. Otherwise, not all sections are loaded in Luma Checkout 2572 hyva.setCookie( 2573 section_data_ids_key, 2574 JSON.stringify( 2575 Object.keys(data).reduce((sectionDataIds, sectionKey) => { 2576 sectionDataIds[sectionKey] = data[sectionKey]['data_id']; 2577 return sectionDataIds; 2578 }, {}) 2579 ), 2580 false, 2581 true 2582 ); 2583 } catch (error) { 2584 console.warn("Couldn't store privateContent", error); 2585 } 2586 } 2587 } 2588 ) 2589 .catch(error => { 2590 console.warn("Couldn't fetch privateContent", error); 2591 }); 2592 } 2593 })(); 2594 //# sourceURL=hyva-private-content.js 2595</script>
vendor: 1 bytes, line 2595
2595
2596<script> 2597 (() => { 2598 document.addEventListener('submit', event => event.target.action = event.target.action.replace('%25uenc%25', hyva.getUenc())); 2599 })() 2600</script>
vendor: 1 bytes, line 2600
2600
2601<script> 2602 (events => { 2603 const dispatchUserInteractionEvent = () => { 2604 events.forEach(type => window.removeEventListener(type, dispatchUserInteractionEvent)) 2605 window.dispatchEvent(new Event('init-external-scripts')) 2606 }; 2607 events.forEach(type => window.addEventListener(type, dispatchUserInteractionEvent, {once: true, passive: true})) 2608 })(['touchstart', 'mousemove', 'wheel', 'scroll', 'keydown']); 2609 //# sourceURL=hyva-init-external-scripts.js 2610</script>
vendor: 1 bytes, line 2610
2610
2611<script> 2612 document.addEventListener("alpine:init", () => { 2613 Alpine.data('hyvaCmsToggle', () => ({ 2614 open: false, 2615 notOpen() { 2616 return !this.open; 2617 }, 2618 show() { 2619 this.open = true 2620 }, 2621 hide() { 2622 this.open = false 2623 }, 2624 toggle() { 2625 this.open = !this.open 2626 } 2627 })) 2628 }); 2629</script>
vendor: 1 bytes, line 2629
2629
2630<script type="text/json" id="default-section-data"> 2631 {"messages":[],"customer":[],"compare-products":[],"last-ordered-items":[],"cart":[],"directory-data":{"BE":{"name":"Belgi\u00eb","regions":{"609":{"code":"VAN","name":"Antwerpen"},"610":{"code":"WBR","name":"Brabant wallon"},"611":{"code":"BRU","name":"Brussels-Capital Region"},"612":{"code":"WHT","name":"Hainaut"},"614":{"code":"WLG","name":"Li\u00e8ge"},"613":{"code":"VLI","name":"Limburg"},"615":{"code":"WLX","name":"Luxembourg"},"616":{"code":"WNA","name":"Namur"},"617":{"code":"VOV","name":"Oost-Vlaanderen"},"618":{"code":"VBR","name":"Vlaams-Brabant"},"619":{"code":"VWV","name":"West-Vlaanderen"}}},"NL":{"name":"Nederland"},"data_id":1790918008},"instant-purchase":[],"loggedAsCustomer":[],"captcha":[],"persistent":[],"review":[],"wishlist":{"items":[]},"hyva_commerce_messages":[],"hyva_checkout":[],"gtm-checkout":[],"recently_viewed_product":[],"recently_compared_product":[],"product_data_storage":[],"paypal-billing-agreement":[],"paypal-buyer-country":[]}</script>
vendor: 1 bytes, line 2631
2631
2632<script> 2633 window.processSectionDataBeforeDispatch && (window.processSectionDataBeforeDispatch = new Proxy(window.processSectionDataBeforeDispatch, { 2634 apply(target, thisArg, [data]) { 2635 data.hyva_commerce_messages = data.hyva_commerce_messages || {}; 2636 return target.call(thisArg, data); 2637 } 2638 })); 2639</script>
vendor: 1 bytes, line 2639
2639
2640<script>if (!window.__rumvisionSkip) { 2641 window.rumv = window.rumv || function() { (window.rumv.q = window.rumv.q || []).push(arguments) }; 2642 (function(rum, vi,si,on) { 2643 var s = JSON.parse( sessionStorage.getItem('rumv') || '{"pageviews":0}' ); s.pageviews++; 2644 if ( s.urls && s.regex && ( s.page = eval('('+s.regex+')')( s.urls, vi.location.pathname ) ) && !s.page.type ) { 2645 return sessionStorage.setItem('rumv', JSON.stringify( s ) ); 2646 } 2647 2648 vi.rumv.storage = s; 2649 var head = si.querySelector('head'), js = si.createElement('script'); 2650 js.src = 'https://d5yoctgpv4cpx.cloudfront.net/'+rum+'/v5-'+vi.location.hostname+'.js'; 2651 head.appendChild(js); 2652 })( 'RUM\u002D409605D6E0', window, document, 'uitgeverijpluim.nl' ); 2653}</script>
2653</div>
2653<script nonce="MzJpbnJpdDZjbzFsOHF3bG5pazUydXh3b3RsYnU3bHk="> 2654 yireoGoogleTagManager2Pusher({"version":"3.10.7","page_type":"prismicio\/direct\/singleton","page_path":"https:\/\/uitgeverijpluim.nl\/","page_title":"Homepage | Uitgeverij Pluim","virtual_page":"","breadcrumb":[],"user":{"has_transacted":false}}, 'push (initial page) [data-layer.phtml]'); 2655 </script>
vendor: 1 bytes, line 2655
2655
2656<script nonce="MzJpbnJpdDZjbzFsOHF3bG5pazUydXh3b3RsYnU3bHk="> 2657 function yireoGoogleTagManager2FindParentElementWithName(element, parentTagName) { 2658 if (!element.parentElement) { 2659 return null; 2660 } 2661 2662 if (element.parentElement.tagName.toLowerCase() === parentTagName) { 2663 return element.parentElement; 2664 } 2665 2666 return yireoGoogleTagManager2FindParentElementWithName(element.parentElement, parentTagName); 2667 } 2668 2669 const products = document.querySelectorAll('.products\u0020a.product,\u0020.products\u0020a.product\u002Ditem\u002Dlink,\u0020.card\u0020a.product\u002Ditem\u002Dlink,\u0020.card\u0020a.product\u002Ditem\u002Dphoto,\u0020.card\u0020a.btn\u002Dprimary'); 2670 if (products && products.length > 0) { 2671 products.forEach(function(product) { 2672 product.addEventListener('click', function(event, s) { 2673 let parentElement = yireoGoogleTagManager2FindParentElementWithName(event.target, 'form'); 2674 if (!parentElement) { 2675 return; 2676 } 2677 2678 const inputElement = parentElement.querySelector('input[name="product"]'); 2679 const productId = inputElement.value; 2680 const productData = window['YIREO_GOOGLETAGMANAGER2_PRODUCT_DATA_ID_' + productId] || {}; 2681 productData.item_id = productId; 2682 2683 const eventData = { 2684 'event': 'select_item', 2685 'ecommerce': { 2686 'items': [productData] 2687 } 2688 } 2689 2690 yireoGoogleTagManager2Pusher(eventData, 'push (page event "select_item") [script-product-clicks.phtml]'); 2691 2692 if (window['YIREO_GOOGLETAGMANAGER2_DEBUG_CLICKS'] && confirm("Press to continue with redirect") === false) { 2693 event.preventDefault(); 2694 } 2695 }); 2696 }) 2697 } 2698</script>
vendor: 1 bytes, line 2698
2698
2699<script nonce="MzJpbnJpdDZjbzFsOHF3bG5pazUydXh3b3RsYnU3bHk="> 2700 window.addEventListener('private-content-loaded', function(event) { 2701 window.dataLayer = window.dataLayer || []; 2702 2703 var isEmpty = function (variable) { 2704 if (typeof variable === 'undefined') { 2705 return true; 2706 } 2707 2708 if (Array.isArray(variable) && variable.length === 0) { 2709 return true; 2710 } 2711 2712 return typeof variable === 'object' && Object.keys(variable).length === 0; 2713 } 2714 2715 var getSectionNames = function () { 2716 return ['cart', 'customer']; 2717 } 2718 2719 getSectionNames().forEach(function (sectionName) { 2720 if (!event.detail.data[sectionName].gtm) { 2721 return; 2722 } 2723 2724 const attributes = event.detail.data[sectionName].gtm; 2725 if (!Object.keys(attributes).length > 0) { 2726 return; 2727 } 2728 2729 yireoGoogleTagManager2Pusher(attributes, 'push (attributes) [script-additions.phtml]'); 2730 }); 2731 2732 let attributes = {}; 2733 getSectionNames().forEach(function (sectionName) { 2734 if (!event.detail.data[sectionName].gtm_events) { 2735 return; 2736 } 2737 2738 const gtmEvents = event.detail.data[sectionName].gtm_events; 2739 for (const [eventId, eventData] of Object.entries(gtmEvents)) { 2740 yireoGoogleTagManager2Logger('found event in section "' + sectionName + '"', eventData); 2741 2742 const metaData = eventData.meta; 2743 yireoGoogleTagManager2Pusher(eventData, 'push (customerData "' + sectionName + '" event "' + eventId + '")'); 2744 2745 if (!metaData || metaData.cacheable !== true) { 2746 yireoGoogleTagManager2Logger('removing event "' + eventId + '" from section "' + sectionName + '"') 2747 2748 let mageCacheStorage = event.detail.data; 2749 2750 if (mageCacheStorage !== null 2751 && typeof mageCacheStorage[sectionName] !== 'undefined' 2752 && typeof mageCacheStorage[sectionName].gtm_events !== 'undefined' 2753 && eventId in mageCacheStorage[sectionName].gtm_events) { 2754 delete mageCacheStorage[sectionName].gtm_events[eventId]; 2755 } 2756 2757 window.localStorage.setItem('mage-cache-storage', JSON.stringify(mageCacheStorage)); 2758 } 2759 } 2760 }); 2761 2762 if (false === isEmpty(attributes)) { 2763 yireoGoogleTagManager2Pusher(attributes, 'dataLayer (initial js) [script-additions.phtml]'); 2764 } 2765 }); 2766 2767 window.addEventListener('toggle-cart', (event) => { 2768 if (!event.detail || !event.detail.isOpen) { 2769 return; 2770 } 2771 2772 window.dispatchEvent(new CustomEvent('minicart_collapse')); 2773 }); 2774</script>
2774 2775</body> 2776</html>
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.