1<!doctype html> 2<html lang="nl"> 3<head > 4 <meta charset="utf-8"/> 5<meta name="title" content="Boeken | 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<meta name="page-type" content="product-listing"/> 11<title>Boeken | Uitgeverij Pluim</title> 12 <link rel="stylesheet" type="text/css" defer="defer" href="https://uitgeverijpluim.nl/static/version1790248052/frontend/Pluim/Hyva/nl_NL/css/styles.css" />
13<script type="text/javascript" src="https://uitgeverijpluim.nl/static/version1790248052/frontend/Pluim/Hyva/nl_NL/Smile_ElasticsuiteTracker/js/tracking.js"></script>
13 14<link rel="canonical" href="https://uitgeverijpluim.nl/boeken" /> 15<link rel="icon" type="image/x-icon" href="https://uitgeverijpluim.nl/media/favicon/websites/1/logo_sxfncc_1.jpg" /> 16<link rel="shortcut icon" type="image/x-icon" href="https://uitgeverijpluim.nl/media/favicon/websites/1/logo_sxfncc_1.jpg" /> 17<link rel="dns-prefetch" href="https://acdn.adnxs.com"> 18<link rel="dns-prefetch" href="https://ib.adnxs.com"> 19 20<!-- Xandr Universal Pixel - Optimized -->
21<script async defer> 22(function() { 23 // Initialize pixie stub immediately 24 !function(e,i){ 25 if(!e.pixie){ 26 var n=e.pixie=function(e,i,a){ 27 n.actionQueue.push({action:e,actionValue:i,params:a}) 28 }; 29 n.actionQueue=[]; 30 var a=i.createElement("script"); 31 a.async=!0; 32 a.src="https://acdn.adnxs.com/dmp/up/pixie.js"; 33 var t=i.getElementsByTagName("head")[0]; 34 t.insertBefore(a,t.firstChild) 35 } 36 }(window,document); 37 38 // Queue consent and initialization 39 pixie('consent', 'default', { 'ad_storage': 'granted', 'wait_for_update': 1000}); 40 pixie('init', 'cf677036-49f6-4ee6-8255-72e7d7a8944c'); 41 pixie('event', 'PageView'); 42})(); 43</script>
43
44<script nonce="ZTc3MHNqenIwcWZ2NnRtNHpuY2VjbnV2MDhwbWRtMDI="> 45 window.YIREO_GOOGLETAGMANAGER2_ENABLED = true; 46 (function (events) { 47 const initYireoGoogleTagManager2 = function () { 48 events.forEach(function (eventType) { 49 window.removeEventListener(eventType, initYireoGoogleTagManager2); 50 }); 51 52 (function (w, d, s, l, i) { 53 w[l] = w[l] || []; 54 w[l].push({'gtm.start': new Date().getTime(), event: 'gtm.js'}); 55 var f = d.getElementsByTagName(s)[0], 56 j = d.createElement(s), 57 dl = l != 'dataLayer' ? '&l=' + l : ''; 58 j.async = true; 59 j.src = 'https\u003A\u002F\u002Fwww.googletagmanager.com' + '/gtm.js?id=' + i + dl; 60 f.parentNode.insertBefore(j, f); 61 })(window, document, 'script', 'dataLayer', 'GTM\u002DWDGVFB9'); 62 }; 63 64 events.forEach(function (eventType) { 65 window.addEventListener(eventType, initYireoGoogleTagManager2, {once: true, passive: true}) 66 }); 67 })(['load', 'keydown', 'mouseover', 'scroll', 'touchstart', 'wheel']); 68 69 </script>
vendor: 1 bytes, line 69
69
70<script> 71 (function( hyva, undefined ) { 72 73 function lifetimeToExpires(options, defaults) { 74 75 const lifetime = options.lifetime || defaults.lifetime; 76 77 if (lifetime) { 78 const date = new Date; 79 date.setTime(date.getTime() + lifetime * 1000); 80 return date; 81 } 82 83 return null; 84 } 85 86 function generateRandomString() { 87 88 const allowedCharacters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 89 length = 16; 90 91 let formKey = '', 92 charactersLength = allowedCharacters.length; 93 94 for (let i = 0; i < length; i++) { 95 formKey += allowedCharacters[Math.round(Math.random() * (charactersLength - 1))] 96 } 97 98 return formKey; 99 } 100 101 const sessionCookieMarker = {noLifetime: true} 102 103 const cookieTempStorage = {}; 104 105 const internalCookie = { 106 get(name) { 107 const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); 108 return v ? v[2] : null; 109 }, 110 set(name, value, days, skipSetDomain) { 111 let expires, 112 path, 113 domain, 114 secure, 115 samesite; 116 117 const defaultCookieConfig = { 118 expires: null, 119 path: '/', 120 domain: null, 121 secure: false, 122 lifetime: null, 123 samesite: 'lax' 124 }; 125 126 const cookieConfig = window.COOKIE_CONFIG || {}; 127 128 expires = days && days !== sessionCookieMarker 129 ? lifetimeToExpires({lifetime: 24 * 60 * 60 * days, expires: null}, defaultCookieConfig) 130 : lifetimeToExpires(window.COOKIE_CONFIG, defaultCookieConfig) || defaultCookieConfig.expires; 131 132 path = cookieConfig.path || defaultCookieConfig.path; 133 domain = !skipSetDomain && (cookieConfig.domain || defaultCookieConfig.domain); 134 secure = cookieConfig.secure || defaultCookieConfig.secure; 135 samesite = cookieConfig.samesite || defaultCookieConfig.samesite; 136 137 document.cookie = name + "=" + encodeURIComponent(value) + 138 (expires && days !== sessionCookieMarker ? '; expires=' + expires.toGMTString() : '') + 139 (path ? '; path=' + path : '') + 140 (domain ? '; domain=' + domain : '') + 141 (secure ? '; secure' : '') + 142 (samesite ? '; samesite=' + samesite : 'lax'); 143 }, 144 isWebsiteAllowedToSaveCookie() { 145 const allowedCookies = this.get('user_allowed_save_cookie'); 146 if (allowedCookies) { 147 const allowedWebsites = JSON.parse(unescape(allowedCookies)); 148 149 return allowedWebsites[CURRENT_WEBSITE_ID] === 1; 150 } 151 return false; 152 }, 153 getGroupByCookieName(name) { 154 const cookieConsentConfig = window.cookie_consent_config || {}; 155 let group = null; 156 for (let prop in cookieConsentConfig) { 157 if (!cookieConsentConfig.hasOwnProperty(prop)) continue; 158 if (cookieConsentConfig[prop].includes(name)) { 159 group = prop; 160 break; 161 } 162 } 163 return group; 164 }, 165 isCookieAllowed(name) { 166 const cookieGroup = this.getGroupByCookieName(name); 167 return cookieGroup 168 ? window.cookie_consent_groups[cookieGroup] 169 : this.isWebsiteAllowedToSaveCookie(); 170 }, 171 saveTempStorageCookies() { 172 for (const [name, data] of Object.entries(cookieTempStorage)) { 173 if (this.isCookieAllowed(name)) { 174 this.set(name, data['value'], data['days'], data['skipSetDomain']); 175 delete cookieTempStorage[name]; 176 } 177 } 178 } 179 }; 180 181 hyva.getCookie = (name) => { 182 const cookieConfig = window.COOKIE_CONFIG || {}; 183 184 if (cookieConfig.cookie_restriction_enabled && ! internalCookie.isCookieAllowed(name)) { 185 return cookieTempStorage[name] ? cookieTempStorage[name]['value'] : null; 186 } 187 188 return internalCookie.get(name); 189 } 190 191 hyva.setCookie = (name, value, days, skipSetDomain) => { 192 const cookieConfig = window.COOKIE_CONFIG || {}; 193 194 if (cookieConfig.cookie_restriction_enabled && ! internalCookie.isCookieAllowed(name)) { 195 cookieTempStorage[name] = {value, days, skipSetDomain}; 196 return; 197 } 198 return internalCookie.set(name, value, days, skipSetDomain); 199 } 200 201 202 hyva.setSessionCookie = (name, value, skipSetDomain) => { 203 return hyva.setCookie(name, value, sessionCookieMarker, skipSetDomain) 204 } 205 206 hyva.getBrowserStorage = () => { 207 const browserStorage = window.localStorage || window.sessionStorage; 208 if (!browserStorage) { 209 console.warn('Browser Storage is unavailable'); 210 return false;
211 } 212 try { 213 browserStorage.setItem('storage_test', '1'); 214 browserStorage.removeItem('storage_test'); 215 } catch (error) { 216 console.warn('Browser Storage is not accessible', error); 217 return false; 218 } 219 return browserStorage; 220 } 221 222 hyva.postForm = (postParams) => { 223 const form = document.createElement("form"); 224 225 let data = postParams.data; 226 227 if (! postParams.skipUenc && ! data.uenc) { 228 data.uenc = btoa(window.location.href); 229 } 230 form.method = "POST"; 231 form.action = postParams.action; 232 233 Object.keys(postParams.data).map(key => { 234 const field = document.createElement("input"); 235 field.type = 'hidden' 236 field.value = postParams.data[key]; 237 field.name = key; 238 form.appendChild(field); 239 }); 240 241 const form_key = document.createElement("input"); 242 form_key.type = 'hidden'; 243 form_key.value = hyva.getFormKey(); 244 form_key.name="form_key"; 245 form.appendChild(form_key); 246 247 document.body.appendChild(form); 248 249 form.submit(); 250 } 251 252 hyva.getFormKey = function () { 253 let formKey = hyva.getCookie('form_key'); 254 255 if (!formKey) { 256 formKey = generateRandomString(); 257 hyva.setCookie('form_key', formKey); 258 } 259 260 return formKey; 261 } 262 263 hyva.formatPrice = (value, showSign, options = {}) => { 264 const groupSeparator = options.groupSeparator; 265 const decimalSeparator = options.decimalSeparator 266 delete options.groupSeparator; 267 delete options.decimalSeparator; 268 const formatter = new Intl.NumberFormat( 269 'nl\u002DNL', 270 Object.assign({ 271 style: 'currency', 272 currency: 'EUR', 273 signDisplay: showSign ? 'always' : 'auto' 274 }, options) 275 ); 276 return (typeof Intl.NumberFormat.prototype.formatToParts === 'function') ? 277 formatter.formatToParts(value).map(({type, value}) => { 278 switch (type) { 279 case 'currency': 280 return '\u20AC' || value; 281 case 'minusSign': 282 return '- '; 283 case 'plusSign': 284 return '+ '; 285 case 'group': 286 return groupSeparator !== undefined ? groupSeparator : value; 287 case 'decimal': 288 return decimalSeparator !== undefined ? decimalSeparator : value; 289 default : 290 return value; 291 } 292 }).reduce((string, part) => string + part) : 293 formatter.format(value); 294 } 295 296 const formatStr = function (str, nStart) { 297 const args = Array.from(arguments).slice(2); 298 299 return str.replace(/(%+)([0-9]+)/g, (m, p, n) => { 300 const idx = parseInt(n) - nStart; 301 302 if (args[idx] === null || args[idx] === void 0) { 303 return m; 304 } 305 return p.length % 2 306 ? p.slice(0, -1).replace('%%', '%') + args[idx] 307 : p.replace('%%', '%') + n; 308 }) 309 } 310 311 hyva.str = function (string) { 312 const args = Array.from(arguments); 313 args.splice(1, 0, 1); 314 315 return formatStr.apply(undefined, args); 316 } 317 318 hyva.strf = function () { 319 const args = Array.from(arguments); 320 args.splice(1, 0, 0); 321 322 return formatStr.apply(undefined, args); 323 } 324 325 /** 326 * Take a html string as `content` parameter and 327 * extract an element from the DOM to replace in 328 * the current page under the same selector, 329 * defined by `targetSelector` 330 */ 331 hyva.replaceDomElement = (targetSelector, content) => { 332 // Parse the content and extract the DOM node using the `targetSelector` 333 const parser = new DOMParser(); 334 const doc = parser.parseFromString(content, 'text/html');
335 const contentNode = doc.querySelector(targetSelector); 336 337 // Bail if content or target can't be found 338 if (!contentNode || !document.querySelector(targetSelector)) { 339 return; 340 } 341 342 hyva.activateScripts(contentNode); 343 344 // Replace the old DOM node with the new content 345 document.querySelector(targetSelector).replaceWith(contentNode); 346 347 // Reload customerSectionData and display cookie-messages if present 348 window.dispatchEvent(new CustomEvent("reload-customer-section-data")); 349 hyva.initMessages(); 350 } 351 352 hyva.removeScripts = (contentNode) => { 353 const scripts = contentNode.getElementsByTagName('script'); 354 for (let i = 0; i < scripts.length; i++) { 355 scripts[i].parentNode.removeChild(scripts[i]); 356 } 357 const templates = contentNode.getElementsByTagName('template'); 358 for (let i = 0; i < templates.length; i++) { 359 const container = document.createElement('div'); 360 container.innerHTML = templates[i].innerHTML; 361 hyva.removeScripts(container); 362 templates[i].innerHTML = container.innerHTML; 363 } 364 } 365 366 hyva.activateScripts = (contentNode) => { 367 // Create new array from HTMLCollection to avoid mutation of collection while manipulating the DOM. 368 const scripts = Array.from(contentNode.getElementsByTagName('script')); 369 370 // Iterate over all script tags to duplicate+inject each into the head 371 for (const original of scripts) { 372 const script = document.createElement('script'); 373 original.type && (script.type = original.type); 374 script.innerHTML = original.innerHTML; 375 376 // Remove the original (non-executing) script from the contentNode 377 original.parentNode.removeChild(original) 378 379 // Add script to head 380 document.head.appendChild(script); 381 } 382 383 return contentNode; 384 } 385 386 const replace = {['+']: '-', ['/']: '_', ['=']: ','}; 387 hyva.getUenc = () => btoa(window.location.href).replace(/[+/=]/g, match => replace[match]); 388 389 let currentTrap; 390 391 const focusableElements = (rootElement) => { 392 const selector = 'button, [href], input, select, textarea, details, [tabindex]:not([tabindex="-1"]'; 393 return Array.from(rootElement.querySelectorAll(selector)) 394 .filter(el => { 395 return el.style.display !== 'none' 396 && !el.disabled 397 && el.tabIndex !== -1 398 && (el.offsetWidth || el.offsetHeight || el.getClientRects().length) 399 }) 400 } 401 402 const focusTrap = (e) => { 403 const isTabPressed = e.key === 'Tab' || e.keyCode === 9; 404 if (!isTabPressed) return; 405 406 const focusable = focusableElements(currentTrap) 407 const firstFocusableElement = focusable[0] 408 const lastFocusableElement = focusable[focusable.length - 1] 409 410 e.shiftKey 411 ? document.activeElement === firstFocusableElement && (lastFocusableElement.focus(), e.preventDefault()) 412 : document.activeElement === lastFocusableElement && (firstFocusableElement.focus(), e.preventDefault()) 413 }; 414 415 hyva.releaseFocus = (rootElement) => { 416 if (currentTrap && (!rootElement || rootElement === currentTrap)) { 417 currentTrap.removeEventListener('keydown', focusTrap) 418 currentTrap = null 419 } 420 } 421 hyva.trapFocus = (rootElement) => { 422 if (!rootElement) return; 423 hyva.releaseFocus() 424 currentTrap = rootElement 425 rootElement.addEventListener('keydown', focusTrap) 426 const firstElement = focusableElements(rootElement)[0] 427 firstElement && firstElement.focus() 428 } 429 430 hyva.safeParseNumber = (rawValue) => { 431 const number = rawValue ? parseFloat(rawValue) : null; 432 433 return Array.isArray(number) || isNaN(number) ? rawValue : number; 434 } 435 436 const toCamelCase = s => s.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(''); 437 hyva.createBooleanObject = (name, value = false, additionalMethods = {}) => { 438 439 const camelCase = toCamelCase(name);
440 const key = '__hyva_bool_' + name 441 442 return new Proxy(Object.assign( 443 additionalMethods, 444 { 445 [key]: !!value, 446 [name]() {return !!this[key]}, 447 ['!' + name]() {return !this[key]}, // @deprecated This does not work with non-CSP Alpine 448 ['not' + camelCase]() {return !this[key]}, 449 ['toggle' + camelCase]() {this[key] = !this[key]}, 450 [`set${camelCase}True`]() {this[key] = true}, 451 [`set${camelCase}False`]() {this[key] = false}, 452 } 453 ), { 454 set(target, prop, value) { 455 return prop === name 456 ? (target[key] = !!value) 457 : Reflect.set(...arguments); 458 } 459 }) 460 } 461 462 hyva.alpineInitialized = (fn) => window.addEventListener('alpine:initialized', fn, {once: true}) 463 window.addEventListener('alpine:init', () => Alpine.data('{}', () => ({})), {once: true}); 464 window.addEventListener('user-allowed-save-cookie', () => internalCookie.saveTempStorageCookies()) 465 466 }( window.hyva = window.hyva || {} )); 467 //# sourceURL=hyva.js 468</script>
vendor: 1 bytes, line 468
468
469<script> 470 if (HTMLScriptElement.supports && HTMLScriptElement.supports('speculationrules') && document.body) { 471 const specScript = document.createElement('script'); 472 specScript.type = 'speculationrules'; 473 specRules = { 474 'prerender': [{ 475 'where': { 476 'and': [ 477 { 'href_matches': '/*' }, 478 { "not": { "href_matches": "/(customer|checkout|wishlist)*" } }, 479 { 'not': { 'selector_matches': '.do-not-prerender'}}, 480 { 'not': { 'selector_matches': '[rel~=nofollow]' } } 481 ] 482 }, 483 'eagerness': 'moderate' 484 }] 485 }; 486 specScript.textContent = JSON.stringify(specRules); 487 document.body.append(specScript); 488 } 489</script>
489 490</head> 491<body id="html-body" class="page-with-filter page-products categorypath-boeken category-boeken catalog-category-view page-layout-2columns-left">
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/version1790248052/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="7SrAQN4OFxHzhKHj" />
523<script nonce="ZTc3MHNqenIwcWZ2NnRtNHpuY2VjbnV2MDhwbWRtMDI="> 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="ZTc3MHNqenIwcWZ2NnRtNHpuY2VjbnV2MDhwbWRtMDI="> 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 underline">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="category-view mx-auto flex flex-col items-center"><div class="w-full border-2 border-solid border-gray-900 bg-none text-center uppercase mx-auto mb-4 p-4 lg:p-8 bg-cover"> 1650 <h1>Boeken</h1> 1651</div> 1652</div><div class="row"><div><div class="row"><div class="columns"><div class="sidebar sidebar-main"> 1653 <div 1654 role="region" 1655 aria-label="Product filters" 1656 class="block border border-container bg-container-darker p-4 md:border-0 md:bg-transparent md:py-0 md:px-0 my-6" 1657 x-data="initLayeredNavigation()" 1658 x-init="checkIsMobileResolution()" 1659 @resize.window.debounce="checkIsMobileResolution()" 1660 @visibilitychange.window.debounce="checkIsMobileResolution()" 1661 > 1662 <h2> 1663 <div 1664 class="block-title h-10 flex items-center justify-between" 1665 :class="{ 'cursor-pointer' : isMobile }" 1666 :role="isMobile ? 'button' : 'presentation'" 1667 :aria-expanded="isMobile ? blockOpen : undefined" 1668 :aria-controls="isMobile ? 'filters-content' : undefined" 1669 :tabindex="isMobile ? 0 : undefined" 1670 @click="blockOpen = !blockOpen" 1671 @keydown.enter="blockOpen = !blockOpen" 1672 @keydown.space="blockOpen = !blockOpen" 1673 > 1674 <span class="text-primary text-md md:text-3xl font-medium uppercase"> 1675 Filteren </span> 1676 <span class="py-1 px-1 bg-container-lighter rounded border border-container-darker hidden" 1677 :class="{ 'block' : isMobile, 'hidden': !isMobile }"> 1678 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" 1679 xmlns="http://www.w3.org/2000/svg" 1680 class="transition-transform transform duration-300 ease-in-out" 1681 :class="blockOpen ? 'rotate-180' : ''"> 1682 <path d="M19 9L12 16L5 9" stroke="#4A5568" stroke-width="2" stroke-linecap="round" 1683 stroke-linejoin="round"/> 1684 </svg> 1685 </span> 1686 </div> 1687 </h2> 1688 <div 1689 id="filters-content" 1690 class="block-content filter-content pt-3" 1691 x-show="isMobile && blockOpen || !isMobile" 1692 > 1693 <a 1694 href="#products-list" 1695 class="sr-only focus:not-sr-only bg-blue-600 text-white px-12 py-4 text-center block rounded-sm" 1696 > 1697 Skip to product list </a> 1698 <div 1699 role="group" 1700 aria-labelledby="filter-option-0-title" 1701 class="filter-option card my-4" 1702 x-data="{ open: true }" 1703 > 1704 <h3 1705 id="filter-option-0-title" 1706 class="filter-options-title" 1707 > 1708 <button 1709 class="flex justify-between items-center cursor-pointer hover:text-secondary-darker border-container w-full" 1710 :class="{ 'border-b pb-4': open }" 1711 @click="open = !open" 1712 aria-controls="filter-option-0-content" 1713 :aria-expanded="open" 1714 >
1715 <span class="title text-md md:text-lg font-semibold"> 1716 Categorie <span class="sr-only"> filter</span> 1717 </span> 1718 <span class="py-1 px-1 rounded border border-container"> 1719 <svg 1720 width="24" 1721 height="24" 1722 viewBox="0 0 24 24" 1723 fill="none" 1724 xmlns="http://www.w3.org/2000/svg" 1725 class="transition-transform transform duration-300 ease-in-out" 1726 :class="open ? 'rotate-180' : ''" 1727 aria-hidden="true" 1728 > 1729 <path 1730 d="M19 9L12 16L5 9" 1731 stroke="#4A5568" 1732 stroke-width="2" 1733 stroke-linecap="round" 1734 stroke-linejoin="round" 1735 /> 1736 </svg> 1737 </span> 1738 </button> 1739 </h3> 1740 <div 1741 id="filter-option-0-content" 1742 class="filter-options-content pt-3" 1743 x-show="open" 1744 > 1745 1746<ol class="items" aria-label="Categorie filter options"> 1747 <li class="item my-1"> 1748 <a 1749 href="https://uitgeverijpluim.nl/boeken/fictie" 1750 class="flex items-center" 1751 > 1752 Fictie (<span class="count">82</span> 1753 <span class="sr-only">products available</span>) 1754 1755 </a> 1756 </li> 1757 <li class="item my-1"> 1758 <a 1759 href="https://uitgeverijpluim.nl/boeken/poezie" 1760 class="flex items-center" 1761 > 1762 Poëzie (<span class="count">22</span> 1763 <span class="sr-only">products available</span>) 1764 1765 </a> 1766 </li> 1767 <li class="item my-1"> 1768 <a 1769 href="https://uitgeverijpluim.nl/boeken/non-fictie" 1770 class="flex items-center" 1771 > 1772 Non-fictie (<span class="count">149</span> 1773 <span class="sr-only">products available</span>) 1774 1775 </a> 1776 </li> 1777 <li class="item my-1"> 1778 <a 1779 href="https://uitgeverijpluim.nl/boeken/verwacht" 1780 class="flex items-center" 1781 > 1782 Verwacht (<span class="count">11</span> 1783 <span class="sr-only">products available</span>) 1784 1785 </a> 1786 </li> 1787 </ol> 1788 </div> 1789 </div> 1790 </div> 1791 </div> 1792
1792<script> 1793 function initLayeredNavigation() { 1794 return { 1795 isMobile: false, 1796 blockOpen: false, 1797 checkIsMobileResolution: function () { 1798 this.isMobile = window.outerWidth < 768 1799 } 1800 } 1801 } 1802 </script>
1802 1803 </div><div class="column main"> <section class="py-2 lg:py-8"> 1804
1804<script> 1805 function initToolbar_6ab5759acae63() { 1806 return { 1807 options: { 1808 url: 'https://uitgeverijpluim.nl/boeken' 1809 }, 1810 oneDirection: false, 1811 changeUrl: function (paramName, paramValue, defaultValue) { 1812 var decode = window.decodeURIComponent, 1813 urlPaths = this.options.url.split('?'), 1814 baseUrl = urlPaths[0], 1815 urlParams = urlPaths[1] ? urlPaths[1].split('&') : [], 1816 paramData = {}, 1817 parameters, i, form, params, key, input, formKey; 1818 1819 for (i = 0; i < urlParams.length; i++) { 1820 parameters = urlParams[i].split('='); 1821 paramData[decode(parameters[0])] = parameters[1] !== undefined ? 1822 decode(parameters[1].replace(/\+/g, '%20')) : 1823 ''; 1824 } 1825 paramData[paramName] = paramValue; 1826 1827 if (this.options.post) { 1828 form = document.createElement('form'); 1829 params = [this.options.mode, this.options.direction, this.options.order, this.options.limit]; 1830 1831 for (key in paramData) { 1832 if (params.indexOf(key) !== -1) { //eslint-disable-line max-depth 1833 input = document.createElement('input'); 1834 input.name = key; 1835 input.value = paramData[key]; 1836 form.appendChild(input); 1837 delete paramData[key]; 1838 } 1839 } 1840 formKey = document.createElement('input'); 1841 formKey.name = 'form_key'; 1842 formKey.value = this.options.formKey; 1843 form.appendChild(formKey); 1844 1845 paramData = $.param(paramData); 1846 baseUrl += paramData.length ? '?' + paramData : ''; 1847 1848 form.action = baseUrl; 1849 form.method = 'POST'; 1850 document.body.appendChild(form); 1851 form.submit(); 1852 } else { 1853 if (paramValue === defaultValue) { //eslint-disable-line eqeqeq 1854 delete paramData[paramName]; 1855 } 1856 paramData = Object.keys(paramData).map(function(k) { 1857 return encodeURIComponent(k) + '=' + encodeURIComponent(paramData[k]) 1858 }).join('&'); 1859 1860 location.href = baseUrl + (paramData.length ? '?' + paramData : ''); 1861 } 1862 } 1863 } 1864 } 1865</script>
1865 1866 <div x-data="initToolbar_6ab5759acae63()" 1867 class="toolbar toolbar-products h-28 grid grid-cols-3 grid-flow-row gap-2 items-center"> 1868 <div class="toolbar-sorter sorter flex items-center order-1 justify-left col-span-1 invisible md:inline-block"> 1869 <label class="sr-only sorter-label" for="sorter">Sorteer op</label> 1870 <select id="sorter" data-role="sorter" class="form-select text-sm leading-normal w-full rounded-none h-2md py-1 px-3 text-gray-700 border border-gray-200 focus:outline-none focus:shadow-none focus:border-gray-1400 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-900" 1871 @change="changeUrl('product_list_order',event.currentTarget.options[event.currentTarget.selectedIndex].value,'position')"> 1872 <option value="position" 1873 > 1874 Positie </option> 1875 <option value="name" 1876 > 1877 Productnaam </option> 1878 <option value="price" 1879 > 1880 prijs </option> 1881 <option value="publish_date" 1882 selected="selected" 1883 > 1884 Verschijningsdatum </option> 1885 </select> 1886 <a title="Van laag naar hoog sorteren" 1887 href="#" 1888 class="action sorter-action sort-desc text-gray-400 ml-2" 1889 data-role="direction-switcher" 1890 data-value="asc" 1891 @click.prevent="changeUrl('product_list_dir','asc','asc')" 1892 x-show="!oneDirection" 1893 > 1894 <span> 1895 <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 1896 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12" /> 1897 </svg> 1898 </span> 1899 </a> 1900 </div> 1901 1902 1903 <div class="pages order-2 col-span-2 justify-end flex"> 1904 <strong class="sr-only label pages-label" id="paging-label_154">Pagina</strong> 1905 <ul class="pagination flex ml-auto -mr-3" aria-labelledby="paging-label_154"> 1906 1907 1908 1909 <li> 1910 <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 border-b-2 text-gray-800 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken" title="Pagina 1"> 1911 1 </a> 1912 </li> 1913 <li> 1914 <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken?p=2" title="Pagina 2"> 1915 2 </a> 1916 </li> 1917 <li> 1918 <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken?p=3" title="Pagina 3"> 1919 3 </a> 1920 </li> 1921 <li> 1922 <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken?p=4" title="Pagina 4"> 1923 4 </a> 1924 </li> 1925 <li> 1926 <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken?p=5" title="Pagina 5"> 1927 5 </a> 1928 </li> 1929 1930 1931 1932 <li> 1933 <a class="inline-block py-3 px-5 uppercase text-gray-700 border-b-2 border-transparent hover:text-gray-800 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken?p=2" title="Volgende"> 1934 <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> 1935 <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path> 1936 </svg> 1937 </a> 1938 </li> 1939 </ul> 1940 1941 </div> 1942 1943 </div> 1944 <div class="products wrapper mode-grid products-grid"> 1945 <div class="mx-auto md:grid md:grid-cols-3 gap-x-4 pt-4 pb-12 -mr-2"> 1946 <div class="item product product-item w-full mt-8"> 1947 <form method="post" 1948 action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2Vu/product/890/" 1949 class="product_addtocart_form card card-interactive mr-2 mb-2" 1950 > 1951 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <input type="hidden" name="product" value="890"/> 1952 <div class="relative "> 1953 <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white"> 1954 <a class="absolute w-full h-full z-30"
1955 href="https://uitgeverijpluim.nl/boeken/de-dichter-en-de-duivel-t-shirt" aria-label="De dichter en de duivel - T-shirt"></a> 1956 <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/IJMBK7OxZCX_F02YzJNyteKnaJb1jJ4DBt4Ma_SOZdQ/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL3QvLS90LXNoaXJ0X3Zvb3JrYW50X2RlX2RpY2h0ZXJfZW5fZGVfZHVpdmVsLmpwZw.jpg" alt="De dichter en de duivel - T-shirt" itemprop="image" loading="eager" fetchpriority="high"> 1957 <div 1958 class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none"> 1959 <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none" 1960 href="https://uitgeverijpluim.nl/boeken/de-dichter-en-de-duivel-t-shirt" aria-label="De dichter en de duivel - T-shirt"></a> 1961 1962 <a href="https://uitgeverijpluim.nl/boeken/de-dichter-en-de-duivel-t-shirt" aria-label="De dichter en de duivel - T-shirt" 1963 class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a> 1964 </div> 1965 </div> 1966 <div class="mt-6"> 1967 <div class="text-sm leading-normal uppercase mb-1px"> 1968 <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400" 1969 href="https://uitgeverijpluim.nl/boeken/de-dichter-en-de-duivel-t-shirt" aria-label="De dichter en de duivel - T-shirt"> 1970 De dichter en de duivel - T-shirt </a> 1971 </div> 1972 <div class="flex justify-between items-center"> 1973 <p class="text-gray-1400 text-base leading-normal"> 1974 <ins class="no-underline">€ 25,00</ins> 1975 </p> 1976 </div> 1977 </div> 1978 </div> 1979 </form> 1980</div> 1981 <div class="item product product-item w-full mt-8"> 1982 <form method="post" 1983 action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2Vu/product/917/" 1984 class="product_addtocart_form card card-interactive mr-2 mb-2" 1985 > 1986 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <input type="hidden" name="product" value="917"/> 1987 <div class="relative "> 1988 <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white"> 1989 <a class="absolute w-full h-full z-30" 1990 href="https://uitgeverijpluim.nl/boeken/wij-komen-van-de-werkplaats" aria-label="Hannah van Binsbergen - Wij komen van de werkplaats"></a> 1991 <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/GD7Tph62C0UrEg6ZZ3br1I6NwZQHDNCrWaq1-o-44gE/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL2IvaS9iaW5zYmVyZ2VuX2hhbm5haF92YW5fLV93aWpfa29tZW5fdmFuX2RlX3dlcmtwbGFhdHMuanBlZw.jpeg" alt="Wij komen van de werkplaats" itemprop="image" loading="eager" fetchpriority="high"> 1992 <div 1993 class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none"> 1994 <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none" 1995 href="https://uitgeverijpluim.nl/boeken/wij-komen-van-de-werkplaats" aria-label="Hannah van Binsbergen - Wij komen van de werkplaats"></a> 1996 1997 <a href="https://uitgeverijpluim.nl/boeken/wij-komen-van-de-werkplaats" aria-label="Hannah van Binsbergen - Wij komen van de werkplaats" 1998 class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a> 1999 </div> 2000 </div> 2001 <div class="mt-6"> 2002 <div class="text-sm leading-normal uppercase mb-1px"> 2003 <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400" 2004 href="https://uitgeverijpluim.nl/boeken/wij-komen-van-de-werkplaats" aria-label="Hannah van Binsbergen - Wij komen van de werkplaats"> 2005 Hannah van Binsbergen - Wij komen van de werkplaats </a> 2006 </div> 2007 <div class="flex justify-between items-center"> 2008 <p class="text-gray-1400 text-base leading-normal"> 2009 <ins class="no-underline">€ 21,99</ins> 2010 </p> 2011 </div> 2012 </div> 2013 </div> 2014 </form> 2015</div> 2016 <div class="item product product-item w-full mt-8"> 2017 <form method="post" 2018 action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2Vu/product/914/" 2019 class="product_addtocart_form card card-interactive mr-2 mb-2" 2020 > 2021 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <input type="hidden" name="product" value="914"/> 2022 <div class="relative "> 2023 <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white"> 2024 <a class="absolute w-full h-full z-30"
2025 href="https://uitgeverijpluim.nl/boeken/zwijgen-is-zilver-kunst-en-activisme" aria-label="Quinsy Gario - Zwijgen is zilver. Kunst en activisme"></a> 2026 <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/hQasEFXOEOJJuX613HEcVx1hQJsYsLW2vpwtBGZ2vL4/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL2cvYS9nYXJpb19xdWluc3lfLV96d2lqZ2VuX2lzX3ppbHZlci5qcGc.jpg" alt="Zwijgen is zilver. Kunst en activisme" itemprop="image" loading="eager" fetchpriority="high"> 2027 <div 2028 class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none"> 2029 <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none" 2030 href="https://uitgeverijpluim.nl/boeken/zwijgen-is-zilver-kunst-en-activisme" aria-label="Quinsy Gario - Zwijgen is zilver. Kunst en activisme"></a> 2031 2032 <a href="https://uitgeverijpluim.nl/boeken/zwijgen-is-zilver-kunst-en-activisme" aria-label="Quinsy Gario - Zwijgen is zilver. Kunst en activisme" 2033 class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a> 2034 </div> 2035 </div> 2036 <div class="mt-6"> 2037 <div class="text-sm leading-normal uppercase mb-1px"> 2038 <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400" 2039 href="https://uitgeverijpluim.nl/boeken/zwijgen-is-zilver-kunst-en-activisme" aria-label="Quinsy Gario - Zwijgen is zilver. Kunst en activisme"> 2040 Quinsy Gario - Zwijgen is zilver. Kunst en activisme </a> 2041 </div> 2042 <div class="flex justify-between items-center"> 2043 <p class="text-gray-1400 text-base leading-normal"> 2044 <ins class="no-underline">€ 21,99</ins> 2045 </p> 2046 </div> 2047 </div> 2048 </div> 2049 </form> 2050</div> 2051 <div class="item product product-item w-full mt-8"> 2052 <form method="post" 2053 action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2Vu/product/920/" 2054 class="product_addtocart_form card card-interactive mr-2 mb-2" 2055 > 2056 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <input type="hidden" name="product" value="920"/> 2057 <div class="relative "> 2058 <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white"> 2059 <a class="absolute w-full h-full z-30" 2060 href="https://uitgeverijpluim.nl/boeken/joods-zijn-na-de-vernietiging-van-gaza" aria-label="Peter Beinart - Joods zijn na de vernietiging van Gaza"></a> 2061 <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/uZCiCBTbXwsx5KVWgQr1E_BLApbexyC3iBeI2ymNNJE/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL2IvZS9iZWluYXJ0X3BldGVyXy1fam9vZHNfemlqbl9uYV9kZV92ZXJuaWV0aWdpbmdfdmFuX2dhemEuanBn.jpg" alt="Joods zijn na de vernietiging van Gaza" itemprop="image" loading="lazy" decoding="async" fetchpriority="low"> 2062 <div 2063 class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none"> 2064 <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none" 2065 href="https://uitgeverijpluim.nl/boeken/joods-zijn-na-de-vernietiging-van-gaza" aria-label="Peter Beinart - Joods zijn na de vernietiging van Gaza"></a> 2066 2067 <a href="https://uitgeverijpluim.nl/boeken/joods-zijn-na-de-vernietiging-van-gaza" aria-label="Peter Beinart - Joods zijn na de vernietiging van Gaza" 2068 class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a> 2069 </div> 2070 </div> 2071 <div class="mt-6"> 2072 <div class="text-sm leading-normal uppercase mb-1px"> 2073 <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400" 2074 href="https://uitgeverijpluim.nl/boeken/joods-zijn-na-de-vernietiging-van-gaza" aria-label="Peter Beinart - Joods zijn na de vernietiging van Gaza"> 2075 Peter Beinart - Joods zijn na de vernietiging van Gaza </a> 2076 </div> 2077 <div class="flex justify-between items-center"> 2078 <p class="text-gray-1400 text-base leading-normal"> 2079 <ins class="no-underline">€ 22,99</ins> 2080 </p> 2081 </div> 2082 </div> 2083 </div> 2084 </form> 2085</div> 2086 <div class="item product product-item w-full mt-8"> 2087 <form method="post" 2088 action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2Vu/product/907/" 2089 class="product_addtocart_form card card-interactive mr-2 mb-2" 2090 > 2091 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <input type="hidden" name="product" value="907"/> 2092 <div class="relative "> 2093 <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white"> 2094 <a class="absolute w-full h-full z-30"
2095 href="https://uitgeverijpluim.nl/boeken/ik-ben-oud-dat-komt-door-mijn-leeftijd" aria-label="Yvonne Kroonenberg - Ik ben oud, dat komt door mijn leeftijd"></a> 2096 <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/hbc_jHuDZ99SEC7Be7EztI0-qq5quK-W51OIdXWcobQ/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL2svci9rcm9vbmVuYmVyZ195dm9ubmVfLV9pa19iZW5fb3VkX2RhdF9rb210X2Rvb3JfbWlqbl9sZWVmdGlqZC5qcGc.jpg" alt="Ik ben oud, dat komt door mijn leeftijd" itemprop="image" loading="lazy" decoding="async" fetchpriority="low"> 2097 <div 2098 class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none"> 2099 <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none" 2100 href="https://uitgeverijpluim.nl/boeken/ik-ben-oud-dat-komt-door-mijn-leeftijd" aria-label="Yvonne Kroonenberg - Ik ben oud, dat komt door mijn leeftijd"></a> 2101 2102 <a href="https://uitgeverijpluim.nl/boeken/ik-ben-oud-dat-komt-door-mijn-leeftijd" aria-label="Yvonne Kroonenberg - Ik ben oud, dat komt door mijn leeftijd" 2103 class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a> 2104 </div> 2105 </div> 2106 <div class="mt-6"> 2107 <div class="text-sm leading-normal uppercase mb-1px"> 2108 <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400" 2109 href="https://uitgeverijpluim.nl/boeken/ik-ben-oud-dat-komt-door-mijn-leeftijd" aria-label="Yvonne Kroonenberg - Ik ben oud, dat komt door mijn leeftijd"> 2110 Yvonne Kroonenberg - Ik ben oud, dat komt door mijn leeftijd </a> 2111 </div> 2112 <div class="flex justify-between items-center"> 2113 <p class="text-gray-1400 text-base leading-normal"> 2114 <ins class="no-underline">€ 22,99</ins> 2115 </p> 2116 </div> 2117 </div> 2118 </div> 2119 </form> 2120</div> 2121 <div class="item product product-item w-full mt-8"> 2122 <form method="post" 2123 action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2Vu/product/910/" 2124 class="product_addtocart_form card card-interactive mr-2 mb-2" 2125 > 2126 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <input type="hidden" name="product" value="910"/> 2127 <div class="relative "> 2128 <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white"> 2129 <a class="absolute w-full h-full z-30" 2130 href="https://uitgeverijpluim.nl/boeken/claus-het-verscheurde-leven-van-een-prins" aria-label="Jutta Chorus en Bas Blokker - Claus. Het verscheurde leven van een prins"></a> 2131 <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/XfcNMrIfRdjvRfgZFePpox-BLly4T_bQCrLCTWQZBuc/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL2MvaC9jaG9ydXNfZW5fYmxva2tlcl8tX2NsYXVzLnBuZw.png" alt="Claus. Het verscheurde leven van een prins" itemprop="image" loading="lazy" decoding="async" fetchpriority="low"> 2132 <div 2133 class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none"> 2134 <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none" 2135 href="https://uitgeverijpluim.nl/boeken/claus-het-verscheurde-leven-van-een-prins" aria-label="Jutta Chorus en Bas Blokker - Claus. Het verscheurde leven van een prins"></a> 2136 2137 <a href="https://uitgeverijpluim.nl/boeken/claus-het-verscheurde-leven-van-een-prins" aria-label="Jutta Chorus en Bas Blokker - Claus. Het verscheurde leven van een prins" 2138 class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a> 2139 </div> 2140 </div> 2141 <div class="mt-6"> 2142 <div class="text-sm leading-normal uppercase mb-1px"> 2143 <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400" 2144 href="https://uitgeverijpluim.nl/boeken/claus-het-verscheurde-leven-van-een-prins" aria-label="Jutta Chorus en Bas Blokker - Claus. Het verscheurde leven van een prins"> 2145 Jutta Chorus en Bas Blokker - Claus. Het verscheurde leven van een prins </a> 2146 </div> 2147 <div class="flex justify-between items-center"> 2148 <p class="text-gray-1400 text-base leading-normal"> 2149 <ins class="no-underline">
2149€ 36,99</ins> 2150 </p> 2151 </div> 2152 </div> 2153 </div> 2154 </form> 2155</div> 2156 <div class="item product product-item w-full mt-8"> 2157 <form method="post" 2158 action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2Vu/product/904/" 2159 class="product_addtocart_form card card-interactive mr-2 mb-2" 2160 > 2161 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <input type="hidden" name="product" value="904"/> 2162 <div class="relative "> 2163 <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white"> 2164 <a class="absolute w-full h-full z-30" 2165 href="https://uitgeverijpluim.nl/boeken/auschwitz-is-dood" aria-label="David Wertheim - Auschwitz is dood"></a> 2166 <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/sWPyJdNm5_aeeZU8i_sx3jRJrB41lTSo79BjiHRVRnw/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL3cvZS93ZXJ0aGVpbV9kYXZpZF8tX2F1c2Nod2l0el9pc19kb29kXzEuanBn.jpg" alt="Auschwitz is dood" itemprop="image" loading="lazy" decoding="async" fetchpriority="low"> 2167 <div 2168 class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none"> 2169 <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none" 2170 href="https://uitgeverijpluim.nl/boeken/auschwitz-is-dood" aria-label="David Wertheim - Auschwitz is dood"></a> 2171 2172 <a href="https://uitgeverijpluim.nl/boeken/auschwitz-is-dood" aria-label="David Wertheim - Auschwitz is dood" 2173 class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a> 2174 </div> 2175 </div> 2176 <div class="mt-6"> 2177 <div class="text-sm leading-normal uppercase mb-1px"> 2178 <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400" 2179 href="https://uitgeverijpluim.nl/boeken/auschwitz-is-dood" aria-label="David Wertheim - Auschwitz is dood"> 2180 David Wertheim - Auschwitz is dood </a> 2181 </div> 2182 <div class="flex justify-between items-center"> 2183 <p class="text-gray-1400 text-base leading-normal"> 2184 <ins class="no-underline">€ 21,99</ins> 2185 </p> 2186 </div> 2187 </div> 2188 </div> 2189 </form> 2190</div> 2191 <div class="item product product-item w-full mt-8"> 2192 <form method="post" 2193 action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2Vu/product/860/" 2194 class="product_addtocart_form card card-interactive mr-2 mb-2" 2195 > 2196 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <input type="hidden" name="product" value="860"/> 2197 <div class="relative "> 2198 <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white"> 2199 <a class="absolute w-full h-full z-30" 2200 href="https://uitgeverijpluim.nl/boeken/samen-een-manifest-tegen-een-harteloze-samenleving" aria-label="Ece Temelkuran - Samen. Een manifest tegen een harteloze samenleving"></a> 2201 <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/j9k-GraBG8-y0uPHqQ7IeaNkAmkQ7OPgns1bz1Jhodk/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL3QvZS90ZW1lbGt1cmFuX2VjZV8tX3NhbWVuLmpwZw.jpg" alt="Samen. Een manifest tegen een harteloze samenleving" itemprop="image" loading="lazy" decoding="async" fetchpriority="low"> 2202 <div 2203 class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none"> 2204 <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none" 2205 href="https://uitgeverijpluim.nl/boeken/samen-een-manifest-tegen-een-harteloze-samenleving" aria-label="Ece Temelkuran - Samen. Een manifest tegen een harteloze samenleving"></a> 2206 2207 <a href="https://uitgeverijpluim.nl/boeken/samen-een-manifest-tegen-een-harteloze-samenleving" aria-label="Ece Temelkuran - Samen. Een manifest tegen een harteloze samenleving" 2208 class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a> 2209 </div> 2210 </div> 2211 <div class="mt-6"> 2212 <div class="text-sm leading-normal uppercase mb-1px"> 2213 <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400" 2214 href="https://uitgeverijpluim.nl/boeken/samen-een-manifest-tegen-een-harteloze-samenleving" aria-label="Ece Temelkuran - Samen. Een manifest tegen een harteloze samenleving"> 2215 Ece Temelkuran - Samen. Een manifest tegen een harteloze samenleving </a> 2216 </div> 2217 <div class="flex justify-between items-center"> 2218 <p class="text-gray-1400 text-base leading-normal"> 2219 <ins class="no-underline">€ 22,99</ins> 2220 </p> 2221 </div> 2222 </div> 2223 </div> 2224 </form> 2225</div> 2226 <div class="item product product-item w-full mt-8"> 2227 <form method="post" 2228 action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2Vu/product/877/" 2229 class="product_addtocart_form card card-interactive mr-2 mb-2" 2230 > 2231 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <input type="hidden" name="product" value="877"/> 2232 <div class="relative "> 2233 <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white"> 2234 <a class="absolute w-full h-full z-30"
2235 href="https://uitgeverijpluim.nl/boeken/brooklyn-de-memoires-van-abel-sikkink" aria-label="Bert Wagendorp - Brooklyn. De memoires van Abel Sikkink"></a> 2236 <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/2cqRBsAHUN8IEq7fj1PpWOLdak7C6kpkvRVM3ncRnfw/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL3cvYS93YWdlbmRvcnBfYmVydF8tX2Jyb29rbHluLmpwZw.jpg" alt="Brooklyn. De memoires van Abel Sikkink" itemprop="image" loading="lazy" decoding="async" fetchpriority="low"> 2237 <div 2238 class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none"> 2239 <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none" 2240 href="https://uitgeverijpluim.nl/boeken/brooklyn-de-memoires-van-abel-sikkink" aria-label="Bert Wagendorp - Brooklyn. De memoires van Abel Sikkink"></a> 2241 2242 <a href="https://uitgeverijpluim.nl/boeken/brooklyn-de-memoires-van-abel-sikkink" aria-label="Bert Wagendorp - Brooklyn. De memoires van Abel Sikkink" 2243 class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a> 2244 </div> 2245 </div> 2246 <div class="mt-6"> 2247 <div class="text-sm leading-normal uppercase mb-1px"> 2248 <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400" 2249 href="https://uitgeverijpluim.nl/boeken/brooklyn-de-memoires-van-abel-sikkink" aria-label="Bert Wagendorp - Brooklyn. De memoires van Abel Sikkink"> 2250 Bert Wagendorp - Brooklyn. De memoires van Abel Sikkink </a> 2251 </div> 2252 <div class="flex justify-between items-center"> 2253 <p class="text-gray-1400 text-base leading-normal"> 2254 <ins class="no-underline">€ 26,99</ins> 2255 </p> 2256 </div> 2257 </div> 2258 </div> 2259 </form> 2260</div> 2261 <div class="item product product-item w-full mt-8"> 2262 <form method="post" 2263 action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2Vu/product/886/" 2264 class="product_addtocart_form card card-interactive mr-2 mb-2" 2265 > 2266 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <input type="hidden" name="product" value="886"/> 2267 <div class="relative "> 2268 <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white"> 2269 <a class="absolute w-full h-full z-30" 2270 href="https://uitgeverijpluim.nl/boeken/control-en-delete-herinneringen-aan-autoritair-china" aria-label="Garrie van Pinxteren - Control en delete. Herinneringen aan autoritair China"></a> 2271 <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/bxFNbWrIprExsNBmrB4uku5B_7HzxoTpor4Lnwq1CTo/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL3AvaS9waW54dGVyZW5fZ2FycmllX3Zhbl8tX2NvbnRyb2xfZW5fZGVsZXRlLnBuZw.png" alt="Control en delete. Herinneringen aan autoritair China" itemprop="image" loading="lazy" decoding="async" fetchpriority="low"> 2272 <div 2273 class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none"> 2274 <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none" 2275 href="https://uitgeverijpluim.nl/boeken/control-en-delete-herinneringen-aan-autoritair-china" aria-label="Garrie van Pinxteren - Control en delete. Herinneringen aan autoritair China"></a> 2276 2277 <a href="https://uitgeverijpluim.nl/boeken/control-en-delete-herinneringen-aan-autoritair-china" aria-label="Garrie van Pinxteren - Control en delete. Herinneringen aan autoritair China" 2278 class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a> 2279 </div> 2280 </div> 2281 <div class="mt-6"> 2282 <div class="text-sm leading-normal uppercase mb-1px"> 2283 <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400" 2284 href="https://uitgeverijpluim.nl/boeken/control-en-delete-herinneringen-aan-autoritair-china" aria-label="Garrie van Pinxteren - Control en delete. Herinneringen aan autoritair China"> 2285 Garrie van Pinxteren - Control en delete. Herinneringen aan autoritair China </a> 2286 </div> 2287 <div class="flex justify-between items-center"> 2288 <p class="text-gray-1400 text-base leading-normal"> 2289 <ins class="no-underline">€ 24,99</ins> 2290 </p> 2291 </div> 2292 </div> 2293 </div> 2294 </form> 2295</div> 2296 <div class="item product product-item w-full mt-8"> 2297 <form method="post" 2298 action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2Vu/product/901/" 2299 class="product_addtocart_form card card-interactive mr-2 mb-2" 2300 > 2301 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <input type="hidden" name="product" value="901"/> 2302 <div class="relative "> 2303 <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white"> 2304 <a class="absolute w-full h-full z-30"
2305 href="https://uitgeverijpluim.nl/boeken/thomas-in-het-verpleeghuis" aria-label="Thomas van der Meer - Thomas in het verpleeghuis"></a> 2306 <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/fSJQJ9XQtG7QAAAxow2XruMA6Yy59zCP9y62SH77i30/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL28vbS9vbXNsYWdfdGhvbWFzX2luX2hldF92ZXJwbGVlZ2h1aXMyLmpwZw.jpg" alt="Thomas in het verpleeghuis" itemprop="image" loading="lazy" decoding="async" fetchpriority="low"> 2307 <div 2308 class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none"> 2309 <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none" 2310 href="https://uitgeverijpluim.nl/boeken/thomas-in-het-verpleeghuis" aria-label="Thomas van der Meer - Thomas in het verpleeghuis"></a> 2311 2312 <a href="https://uitgeverijpluim.nl/boeken/thomas-in-het-verpleeghuis" aria-label="Thomas van der Meer - Thomas in het verpleeghuis" 2313 class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a> 2314 </div> 2315 </div> 2316 <div class="mt-6"> 2317 <div class="text-sm leading-normal uppercase mb-1px"> 2318 <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400" 2319 href="https://uitgeverijpluim.nl/boeken/thomas-in-het-verpleeghuis" aria-label="Thomas van der Meer - Thomas in het verpleeghuis"> 2320 Thomas van der Meer - Thomas in het verpleeghuis </a> 2321 </div> 2322 <div class="flex justify-between items-center"> 2323 <p class="text-gray-1400 text-base leading-normal"> 2324 <ins class="no-underline">€ 22,99</ins> 2325 </p> 2326 </div> 2327 </div> 2328 </div> 2329 </form> 2330</div> 2331 <div class="item product product-item w-full mt-8"> 2332 <form method="post" 2333 action="https://uitgeverijpluim.nl/checkout/cart/add/uenc/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvYm9la2Vu/product/883/" 2334 class="product_addtocart_form card card-interactive mr-2 mb-2" 2335 > 2336 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <input type="hidden" name="product" value="883"/> 2337 <div class="relative "> 2338 <div class="relative overflow-hidden group cursor-pointer z-30 aspect-[2/3] bg-white"> 2339 <a class="absolute w-full h-full z-30" 2340 href="https://uitgeverijpluim.nl/boeken/middle-management-science" aria-label="Nachoem M. Wijnberg - Middle management science."></a> 2341 <img class="w-full h-full object-cover" width="480" height="768" src="https://images.uitgeverijpluim.nl/dcjgBXAU8FtDyhdUbO9GKKRK9wPFlx83KaINQOrwMFA/w:0/h:768/rt:fit/g:sm/el:1/ex:1:ce:0:0/aHR0cHM6Ly91aXRnZXZlcmlqcGx1aW0ubmwvbWVkaWEvY2F0YWxvZy9wcm9kdWN0L2NhY2hlLzBlNTdhMjc1NmViNWNhNDRiZjY2YTUxZTM1OTU3MzRhL3cvaS93aWpuYmVyZ19uYWNob2VtXy1fbWlkZGxlX21hbmFnZW1lbnRfc2NpZW5jZS5qcGc.jpg" alt="Middle management science." itemprop="image" loading="lazy" decoding="async" fetchpriority="low"> 2342 <div 2343 class="hidden lg:flex absolute z-50 left-0 top-0 w-full h-full items-center justify-center transform translate-x-full transition-transform duration-400 group-hover:translate-x-0 pointer-events-none"> 2344 <a class="absolute w-full h-full z-40 opacity-0 bg-gray-400 bg-opacity-50 group-hover:opacity-100 pointer-events-none" 2345 href="https://uitgeverijpluim.nl/boeken/middle-management-science" aria-label="Nachoem M. Wijnberg - Middle management science."></a> 2346 2347 <a href="https://uitgeverijpluim.nl/boeken/middle-management-science" aria-label="Nachoem M. Wijnberg - Middle management science." 2348 class="z-50 my-1 btn btn--hover block uppercase py-sm px-6 border-3 border-gray-1000 bg-white relative text-gray-900 hover:text-white pointer-events-auto">Bekijk product</a> 2349 </div> 2350 </div> 2351 <div class="mt-6"> 2352 <div class="text-sm leading-normal uppercase mb-1px"> 2353 <a class="text-gray-900 hover:text-gray-1400 transition-colors duration-400" 2354 href="https://uitgeverijpluim.nl/boeken/middle-management-science" aria-label="Nachoem M. Wijnberg - Middle management science."> 2355 Nachoem M. Wijnberg - Middle management science. </a> 2356 </div> 2357 <div class="flex justify-between items-center"> 2358 <p class="text-gray-1400 text-base leading-normal"> 2359 <ins class="no-underline">€ 24,99</ins> 2360 </p> 2361 </div> 2362 </div> 2363 </div> 2364 </form> 2365</div> 2366 </div> 2367 </div> 2368
2368<script> 2369 function initToolbar_6ab5759ad4c9e() { 2370 return { 2371 options: { 2372 url: 'https://uitgeverijpluim.nl/boeken' 2373 }, 2374 oneDirection: false, 2375 changeUrl: function (paramName, paramValue, defaultValue) { 2376 var decode = window.decodeURIComponent, 2377 urlPaths = this.options.url.split('?'), 2378 baseUrl = urlPaths[0], 2379 urlParams = urlPaths[1] ? urlPaths[1].split('&') : [], 2380 paramData = {}, 2381 parameters, i, form, params, key, input, formKey; 2382 2383 for (i = 0; i < urlParams.length; i++) { 2384 parameters = urlParams[i].split('='); 2385 paramData[decode(parameters[0])] = parameters[1] !== undefined ? 2386 decode(parameters[1].replace(/\+/g, '%20')) : 2387 ''; 2388 } 2389 paramData[paramName] = paramValue; 2390 2391 if (this.options.post) { 2392 form = document.createElement('form'); 2393 params = [this.options.mode, this.options.direction, this.options.order, this.options.limit]; 2394 2395 for (key in paramData) { 2396 if (params.indexOf(key) !== -1) { //eslint-disable-line max-depth 2397 input = document.createElement('input'); 2398 input.name = key; 2399 input.value = paramData[key]; 2400 form.appendChild(input); 2401 delete paramData[key]; 2402 } 2403 } 2404 formKey = document.createElement('input'); 2405 formKey.name = 'form_key'; 2406 formKey.value = this.options.formKey; 2407 form.appendChild(formKey); 2408 2409 paramData = $.param(paramData); 2410 baseUrl += paramData.length ? '?' + paramData : ''; 2411 2412 form.action = baseUrl; 2413 form.method = 'POST'; 2414 document.body.appendChild(form); 2415 form.submit(); 2416 } else { 2417 if (paramValue === defaultValue) { //eslint-disable-line eqeqeq 2418 delete paramData[paramName]; 2419 } 2420 paramData = Object.keys(paramData).map(function(k) { 2421 return encodeURIComponent(k) + '=' + encodeURIComponent(paramData[k]) 2422 }).join('&'); 2423 2424 location.href = baseUrl + (paramData.length ? '?' + paramData : ''); 2425 } 2426 } 2427 } 2428 } 2429</script>
2429 2430 <div x-data="initToolbar_6ab5759ad4c9e()" 2431 class="toolbar toolbar-products h-28 grid grid-cols-3 grid-flow-row gap-2 items-center"> 2432 <div class="colspan-1"> </div> 2433 2434 2435 <div class="pages order-2 col-span-2 justify-end flex"> 2436 <strong class="sr-only label pages-label" id="paging-label_520">Pagina</strong> 2437 <ul class="pagination flex ml-auto -mr-3" aria-labelledby="paging-label_520"> 2438 2439 2440 2441 <li> 2442 <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 border-b-2 text-gray-800 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken" title="Pagina 1"> 2443 1 </a> 2444 </li> 2445 <li> 2446 <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken?p=2" title="Pagina 2"> 2447 2 </a> 2448 </li> 2449 <li> 2450 <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken?p=3" title="Pagina 3"> 2451 3 </a> 2452 </li> 2453 <li> 2454 <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken?p=4" title="Pagina 4"> 2455 4 </a> 2456 </li> 2457 <li> 2458 <a class="inline-block py-3 px-5 uppercase hover:text-gray-800 text-gray-700 border-gray-800 hover:text-gray-1400 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken?p=5" title="Pagina 5"> 2459 5 </a> 2460 </li> 2461 2462 2463 2464 <li> 2465 <a class="inline-block py-3 px-5 uppercase text-gray-700 border-b-2 border-transparent hover:text-gray-800 transition-colors duration-400" href="https://uitgeverijpluim.nl/boeken?p=2" title="Volgende"> 2466 <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> 2467 <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path> 2468 </svg> 2469 </a> 2470 </li> 2471 </ul> 2472 2473 </div> 2474 2475 </div> 2476</section> 2477</div></div></div></div></div></main><footer class="page-footer"><div class="footer content"><footer class="mt-16 lg:mt-32"> 2478 <div class="grid grid-cols-12 gap-4"> 2479 <div class="col-span-12 sm:col-span-3"> 2480 <h2 class="uppercase text-gray-1400 mb-md text-3xl">Uitgeverij Pluim</h2> 2481 <ul class="mb-5"> 2482 <li><a href="/privacybeleid" 2483 class="block uppercase text-secondary text-sm leading-normal hover:text-primary transition-colors duration-400">Privacybeleid</a></li> 2484 <li class="mt-4 lg:mt-0"><a href="/over-ons" 2485 class="block uppercase text-secondary text-sm leading-normal hover:text-primary transition-colors duration-400">Over 2486 ons</a></li> 2487 <li class="mt-4 lg:mt-0"><a href="/contact" 2488 class="block uppercase text-secondary text-sm leading-normal hover:text-primary transition-colors duration-400">Contact</a> 2489 </li> 2490 </ul> 2491 </div> 2492 <div class="col-span-12 sm:col-span-5 lg:col-span-4 xl:col-span-3"> 2493 <div class="lg:w-1/4 md:w-1/2 w-full lg:px-4"> 2494 <form 2495 class="form subscribe mb-2md" 2496 action="https://uitgeverijpluim.nl/newsletter/subscriber/new/" 2497 method="post" 2498 x-data="initNewsletterForm" 2499 @submit.prevent="submitForm" 2500 id="newsletter-validate-detail" 2501 aria-label="Abonneren op de nieuwsbrief" 2502 > 2503 <h2 class="uppercase text-gray-1400 mb-md text-3xl">Nieuwsbrief</h2> 2504 <div class="flex justify-center gap-2 md:justify-start"> 2505 <label for="newsletter-subscribe" class="sr-only"> 2506 E-mailadres </label> 2507 <input 2508 name="email" 2509 type="email" 2510 required 2511 id="newsletter-subscribe" 2512 class="text-gray-1000 h-2md flex-grow border-2 border-gray-300 px-3 mr-1" 2513 placeholder="E-mailadres" 2514 autocomplete="email" 2515 aria-describedby="footer-newsletter-heading" 2516 > 2517 <input name="form_key" type="hidden" value="7SrAQN4OFxHzhKHj" /> <button type="submit" class="bg-primary h-2md block px-3 text-gray-900 uppercase"> 2518 OK </button> 2519 </div> 2520 <div> 2521 <template x-if="displayErrorMessage"> 2522 <p class="flex items-center text-red">
2523 <span class="inline-block w-8 h-8 mr-3"> 2524 <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"> 2525 <path stroke-linecap="round" stroke-linejoin="round" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/> 2526</svg> 2527 </span> 2528 <template x-for="errorMessage in errorMessages"> 2529 <span x-html="errorMessage"></span> 2530 </template> 2531 </p> 2532 </template> 2533 </div> 2534 </form> 2535 <div class="w-full"> 2536 </div> 2537
2537<script> 2538 function initNewsletterForm() { 2539 return { 2540 errors: 0, 2541 hasCaptchaToken: 0, 2542 displayErrorMessage: false, 2543 errorMessages: [], 2544 setErrorMessages(messages) { 2545 this.errorMessages = [messages] 2546 this.displayErrorMessage = this.errorMessages.length 2547 }, 2548 submitForm() { 2549 // Do not rename $form, the variable is expected to be declared in the recaptcha output 2550 const $form = document.querySelector('#newsletter-validate-detail'); 2551 2552 if (this.errors === 0) { 2553 $form.submit(); 2554 } 2555 } 2556 } 2557 } 2558 2559 window.addEventListener('alpine:init', () => Alpine.data('initNewsletterForm', initNewsletterForm), {once: true}) 2560 </script>
2560 2561 </div> 2562 <ul class="flex md:hidden"> 2563 <li class="pr-4"> 2564 <a class="text-gray-700 text-xl leading-normal" href="https://www.instagram.com/uitgeverijpluim/" 2565 target="_blank" title="Instagram" rel="noreferrer"> 2566 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 2567 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 2568 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> 2569 </svg> 2570 </a> 2571 </li> 2572 <li class="pr-4"> 2573 <a class="text-gray-700 text-xl leading-normal" href="https://www.facebook.com/uitgeverijpluim" 2574 target="_blank" title="Facebook" rel="noreferrer"> 2575 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 2576 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 2577 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> 2578 </svg> 2579 </a> 2580 </li> 2581 <li class="pr-4"> 2582 <a class="text-gray-700 text-xl leading-normal" 2583 href="https://www.youtube.com/channel/UCRr07oGz4RdkdjrntFZ76nw" target="_blank" title="YouTube" 2584 rel="noreferrer"> 2585 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 2586 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 2587 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> 2588 </svg> 2589 </a> 2590 </li> 2591 <li class="pr-4"> 2592 <a class="text-gray-700 text-xl leading-normal" href="https://twitter.com/uitgeverijpluim" 2593 target="_blank" title="Twitter" rel="noreferrer"> 2594 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 2595 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 2596 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> 2597 </svg> 2598 </a> 2599 </li> 2600 <li class="pr-4"> 2601 <a class="text-gray-700 text-xl leading-normal" href="https://soundcloud.com/uitgeverijpluim" 2602 target="_blank" title="Soundcloud" rel="noreferrer"> 2603 <svg viewBox="0 0 576 512" stroke="currentColor" class="h-5 w-5"> 2604 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 2605 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
2605-7.191A32 32 0 0 0 512 336v-48c0-141.479-114.496-256-256-256z" 2606 "></path> 2607 </svg> 2608 </a> 2609 </li> 2610 </ul> 2611 </div> 2612 </div> 2613 <p class="text-xs leading-normal mt-12 mb-5 lg:text-right">© Uitgeverij Pluim</p> 2614</footer> 2615</div> 2616</div> 2617</div> 2618</div> 2619</main> 2620</body> 2621</html> 2622</div></footer>
2622<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 2622
2622
2623<script> 2624(() => { 2625 class BFCacheHandler { 2626 constructor() { 2627 this.options = { 2628 isCustomerLoggedIn: false, 2629 enableUserInteractionRefreshMiniCart: true, 2630 autoCloseMenuMobile: true }; 2631 2632 this.userInteractionEvents = ['touchstart', 'mouseover', 'wheel', 'scroll', 'keydown']; 2633 this._reloadTriggered = false; 2634 this.dom = {}; 2635 2636 // Centralized selectors for reuse 2637 this.selectors = { 2638 cartDrawer: "[x-data^='initCartDrawer']", 2639 mobileMenu: "[x-data^='initMenuMobile']", 2640 desktopMenu: "[x-data^='initMenuDesktop']" 2641 }; 2642 } 2643 2644 getElement(key) { 2645 const cached = this.dom[key]; 2646 if (cached && document.contains(cached)) return cached; 2647 const el = document.querySelector(this.selectors[key]); 2648 if (el) this.dom[key] = el; 2649 return el; 2650 } 2651 2652 init() { 2653 this.refreshMiniCart(); 2654 this.reloadCustomerLoginPage(); 2655 this.actionAutoCloseMenu(this.options.autoCloseMenuMobile); 2656 } 2657 2658 refreshMiniCart() { 2659 if (this.options.enableUserInteractionRefreshMiniCart) { 2660 this.refreshMiniCartOnUserInteraction(); 2661 } else { 2662 this.actionRefreshMiniCart(); 2663 } 2664 } 2665 2666 refreshMiniCartOnUserInteraction() { 2667 const refreshMiniCart = () => { 2668 this.userInteractionEvents.forEach(eventType => 2669 window.removeEventListener(eventType, refreshMiniCart) 2670 ); 2671 this.actionRefreshMiniCart(); 2672 }; 2673 const opts = { once: true, passive: true }; 2674 this.userInteractionEvents.forEach(eventType => 2675 window.addEventListener(eventType, refreshMiniCart, opts) 2676 ); 2677 } 2678 2679 reloadCustomerLoginPage() { 2680 const backendLoggedInState = this.options.isCustomerLoggedIn; 2681 2682 let customerData = null; 2683 try { 2684 const cacheStorage = localStorage.getItem('mage-cache-storage'); 2685 if (cacheStorage) { 2686 customerData = JSON.parse(cacheStorage).customer || null; 2687 } 2688 } catch { 2689 // Ignore parse errors silently 2690 } 2691 2692 const frontendLoggedInState = Boolean(customerData?.firstname); 2693 2694 if (frontendLoggedInState !== backendLoggedInState && !this._reloadTriggered) { 2695 this._reloadTriggered = true; 2696 window.location.reload(); 2697 } 2698 } 2699 2700 actionRefreshMiniCart() { 2701 window.dispatchEvent(new CustomEvent('reload-customer-section-data')); 2702 } 2703 2704 actionAutoCloseMenu(autoCloseMenuMobile) { 2705 window.dispatchEvent(new Event('clear-messages')); 2706 2707 const cartDrawer = this.getElement('cartDrawer'); 2708 if (cartDrawer && typeof Alpine !== 'undefined') { 2709 requestAnimationFrame(() => { 2710 const cartDrawerData = Alpine.$data(cartDrawer); 2711 if (cartDrawerData?.open !== undefined) { 2712 cartDrawerData.open = false; 2713 document.body.style.overflow = ''; 2714 } 2715 }); 2716 } 2717 2718 if (autoCloseMenuMobile) { 2719 this.handleMenuClosure(); 2720 } 2721 } 2722 2723 handleMenuClosure() { 2724 if (typeof Alpine === 'undefined' || !Alpine.$data) return; 2725 2726 const mobileMenu = this.getElement('mobileMenu'); 2727 if (mobileMenu) { 2728 const mobileMenuData = Alpine.$data(mobileMenu); 2729 if (mobileMenuData?.open !== undefined) { 2730 mobileMenuData.open = false; 2731 } 2732 } 2733 2734 const desktopMenu = this.getElement('desktopMenu'); 2735 if (desktopMenu) { 2736 const desktopMenuData = Alpine.$data(desktopMenu); 2737 if (desktopMenuData?.hoverPanelActiveId !== undefined) { 2738 desktopMenuData.hoverPanelActiveId = 0; 2739 } 2740 } 2741 } 2742 } 2743 2744 window.addEventListener('pageshow', (event) => { 2745 if (event.persisted) { 2746 (window._bfcacheHandler ??= new BFCacheHandler()).init(); 2747 } 2748 }, { passive: true }); 2749})(); 2750</script>
2750 2751
vendor: 2,386 bytes, lines 2752-2812
2752<script> 2753 (() => { 2754 function src_default(Alpine) { 2755 Alpine.directive("intersect", Alpine.skipDuringClone((el, { value, expression, modifiers }, { evaluateLater, cleanup }) => { 2756 let evaluate = evaluateLater(expression); 2757 let options = { 2758 rootMargin: getRootMargin(modifiers), 2759 threshold: getThreshold(modifiers) 2760 }; 2761 let observer = new IntersectionObserver((entries) => { 2762 entries.forEach((entry) => { 2763 if (entry.isIntersecting === (value === "leave")) 2764 return; 2765 evaluate(); 2766 modifiers.includes("once") && observer.disconnect(); 2767 }); 2768 }, options); 2769 observer.observe(el); 2770 cleanup(() => { 2771 observer.disconnect(); 2772 }); 2773 })); 2774 } 2775 function getThreshold(modifiers) { 2776 if (modifiers.includes("full")) 2777 return 0.99; 2778 if (modifiers.includes("half")) 2779 return 0.5; 2780 if (!modifiers.includes("threshold")) 2781 return 0; 2782 let threshold = modifiers[modifiers.indexOf("threshold") + 1]; 2783 if (threshold === "100") 2784 return 1; 2785 if (threshold === "0") 2786 return 0; 2787 return Number(`.${threshold}`); 2788 } 2789 function getLengthValue(rawValue) { 2790 let match = rawValue.match(/^(-?[0-9]+)(px|%)?$/); 2791 return match ? match[1] + (match[2] || "px") : void 0; 2792 } 2793 function getRootMargin(modifiers) { 2794 const key = "margin"; 2795 const fallback = "0px 0px 0px 0px"; 2796 const index = modifiers.indexOf(key); 2797 if (index === -1) 2798 return fallback; 2799 let values = []; 2800 for (let i = 1; i < 5; i++) { 2801 values.push(getLengthValue(modifiers[index + i] || "")); 2802 } 2803 values = values.filter((v) => v !== void 0); 2804 return values.length ? values.join(" ").trim() : fallback; 2805 } 2806 2807 document.addEventListener("alpine:init", () => { 2808 window.Alpine.plugin(src_default); 2809 }); 2810 })(); 2811 //# sourceURL=alpine-intersect.js 2812</script>
2812 2813
2814<script> 2815 let SnapSlider = class { 2816 constructor(el, { 2817 labelSepparator = 'van', 2818 autoPager, 2819 groupPager 2820 } = {}) { 2821 this.el = el; 2822 this.track = this.el.querySelector("[data-track]"); 2823 if (!this.track) { 2824 console.warn( 2825 "No Slider track defined, reverting back to CSS slider.\nPlease create a wrapper for your slides with the attribute data-track", 2826 this.el 2827 ); 2828 return; 2829 } 2830 this.initialLoad = true; 2831 this.slides = []; 2832 this.inViewObserver; 2833 this.mutationObserver; 2834 this.resizeObserver; 2835 this.pager = this.el.querySelector("[data-pager]"); 2836 this.navBtns = Array.from( 2837 this.el.querySelectorAll("[data-next], [data-prev]") 2838 ); 2839 this.slideLabelSepparator = this.el.dataset.slideLabelSepparator || labelSepparator; 2840 this.useAutoPager = autoPager || this.el.hasAttribute("data-auto-pager") || false; 2841 this.useGroupPager = groupPager || this.el.hasAttribute("data-group-pager") || false; 2842 this.sliderLabel = this.el.hasAttribute("aria-label") && this.el.getAttribute("aria-label").toLowerCase().trim().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, ""); 2843 this.sliderId = this.el.id || this.sliderLabel || "slider"; 2844 this.markerIdName = "data-target-id"; 2845 this.pagerClasses = this.el.dataset.pagerClass || "snap-pager"; 2846 this.markerClasses = this.el.dataset.markerClass || "snap-marker"; 2847 this.init(); 2848 } 2849 init() { 2850 this.setupSlides(); 2851 this.setupNav(); 2852 if (this.useAutoPager) { 2853 this.createPager(); 2854 } else { 2855 this.setupPager(); 2856 } 2857 this.setupObservers(); 2858 this.setupMutationObserver(); 2859 this.setupResizeObserver(); 2860 this.el.addEventListener("click", this.eventHandler.bind(this)); 2861 this.el.addEventListener("keydown", this.eventHandler.bind(this)); 2862 } 2863 destroy() { 2864 this.inViewObserver?.disconnect(); 2865 this.mutationObserver?.disconnect(); 2866 this.resizeObserver?.disconnect(); 2867 this.el.removeEventListener("click", this.eventHandler.bind(this)); 2868 this.el.removeEventListener("keydown", this.eventHandler.bind(this)); 2869 } 2870 roundUpIfGreaterThan(number, min = 8) { 2871 const decimalPart = number - Math.floor(number); 2872 return decimalPart >= min ? Math.ceil(number) : Math.floor(number); 2873 } 2874 getInViewItems() { 2875 const inViewSlides = this.track.querySelectorAll("[data-in-view]"); 2876 const firstInViewSlide = inViewSlides[0] || null; 2877 const lastInViewSlide = inViewSlides[inViewSlides.length - 1] || null; 2878 const isAtStart = firstInViewSlide === this.slides[0]; 2879 const isAtEnd = lastInViewSlide === this.slides[this.slides.length - 1]; 2880 return { 2881 inViewSlides, 2882 totalInViewSlides: inViewSlides.length, 2883 firstInViewSlide, 2884 lastInViewSlide, 2885 isAtStart, 2886 isAtEnd, 2887 hasNoOverflow: isAtStart && isAtEnd 2888 }; 2889 } 2890 groupPagerMarkers() { 2891 if (this.slides.length === 0 || !this.pager || !this.useGroupPager) return; 2892 const totalVisibleSlides = this.roundUpIfGreaterThan( 2893 this.track.offsetWidth / this.slides[0].offsetWidth 2894 ); 2895 const markers = Array.from(this.pager.querySelectorAll("a, button")); 2896 markers.forEach((marker, index) => { 2897 marker.style.display = index % totalVisibleSlides === 0 ? null : "none"; 2898 }); 2899 } 2900 handleInView(entries) { 2901 entries.forEach((entry) => { 2902 const marker = this.pager && (this.pager.querySelector(`[href="#${entry.target.id}"]`) || this.pager.querySelector( 2903 `[${this.markerIdName}="${entry.target.id}"]` 2904 )); 2905 entry.target.toggleAttribute("data-in-view", entry.isIntersecting); 2906 entry.target.toggleAttribute("inert", !entry.isIntersecting); 2907 marker?.setAttribute("aria-current", entry.isIntersecting); 2908 marker?.setAttribute("tabindex", entry.isIntersecting ? "0" : "-1"); 2909 }); 2910 const { 2911 isAtStart, 2912 isAtEnd, 2913 hasNoOverflow 2914 } = this.getInViewItems(); 2915 this.navBtns.forEach((btn) => { 2916 if (btn.hasAttribute("data-next")) { 2917 btn.style.visibility = hasNoOverflow ? "hidden" : null; 2918 isAtEnd ? btn.setAttribute("disabled", "") : btn.removeAttribute("disabled"); 2919 } 2920 if (btn.hasAttribute("data-prev")) { 2921 btn.style.visibility = hasNoOverflow ? "hidden" : null; 2922 isAtStart ? btn.setAttribute("disabled", "") : btn.removeAttribute("disabled"); 2923 } 2924 }); 2925 if (this.pager) { 2926 this.pager.style.visibility = hasNoOverflow ? "hidden" : null; 2927 } 2928 if (!this.initialLoad) { 2929 this.el.dispatchEvent( 2930 new CustomEvent("slideChange", { 2931 detail: this.getInViewItems() 2932 }) 2933 ); 2934 } else { 2935 this.initialLoad = false;
2936 } 2937 if (document.activeElement.parentElement.hasAttribute("data-pager")) { 2938 const activeItems = this.pager.querySelectorAll('[tabindex="0"]'); 2939 if (activeItems.length) { 2940 activeItems[0].focus({ preventScroll: true }); 2941 } 2942 } 2943 } 2944 getSlides() { 2945 if (!this.track) return []; 2946 return Array.from(this.track.children).filter( 2947 (child) => child.tagName.toLowerCase() !== "template" && child.tagName.toLowerCase() !== "style" && child.tagName.toLowerCase() !== "script" 2948 ); 2949 } 2950 refreshSlides() { 2951 this.initialLoad = true; 2952 this.slides = this.getSlides(); 2953 this.setupSlides(); 2954 if (this.useAutoPager) { 2955 this.createPager(); 2956 } else { 2957 this.setupPager(); 2958 } 2959 this.groupPagerMarkers(); 2960 this.setupObservers(); 2961 } 2962 setupSlides() { 2963 this.slides = this.getSlides(); 2964 this.slides.forEach((slide, index) => { 2965 const totalSlides = this.slides.length; 2966 const currentSlide = index + 1; 2967 const existingLabel = slide.getAttribute("aria-label") || ""; 2968 const isImage = slide.tagName.toLowerCase() === "img" || slide.tagName.toLowerCase() === "picture"; 2969 const slideLabelTag = isImage ? "alt" : "aria-label"; 2970 const hasAutoLabel = existingLabel.startsWith( 2971 `${currentSlide} ${this.slideLabelSepparator} ` 2972 ); 2973 if (!slide.hasAttribute("id")) { 2974 slide.setAttribute( 2975 "id", 2976 `${this.sliderId}-item-${currentSlide}` 2977 ); 2978 } 2979 if (!slide.hasAttribute(slideLabelTag) || slide.getAttribute(slideLabelTag) === "" || hasAutoLabel) { 2980 slide.setAttribute( 2981 slideLabelTag, 2982 `${currentSlide} ${this.slideLabelSepparator} ${totalSlides}` 2983 ); 2984 } 2985 if (slide.tagName.toLowerCase() === "div" && !slide.hasAttribute("role")) { 2986 slide.setAttribute("role", "group"); 2987 } 2988 if (this.pager && !isImage) { 2989 slide.setAttribute("role", "tabpanel"); 2990 } 2991 if (slide.getAttribute("role") === "group") { 2992 slide.setAttribute("aria-roledescription", "item"); 2993 } 2994 }); 2995 if (!this.el.hasAttribute("role") && this.el.tagName !== "SECTION") { 2996 this.el.setAttribute("role", "region"); 2997 } 2998 if (!this.el.hasAttribute("aria-roledescription")) { 2999 this.el.setAttribute("aria-roledescription", "carousel"); 3000 } 3001 this.track.setAttribute("tabindex", 0); 3002 this.track.setAttribute("aria-live", "polite"); 3003 } 3004 setupNav() { 3005 this.navBtns.forEach((btn) => { 3006 btn.setAttribute("disabled", ""); 3007 btn.removeAttribute("hidden"); 3008 btn.classList.remove("invisible"); 3009 btn.style.visibility = null; 3010 }); 3011 } 3012 createPager() { 3013 const newPager = document.createElement("nav"); 3014 newPager.setAttribute("data-pager", ""); 3015 newPager.setAttribute("role", "tablist"); 3016 newPager.classList.add(...this.pagerClasses.split(" ")); 3017 this.slides.forEach((slide, index) => { 3018 const marker = document.createElement("button"); 3019 const slideId = slide.id || `${this.sliderId}-item-${index + 1}`; 3020 marker.setAttribute(this.markerIdName, slideId); 3021 marker.classList.add(...this.markerClasses.split(" ")); 3022 this.setupPagerMarker(marker, index); 3023 newPager.appendChild(marker); 3024 if (slide.tagName.toLowerCase() !== "img" && slide.tagName.toLowerCase() !== "picture") { 3025 slide.removeAttribute("aria-roledescription"); 3026 slide.setAttribute("role", "tabpanel"); 3027 } 3028 }); 3029 if (this.pager) { 3030 this.pager.setAttribute("role", "tablist"); 3031 this.pager.replaceChildren(...newPager.children); 3032 } else { 3033 this.track.after(newPager); 3034 this.pager = this.el.querySelector("[data-pager]"); 3035 } 3036 } 3037 setupPager() { 3038 if (!this.pager) return; 3039 this.pager.setAttribute("role", "tablist"); 3040 const items = Array.from(this.pager.querySelectorAll("a, button"));
3041 items.forEach((marker, index) => this.setupPagerMarker(marker, index)); 3042 } 3043 setupPagerMarker(marker, index) { 3044 const markerId = marker.getAttribute("href")?.slice(1) || marker.getAttribute(this.markerIdName); 3045 const slideId = markerId || `${this.sliderId}-item-${index + 1}`; 3046 if (!markerId) { 3047 marker.setAttribute(this.markerIdName, slideId); 3048 } 3049 marker.setAttribute("role", "tab"); 3050 marker.setAttribute("aria-controls", slideId); 3051 marker.setAttribute("aria-posinset", index + 1); 3052 marker.setAttribute("aria-setsize", this.slides.length); 3053 marker.setAttribute("tabindex", "-1"); 3054 if (!marker.hasAttribute("aria-label")) { 3055 marker.setAttribute("aria-label", `Slide ${index + 1}`); 3056 } 3057 } 3058 setupObservers() { 3059 if (this.inViewObserver) { 3060 this.inViewObserver.disconnect(); 3061 } 3062 this.inViewObserver = new IntersectionObserver( 3063 this.handleInView.bind(this), { 3064 root: this.track, 3065 threshold: 0.8 3066 } 3067 ); 3068 this.slides.forEach((slide) => this.inViewObserver.observe(slide)); 3069 } 3070 setupMutationObserver() { 3071 this.mutationObserver = new MutationObserver( 3072 this.refreshSlides.bind(this) 3073 ); 3074 this.mutationObserver.observe(this.track, { 3075 childList: true, 3076 subtree: false 3077 }); 3078 } 3079 setupResizeObserver() { 3080 this.resizeObserver = new ResizeObserver( 3081 this.groupPagerMarkers.bind(this) 3082 ); 3083 this.resizeObserver.observe(this.el); 3084 } 3085 goToSlideDir(dir = "next") { 3086 const { 3087 firstInViewSlide, 3088 lastInViewSlide 3089 } = this.getInViewItems(); 3090 const isPrev = dir === "prev"; 3091 let targetSlide = isPrev ? firstInViewSlide?.previousElementSibling : lastInViewSlide?.nextElementSibling; 3092 if (!targetSlide) return; 3093 if (targetSlide.tagName.toLowerCase() === "template") { 3094 targetSlide = isPrev ? targetSlide?.previousElementSibling : targetSlide?.nextElementSibling; 3095 } 3096 targetSlide.scrollIntoView({ block: "nearest", inline: isPrev ? "end" : "start" }); 3097 } 3098 pagerToSlide(event) { 3099 if (!event.target.closest("[data-pager]")) return; 3100 const marker = event.target.closest("a, button"); 3101 if (!marker) return; 3102 event.preventDefault(); 3103 const slideId = marker.getAttribute("href")?.slice(1) || marker.getAttribute(this.markerIdName); 3104 const targetSlide = this.track.querySelector(`#${slideId}`); 3105 targetSlide?.scrollIntoView({ block: "nearest", inline: "start" }); 3106 } 3107 eventHandler(event) { 3108 const target = event.target.closest( 3109 "[data-next], [data-prev], [data-pager]" 3110 ); 3111 if (!target) return; 3112 if (event.type === "click") { 3113 if (target.hasAttribute("data-next")) { 3114 this.goToSlideDir("next"); 3115 } else if (target.hasAttribute("data-prev")) { 3116 this.goToSlideDir("prev"); 3117 } else if (target.closest("[data-pager]")) { 3118 this.pagerToSlide(event); 3119 } 3120 } 3121 if (event.type === "keydown" && target.closest("[data-pager]")) { 3122 if (event.key === "ArrowRight") { 3123 this.goToSlideDir("next"); 3124 } else if (event.key === "ArrowLeft") { 3125 this.goToSlideDir("prev"); 3126 } 3127 } 3128 } 3129 }; 3130 3131 (() => { 3132 function alpineSnapSlider(Alpine) { 3133 Alpine.directive("snap-slider", (el, { 3134 modifiers 3135 }, { 3136 cleanup 3137 }) => { 3138 const autoPager = modifiers.includes("auto-pager");
3139 const groupPager = modifiers.includes("group-pager"); 3140 const slider = new SnapSlider(el, { 3141 autoPager, 3142 groupPager 3143 }); 3144 cleanup(() => { 3145 slider.destroy(); 3146 }); 3147 }); 3148 } 3149 3150 document.addEventListener("alpine:init", () => { 3151 window.Alpine.plugin(alpineSnapSlider); 3152 }); 3153 })(); 3154</script>
3154 3155
3156<script type="module" 3157 src="https://uitgeverijpluim.nl/static/version1790248052/frontend/Pluim/Hyva/nl_NL/Hyva_Theme/js/alpine3.min.js" 3158 defer 3159 crossorigin 3160></script>
vendor: 1 bytes, line 3160
3160
3161<script> 3162 function dispatchMessages(messages, hideAfter) { 3163 const messagesEvent = new CustomEvent("messages-loaded", { 3164 detail: { 3165 messages: messages, 3166 hideAfter: hideAfter 3167 } 3168 }); 3169 window.dispatchEvent(messagesEvent); 3170 } 3171 3172 if (typeof hyva === 'undefined' || (!hyva.getBrowserStorage || !hyva.getCookie || !hyva.setCookie)) { 3173 console.warn("Hyvä helpers are not loaded yet. Make sure they are included before this script"); 3174 } 3175 3176 (function( hyva, undefined ) { 3177 3178 hyva.initFormKey = () => { 3179 if (typeof hyva.getFormKey !== 'function') return; 3180 3181 const formKey = hyva.getFormKey(); 3182 if (!formKey) return; 3183 3184 document.querySelectorAll('input[name="form_key"]').forEach((input) => { 3185 input.value = formKey; 3186 }); 3187 } 3188 3189 hyva.initMessages = () => { 3190 try { 3191 const messages = hyva.getCookie('mage-messages'); 3192 window.mageMessages = messages ? JSON.parse(decodeURIComponent(messages).replace(/\+/g, ' ')) : []; 3193 3194 dispatchMessages(window.mageMessages); 3195 3196 // empty `mage-messages` cookie 3197 const skipSetDomain = true; 3198 hyva.setCookie('mage-messages','', -1, skipSetDomain); 3199 3200 } catch (error) { 3201 console.warn('Error parsing Cookie Messages:', error); 3202 } 3203 } 3204 3205 window.addEventListener('DOMContentLoaded', hyva.initFormKey); 3206 3207 window.addEventListener('pageshow', (event) => { 3208 if (event.persisted) { 3209 hyva.initFormKey(); 3210 } 3211 }); 3212 3213 hyva.alpineInitialized(hyva.initMessages) 3214 }( window.hyva = window.hyva || {} )); 3215 //# sourceURL=hyva-cookies.js 3216</script>
vendor: 1 bytes, line 3216
3216
3217<script> 3218 (() => { 3219 const private_content_key = 'mage-cache-storage'; 3220 const private_content_expire_key = 'mage-cache-timeout'; 3221 const private_content_version_key = 'private_content_version'; 3222 const section_data_ids_key = 'section_data_ids'; 3223 const mage_cache_session_id_key = 'mage-cache-sessid'; 3224 const last_visited_store_key = 'last_visited_store'; 3225 3226 const ttl = 3600; 3227 3228 if (typeof hyva === 'undefined' || (!hyva.getBrowserStorage || !hyva.getCookie || !hyva.setCookie)) { 3229 console.warn("Hyvä helpers are not loaded yet. Make sure they are included before this script"); 3230 } 3231 3232 function loadSectionData () { 3233 const browserStorage = hyva.getBrowserStorage(); 3234 if (!browserStorage) { 3235 typeof window.dispatchMessages !== "undefined" && window.dispatchMessages( 3236 [{ 3237 type: "warning", 3238 text: "Schakel LocalStorage in in je browser." 3239 }] 3240 ); 3241 return; 3242 } 3243 try { 3244 let isInvalid = false; 3245 3246 if (hyva.getCookie(last_visited_store_key) !== CURRENT_STORE_CODE) { 3247 isInvalid = true; 3248 } 3249 hyva.setCookie(last_visited_store_key, CURRENT_STORE_CODE, false, false); 3250 3251 if (!hyva.getCookie(mage_cache_session_id_key)) { 3252 isInvalid = true; 3253 browserStorage.removeItem(private_content_key); 3254 const skipSetDomain = true; 3255 const days = false; 3256 hyva.setCookie(mage_cache_session_id_key, true, days, skipSetDomain) 3257 } 3258 3259 const cookieVersion = hyva.getCookie(private_content_version_key); 3260 const storageVersion = browserStorage.getItem(private_content_version_key); 3261 3262 if (cookieVersion && !storageVersion || cookieVersion !== storageVersion) { 3263 isInvalid = true; 3264 } 3265 3266 const privateContentExpires = browserStorage.getItem(private_content_expire_key); 3267 if (privateContentExpires && new Date(privateContentExpires) < new Date()) { 3268 browserStorage.removeItem(private_content_key); 3269 } 3270 3271 if (isInvalid && cookieVersion) { 3272 fetchPrivateContent([]); 3273 } else if (cookieVersion && storageVersion && cookieVersion === storageVersion) { 3274 const privateContent = JSON.parse(browserStorage.getItem(private_content_key)); 3275 if ( 3276 privateContent && 3277 privateContentExpires && 3278 privateContent.cart && 3279 privateContent.customer 3280 ) { 3281 dispatchPrivateContent(privateContent); 3282 } else { 3283 fetchPrivateContent([]); 3284 } 3285 } else { 3286 if (document.getElementById('default-section-data')) { 3287 const privateContent = JSON.parse(document.getElementById('default-section-data').textContent.trim()); 3288 dispatchPrivateContent(privateContent); 3289 } else { 3290 dispatchPrivateContent({}); 3291 } 3292 } 3293 3294 } catch (error) { 3295 console.warn('Error retrieving Private Content:', error); 3296 } 3297 } 3298 3299 hyva.alpineInitialized(loadSectionData) 3300 window.addEventListener('reload-customer-section-data', loadSectionData); 3301 3302 window.processSectionDataBeforeDispatch = window.processSectionDataBeforeDispatch || (data => data); 3303 3304 function dispatchPrivateContent(data) { 3305 3306 const privateContentEvent = new CustomEvent("private-content-loaded", { 3307 detail: { 3308 data: window.processSectionDataBeforeDispatch(data) 3309 } 3310 }); 3311 window.dispatchEvent(privateContentEvent); 3312 } 3313 3314 function fetchPrivateContent(sections) { 3315 fetch(`${BASE_URL}customer/section/load/?sections=${encodeURIComponent(sections.join(','))}`, { 3316 method: 'GET', 3317 headers: { 3318 'Content-Type': 'application/json', 3319 'X-Requested-With': 'XMLHttpRequest' 3320 } 3321 }) 3322 .then(response => response.ok && response.json()) 3323 .then( 3324 data => { 3325 if (data) { 3326 try { 3327 const browserStorage = hyva.getBrowserStorage(); 3328 3329 // merge new data preserving non-invalidated sections 3330 const oldSectionData = JSON.parse(browserStorage.getItem(private_content_key) || '{}') || {}; 3331 3332 if ((! data.cart || ! data.cart.cartId) && oldSectionData['checkout-data']) { 3333 delete oldSectionData['checkout-data']; 3334 } 3335 const newSectionData = Object.assign(oldSectionData, data); 3336 3337 dispatchPrivateContent(newSectionData); 3338 3339 // don't persist messages, they've been dispatched already 3340 if (newSectionData.messages && newSectionData.messages.messages ) { 3341 newSectionData.messages.messages = []; 3342 } 3343 3344 browserStorage.setItem(private_content_key, JSON.stringify(newSectionData)); 3345 3346 const expiresAt = new Date(Date.now() + (ttl * 1000)).toISOString(); 3347 browserStorage.setItem(private_content_expire_key, expiresAt); 3348 3349 const newCookieVersion = hyva.getCookie(private_content_version_key); 3350 browserStorage.setItem(private_content_version_key, newCookieVersion); 3351 3352 // We don't need the section_data_ids in Hyvä, but we store them for compatibility 3353 // with Luma Fallback. Otherwise, not all sections are loaded in Luma Checkout 3354 hyva.setCookie( 3355 section_data_ids_key, 3356 JSON.stringify( 3357 Object.keys(data).reduce((sectionDataIds, sectionKey) => { 3358 sectionDataIds[sectionKey] = data[sectionKey]['data_id']; 3359 return sectionDataIds; 3360 }, {}) 3361 ), 3362 false, 3363 true 3364 ); 3365 } catch (error) { 3366 console.warn("Couldn't store privateContent", error); 3367 } 3368 } 3369 } 3370 ) 3371 .catch(error => { 3372 console.warn("Couldn't fetch privateContent", error); 3373 }); 3374 } 3375 })(); 3376 //# sourceURL=hyva-private-content.js 3377</script>
vendor: 1 bytes, line 3377
3377
3378<script> 3379 (() => { 3380 document.addEventListener('submit', event => event.target.action = event.target.action.replace('%25uenc%25', hyva.getUenc())); 3381 })() 3382</script>
vendor: 1 bytes, line 3382
3382
3383<script> 3384 (events => { 3385 const dispatchUserInteractionEvent = () => { 3386 events.forEach(type => window.removeEventListener(type, dispatchUserInteractionEvent)) 3387 window.dispatchEvent(new Event('init-external-scripts')) 3388 }; 3389 events.forEach(type => window.addEventListener(type, dispatchUserInteractionEvent, {once: true, passive: true})) 3390 })(['touchstart', 'mousemove', 'wheel', 'scroll', 'keydown']); 3391 //# sourceURL=hyva-init-external-scripts.js 3392</script>
vendor: 1 bytes, line 3392
3392
3393<script> 3394 document.addEventListener("alpine:init", () => { 3395 Alpine.data('hyvaCmsToggle', () => ({ 3396 open: false, 3397 notOpen() { 3398 return !this.open; 3399 }, 3400 show() { 3401 this.open = true 3402 }, 3403 hide() { 3404 this.open = false 3405 }, 3406 toggle() { 3407 this.open = !this.open 3408 } 3409 })) 3410 }); 3411</script>
vendor: 1 bytes, line 3411
3411
3412<script> 3413 function initSmileAttribute(options) { 3414 "use strict"; 3415 3416 return { 3417 options : options, 3418 searchLabel : 'Zoeken (%s)', 3419 showMoreLabel : 'Toon meer', 3420 showLessLabel : 'Toon minder', 3421 noSearchResultLabel : 'Geen waarde die overeenkomt met de zoekopdracht <b>%s</b>.', 3422 expanded: false, 3423 items: [], 3424 lastSelectIndex: 0, 3425 maxSize: 0, 3426 displaySearch: false, 3427 displayedItems: [], 3428 fulltextSearch: null, 3429 searchPlaceholder: '', 3430 result: 0, 3431 3432 /** 3433 * Component initialization 3434 */ 3435 initialize: function () { 3436 3437 this.items = this.options.items.map(this.addItemId.bind(this)); 3438 3439 var lastSelectedIndex = Math.max.apply(null, (this.items.map( 3440 function (v, k) {return v['is_selected'] ? k : 0;})) 3441 ); 3442 this.maxSize = Math.max(this.options.maxSize, lastSelectedIndex + 1); 3443 3444 this.$nextTick(() => { 3445 this.initSearchPlaceholder(); 3446 this.onShowLess(); 3447 this.displaySearch = this.displayShowMore(); 3448 this.displayedItems = this.getDisplayedItems(); 3449 }); 3450 }, 3451 3452 /** 3453 * Init the place holder 3454 */ 3455 initSearchPlaceholder: function () { 3456 var examples = this.items.slice(0, 2).map(function (item) {return item.label}); 3457 3458 if (this.items.length > 2) { 3459 examples.push('...'); 3460 } 3461 3462 // Tip to make html chars interpretable by the browser when the value is get from JS. 3463 let placeholder = document.createElement('div'); 3464 placeholder.innerHTML = this.searchLabel.replace('%s', examples.join(', ')); 3465 3466 this.searchPlaceholder = placeholder.textContent; 3467 }, 3468 3469 /** 3470 * Triggered when typing on the search input 3471 */ 3472 onSearchChange: function (event) { 3473 var text = event.target.value; 3474 if (text.trim() === "") { 3475 this.fulltextSearch = null; 3476 this.onShowLess(); 3477 } else { 3478 this.fulltextSearch = text; 3479 this.onShowMore(); 3480 } 3481 3482 return true; 3483 }, 3484 3485 /** 3486 * Triggered when leaving the search field. 3487 */ 3488 onSearchFocusOut: function(event) { 3489 var text = event.target.value; 3490 if (text.trim() === "") { 3491 this.fulltextSearch = null; 3492 } 3493 }, 3494 3495 /** 3496 * Retrieve additional Results 3497 * 3498 * @param callback 3499 */ 3500 loadAdditionalItems: function (callback) { 3501 fetch(this.options.ajaxLoadUrl, { 3502 method: 'GET', 3503 headers: { 3504 'X-Requested-With': 'XMLHttpRequest', 3505 }, 3506 }) 3507 .then((response) => response.json()) 3508 .then((data) => { 3509 this.items = data.map(this.addItemId.bind(this)); 3510 this.options.hasMoreItems = false; 3511 3512 if (callback) { 3513 return callback(); 3514 } 3515 } 3516 ); 3517 }, 3518 3519 /** 3520 * Retrieve items to display 3521 * 3522 * @returns {*} 3523 */ 3524 getDisplayedItems: function () { 3525 var items = this.items; 3526 3527 if (this.expanded === false) { 3528 items = this.items.slice(0, this.maxSize); 3529 } 3530 3531 if (this.getFulltextSearch()) { 3532 var searchTokens = this.slugify(this.getFulltextSearch()).split('-'); 3533 var lastSearchToken = searchTokens.splice(-1, 1)[0]; 3534 3535 items = items.filter(function(item) { 3536 var isValidItem = true; 3537 var itemTokens = this.slugify(item.label).split('-'); 3538 searchTokens.forEach(function(currentToken) { 3539 if (itemTokens.indexOf(currentToken) === -1) { 3540 isValidItem = false;
3541 } 3542 }) 3543 if (isValidItem && lastSearchToken) { 3544 var ngrams = itemTokens.map(function(token) {return token.substring(0, lastSearchToken.length)}); 3545 isValidItem = ngrams.indexOf(lastSearchToken) !== -1; 3546 } 3547 return isValidItem; 3548 }.bind(this)) 3549 } 3550 this.result = items.length; 3551 return items; 3552 }, 3553 3554 /** 3555 * Does the search have a result 3556 */ 3557 hasSearchResult: function () { 3558 return this.getDisplayedItems().length > 0 3559 }, 3560 3561 /** 3562 * Search result message 3563 */ 3564 getSearchResultMessage : function() { 3565 return this.noSearchResultLabel.replace("%s", '"' + this.getFulltextSearch() + '"') 3566 }, 3567 3568 getFulltextSearch : function() { 3569 return this.fulltextSearch; 3570 }, 3571 3572 /** 3573 * Callback for the "Show more" button 3574 */ 3575 onShowMore: function () { 3576 if (this.options.hasMoreItems) { 3577 this.loadAdditionalItems(this.onShowMore.bind(this)); 3578 } else { 3579 this.expanded = true; 3580 } 3581 3582 this.displayedItems = this.getDisplayedItems(); 3583 }, 3584 3585 /** 3586 * Index the text to be searched. 3587 */ 3588 slugify: function(text) { 3589 return text.toString().toLowerCase() 3590 .replace(/\s+/g, '-') // Replace spaces with - 3591 .replace(/[^\w\u0400-\u052F\u2DE0-\u2DFF\uA640-\uA69F'\-]+/g, '') // Remove all non-word chars 3592 .replace(/\-\-+/g, '-') // Replace multiple - with single - 3593 .replace(/^-+/, '') // Trim - from start of text 3594 }, 3595 3596 /** 3597 * Callback for the "Show less" button 3598 */ 3599 onShowLess: function () { 3600 this.expanded = false; 3601 this.displayedItems = this.getDisplayedItems(); 3602 }, 3603 3604 /** 3605 * Check if the filter can be expanded 3606 * 3607 * @returns {boolean} 3608 */ 3609 enableExpansion : function () { 3610 return this.options.hasMoreItems || this.items.length > this.maxSize; 3611 }, 3612 3613 /** 3614 * Displays the "Show More" link 3615 * 3616 * @returns {*|boolean} 3617 */ 3618 displayShowMore: function () { 3619 return this.enableExpansion() && this.expanded === false && !this.getFulltextSearch(); 3620 }, 3621 3622 /** 3623 * Displays the "Show Less" link 3624 * 3625 * @returns {*|boolean} 3626 */ 3627 displayShowLess: function () { 3628 return this.enableExpansion() && this.expanded === true && !this.getFulltextSearch(); 3629 }, 3630 3631 /** 3632 * Add an id to items. 3633 */ 3634 addItemId: function (item) { 3635 item.id = Math.floor(Math.random() * 1000000) + "_option_"; 3636 item.displayProductCount = this.options.displayProductCount && (item.count >= 1); 3637 3638 return item; 3639 }, 3640 }; 3641 }; 3642</script>
vendor: 1 bytes, line 3642
3642
3643<script> 3644 function rangeSlider(options, isPriceSlider) { 3645 "use strict"; 3646 3647 return { 3648 options: options, 3649 priceSlider: isPriceSlider, 3650 lastThumbUpdatedMax: true, 3651 minfocused: false, 3652 maxfocused: false, 3653 sliderZindex: 20, 3654 messageTemplates: { 3655 "displayOne": '<span class="msg">1 product</span>', 3656 "displayCount": '<span class="msg"><%- count %> producten</span>', 3657 "displayEmpty": '<span class="msg-error">No items in the current range.</span>' 3658 }, 3659 minprice: 0, 3660 maxprice: 0, 3661 fromLabel: '', 3662 toLabel: '', 3663 min: 0, 3664 max: 0, 3665 minZindex: 0, 3666 maxZindex: 0, 3667 minthumb: 0, 3668 maxthumb: 0, 3669 message: '', 3670 url: '', 3671 showAdaptiveSlider: false, 3672 intervals: [], 3673 3674 initialize: function () { 3675 this.options = Object.assign({}, this.options, options); 3676 this.url = this.options.urlTemplate; 3677 3678 if (this.options.sliderZindex !== undefined) { 3679 this.sliderZindex = this.options.sliderZindex; 3680 } 3681 3682 this.showAdaptiveSlider = false; 3683 if (this.options.showAdaptiveSlider !== undefined) { 3684 this.showAdaptiveSlider = this.options.showAdaptiveSlider; 3685 } 3686 3687 if (this.options.maxLabelOffset === undefined) { 3688 this.options.maxLabelOffset = 0.01; 3689 } 3690 3691 if (this.options.rate === undefined) { 3692 this.options.rate = 1.0000; 3693 } 3694 3695 if (this.showAdaptiveSlider) { 3696 this._initAdaptiveSliderValues(); 3697 } else { 3698 this._initSliderValues(); 3699 } 3700 3701 this.mintrigger(); 3702 this.maxtrigger(); 3703 }, 3704 _initSliderValues: function () { 3705 this.rate = parseFloat(this.options.rate); 3706 this.minprice = Math.floor(this.options.currentValue.from * this.rate); 3707 this.maxprice = Math.round(this.options.currentValue.to * this.rate); 3708 this.intervals = this.options.intervals.map( 3709 function(item) { item.value = Math.round(item.value * this.rate); return item}.bind(this) 3710 ); 3711 this.min = Math.floor(this.options.minValue * this.rate); 3712 this.max = Math.round(this.options.maxValue * this.rate); 3713 }, 3714 3715 _initAdaptiveSliderValues: function () { 3716 this.intervals = this.options.adaptiveIntervals; 3717 this.options.adaptiveIntervals[0].value = Math.floor(this.options.adaptiveIntervals[0].value); 3718 this.minprice = this._getAdaptiveValue(Number(this.options.currentValue.from)); 3719 this.maxprice = this._getAdaptiveValue(Number(this.options.currentValue.to)); 3720 this.rate = parseFloat(this.options.rate); 3721 this.intervals = this.intervals.map( 3722 function(item) { item.originalValue = Math.ceil(item.originalValue * this.rate); return item}.bind(this) 3723 ); 3724 this.min = this.intervals[0].value; 3725 this.max = this.intervals[this.intervals.length - 1].value; 3726 }, 3727 mintrigger: function () { 3728 this.lastThumbUpdatedMax = false; 3729 this.validation(); 3730 this.minprice = this._getClosestAdaptiveValue(Math.min(this.minprice, this.maxprice)); 3731 this.minthumb = ((this.minprice - this.min) / (this.max - this.min)) * 100; 3732 this._onChangeAfter(); 3733 }, 3734 maxtrigger: function () { 3735 this.lastThumbUpdatedMax = true; 3736 this.validation(); 3737 this.maxprice = this._getClosestAdaptiveValue(Math.max(this.maxprice, this.minprice)); 3738 this.maxthumb = ((this.maxprice - this.min) / (this.max - this.min)) * 100; 3739 this._onChangeAfter(); 3740 }, 3741 updateFocusState: function (targetName, stateValue) { 3742 this[`${targetName}focused`] = stateValue 3743 }, 3744 _onChangeAfter: function () {
3745 this._updateSliderZindexes(); 3746 this._applyRange(); 3747 this._refreshDisplay(); 3748 }, 3749 validation: function () { 3750 if (/^[\d.]*$/.test(this.minprice)) { 3751 if (this.minprice > this.max) { 3752 this.minprice = this._getAdaptiveValue(Number(this.options.currentValue.from)); 3753 } 3754 if (this.minprice < this.min) { 3755 this.minprice = this.min; 3756 } 3757 } else { 3758 this.minprice = this._getAdaptiveValue(Number(this.options.currentValue.from)); 3759 } 3760 if (/^[\d.]*$/.test(this.maxprice)) { 3761 if (this.maxprice > this.max) { 3762 this.maxprice = this.max; 3763 } 3764 if (this.maxprice < this.min) { 3765 this.maxprice = this._getAdaptiveValue(Number(this.options.currentValue.to)); 3766 } 3767 } else { 3768 this.maxprice = this._getAdaptiveValue(Number(this.options.currentValue.to)) 3769 } 3770 }, 3771 getItemCount: function () { 3772 var from = this.minprice, to = this.maxprice, intervals = this.intervals; 3773 return intervals.map(function (item) { 3774 return item.value >= from && item.value < to ? item.count : 0; 3775 }) 3776 .reduce(function (a, b) { 3777 return a + b; 3778 }); 3779 }, 3780 _refreshDisplay: function () { 3781 this.count = this.getItemCount(); 3782 let minprice = this._getOriginalValue(this.minprice); 3783 if (minprice >= this._getOriginalValue(this.max) ) { 3784 minprice = this._getOriginalValue(this.max) - this.options.maxLabelOffset; 3785 } 3786 this.fromLabel = this._formatLabel(minprice); 3787 3788 let maxprice = this._getOriginalValue(this.maxprice) - this.options.maxLabelOffset; 3789 if (maxprice < this._getOriginalValue(this.min)) { 3790 maxprice = this._getOriginalValue(this.maxprice); 3791 } 3792 this.toLabel = this._formatLabel(maxprice); 3793 3794 var messageTemplate = this.messageTemplates[this.count > 0 ? (this.count > 1 ? 'displayCount' : 'displayOne') : 'displayEmpty']; 3795 this.message = this._unescape(messageTemplate.replace('<%- count %>', this.count)); 3796 }, 3797 _formatLabel: function (value) { 3798 var formattedValue = value; 3799 3800 if (this.priceSlider && this.options.fieldFormat) { 3801 formattedValue = this._formatPrice(value); 3802 } else if (this.options.fieldFormat) { 3803 var pattern = this.options.fieldFormat.pattern || '%s'; 3804 formattedValue = pattern.replace( 3805 '%s', 3806 Number.parseFloat(formattedValue).toFixed(this.options.fieldFormat.precision) 3807 ); 3808 } 3809 3810 return formattedValue; 3811 }, 3812 _formatPrice: function (value) { 3813 var formatter = new Intl.NumberFormat( 3814 document.documentElement.lang, 3815 { 3816 style: 'currency', 3817 currency: 'EUR', 3818 } 3819 ); 3820 3821 return formatter.format(value); 3822 }, 3823 _applyRange: function () { 3824 var range = { 3825 from : this._getOriginalValue(this.minprice) * (1 / this.rate), 3826 to : this._getOriginalValue(this.maxprice) * (1 / this.rate), 3827 }; 3828 3829 this.url = this._unescape(this.options.urlTemplate.replace('<%- from %>', range.from).replace('<%- to %>', range.to)); 3830 }, 3831 _updateSliderZindexes: function () { 3832 this.minZindex = this.sliderZindex; 3833 this.maxZindex = this.sliderZindex; 3834 if (this.minprice == this.maxprice) { 3835 if (this.minprice === this.min || (this.minprice !== this.min && this.lastThumbUpdatedMax)) { 3836 this.maxZindex = this.sliderZindex + 1; 3837 } 3838 3839 if (this.maxprice === this.max || (this.maxprice !== this.max && !this.lastThumbUpdatedMax)) { 3840 this.minZindex = this.sliderZindex + 1; 3841 } 3842 3843 } 3844 }, 3845 _getAdaptiveValue : function (value) { 3846 if (!this.showAdaptiveSlider) { 3847 return value; 3848 } 3849 3850 var adaptiveValue = this.intervals[0].value; 3851 var found = false;
3852 this.intervals.forEach(function (item) { 3853 if (found === false && item.originalValue === value) { 3854 adaptiveValue = item.value; 3855 found = true; 3856 } 3857 3858 if (found === false && item.originalValue < value) { 3859 adaptiveValue = item.value; 3860 } 3861 }); 3862 3863 return adaptiveValue; 3864 }, 3865 3866 _getClosestAdaptiveValue : function (value) { 3867 if (!this.showAdaptiveSlider) { 3868 return value; 3869 } 3870 3871 var closestValue = this.intervals[0].value; 3872 var found = false; 3873 this.intervals.forEach(function (item) { 3874 if (item.value === value) { 3875 closestValue = value; 3876 found = true; 3877 } 3878 3879 if (found === false && item.value < value) { 3880 closestValue = item.value; 3881 } 3882 }); 3883 3884 return closestValue; 3885 }, 3886 3887 _getOriginalValue : function (value) { 3888 if (!this.showAdaptiveSlider) { 3889 return value; 3890 } 3891 3892 var originalValue = null; 3893 this.intervals.forEach(function (item) { 3894 if (item.value === value) { 3895 originalValue = item.originalValue; 3896 } 3897 }); 3898 3899 return originalValue; 3900 }, 3901 3902 _unescape: function (str) { 3903 return str.replace(/<%|%3C%/g, '<%').replace(/%>|%%3E/g, '%>'); 3904 }, 3905 } 3906 } 3907</script>
vendor: 1 bytes, line 3907
3907
3908<script type="text/json" id="default-section-data"> 3909 {"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":1790277018},"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 3909
3909
3910<script> 3911 window.processSectionDataBeforeDispatch && (window.processSectionDataBeforeDispatch = new Proxy(window.processSectionDataBeforeDispatch, { 3912 apply(target, thisArg, [data]) { 3913 data.hyva_commerce_messages = data.hyva_commerce_messages || {}; 3914 return target.call(thisArg, data); 3915 } 3916 })); 3917</script>
vendor: 1 bytes, line 3917
3917
3918<script>if (!window.__rumvisionSkip) { 3919 window.rumv = window.rumv || function() { (window.rumv.q = window.rumv.q || []).push(arguments) }; 3920 (function(rum, vi,si,on) { 3921 var s = JSON.parse( sessionStorage.getItem('rumv') || '{"pageviews":0}' ); s.pageviews++; 3922 if ( s.urls && s.regex && ( s.page = eval('('+s.regex+')')( s.urls, vi.location.pathname ) ) && !s.page.type ) { 3923 return sessionStorage.setItem('rumv', JSON.stringify( s ) ); 3924 } 3925 3926 vi.rumv.storage = s; 3927 var head = si.querySelector('head'), js = si.createElement('script'); 3928 js.src = 'https://d5yoctgpv4cpx.cloudfront.net/'+rum+'/v5-'+vi.location.hostname+'.js'; 3929 head.appendChild(js); 3930 })( 'RUM\u002D409605D6E0', window, document, 'uitgeverijpluim.nl' ); 3931}</script>
3931</div>
3931<script nonce="ZTc3MHNqenIwcWZ2NnRtNHpuY2VjbnV2MDhwbWRtMDI="> 3932 yireoGoogleTagManager2Pusher({"version":"3.10.7","page_type":"category","page_path":"https:\/\/uitgeverijpluim.nl\/boeken","page_title":"Boeken | Uitgeverij Pluim","virtual_page":"\/boeken","breadcrumb":["Boeken"],"user":{"has_transacted":false},"category_size":12,"products":[{"item_id":"De dichter en de duivel - T-shirt","item_sku":"De dichter en de duivel - T-shirt","magento_sku":"De dichter en de duivel - T-shirt","magento_id":"890","item_name":"De dichter en de duivel - T-shirt","item_list_id":"3","item_list_name":"Boeken","price":25,"item_category":"Boeken","item_category2":"Po\u00ebzie","index":1,"quantity":1},{"item_id":"Wij komen van de werkplaats","item_sku":"Wij komen van de werkplaats","magento_sku":"Wij komen van de werkplaats","magento_id":"917","item_name":"Wij komen van de werkplaats","item_list_id":"3","item_list_name":"Boeken","price":21.99,"item_category":"Boeken","item_category2":"Verwacht","index":2,"quantity":1},{"item_id":"Zwijgen is zilver. Kunst en activisme","item_sku":"Zwijgen is zilver. Kunst en activisme","magento_sku":"Zwijgen is zilver. Kunst en activisme","magento_id":"914","item_name":"Zwijgen is zilver. Kunst en activisme","item_list_id":"3","item_list_name":"Boeken","price":21.99,"item_category":"Boeken","item_category2":"Verwacht","index":3,"quantity":1}],"category_id":"3","category_name":"Boeken"}, 'push (initial page) [data-layer.phtml]'); 3933 yireoGoogleTagManager2Pusher({"event":"view_item_list","ecommerce":{"items":[{"item_id":"De dichter en de duivel - T-shirt","item_sku":"De dichter en de duivel - T-shirt","magento_sku":"De dichter en de duivel - T-shirt","magento_id":"890","item_name":"De dichter en de duivel - T-shirt","item_list_id":"3","item_list_name":"Boeken","price":25,"item_category":"Boeken","item_category2":"Po\u00ebzie","index":1,"quantity":1},{"item_id":"Wij komen van de werkplaats","item_sku":"Wij komen van de werkplaats","magento_sku":"Wij komen van de werkplaats","magento_id":"917","item_name":"Wij komen van de werkplaats","item_list_id":"3","item_list_name":"Boeken","price":21.99,"item_category":"Boeken","item_category2":"Verwacht","index":2,"quantity":1},{"item_id":"Zwijgen is zilver. Kunst en activisme","item_sku":"Zwijgen is zilver. Kunst en activisme","magento_sku":"Zwijgen is zilver. Kunst en activisme","magento_id":"914","item_name":"Zwijgen is zilver. Kunst en activisme","item_list_id":"3","item_list_name":"Boeken","price":21.99,"item_category":"Boeken","item_category2":"Verwacht","index":3,"quantity":1}]}}, 'push (page-based event) [data-layer.phtml]'); 3934 </script>
vendor: 1 bytes, line 3934
3934
3935<script nonce="ZTc3MHNqenIwcWZ2NnRtNHpuY2VjbnV2MDhwbWRtMDI="> 3936 function yireoGoogleTagManager2FindParentElementWithName(element, parentTagName) { 3937 if (!element.parentElement) { 3938 return null; 3939 } 3940 3941 if (element.parentElement.tagName.toLowerCase() === parentTagName) { 3942 return element.parentElement; 3943 } 3944 3945 return yireoGoogleTagManager2FindParentElementWithName(element.parentElement, parentTagName); 3946 } 3947 3948 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'); 3949 if (products && products.length > 0) { 3950 products.forEach(function(product) { 3951 product.addEventListener('click', function(event, s) { 3952 let parentElement = yireoGoogleTagManager2FindParentElementWithName(event.target, 'form'); 3953 if (!parentElement) { 3954 return; 3955 } 3956 3957 const inputElement = parentElement.querySelector('input[name="product"]'); 3958 const productId = inputElement.value; 3959 const productData = window['YIREO_GOOGLETAGMANAGER2_PRODUCT_DATA_ID_' + productId] || {}; 3960 productData.item_id = productId; 3961 3962 const eventData = { 3963 'event': 'select_item', 3964 'ecommerce': { 3965 'items': [productData] 3966 } 3967 } 3968 3969 yireoGoogleTagManager2Pusher(eventData, 'push (page event "select_item") [script-product-clicks.phtml]'); 3970 3971 if (window['YIREO_GOOGLETAGMANAGER2_DEBUG_CLICKS'] && confirm("Press to continue with redirect") === false) { 3972 event.preventDefault(); 3973 } 3974 }); 3975 }) 3976 } 3977</script>
vendor: 1 bytes, line 3977
3977
3978<script nonce="ZTc3MHNqenIwcWZ2NnRtNHpuY2VjbnV2MDhwbWRtMDI="> 3979 window.addEventListener('private-content-loaded', function(event) { 3980 window.dataLayer = window.dataLayer || []; 3981 3982 var isEmpty = function (variable) { 3983 if (typeof variable === 'undefined') { 3984 return true; 3985 } 3986 3987 if (Array.isArray(variable) && variable.length === 0) { 3988 return true; 3989 } 3990 3991 return typeof variable === 'object' && Object.keys(variable).length === 0; 3992 } 3993 3994 var getSectionNames = function () { 3995 return ['cart', 'customer']; 3996 } 3997 3998 getSectionNames().forEach(function (sectionName) { 3999 if (!event.detail.data[sectionName].gtm) { 4000 return; 4001 } 4002 4003 const attributes = event.detail.data[sectionName].gtm; 4004 if (!Object.keys(attributes).length > 0) { 4005 return; 4006 } 4007 4008 yireoGoogleTagManager2Pusher(attributes, 'push (attributes) [script-additions.phtml]'); 4009 }); 4010 4011 let attributes = {}; 4012 getSectionNames().forEach(function (sectionName) { 4013 if (!event.detail.data[sectionName].gtm_events) { 4014 return; 4015 } 4016 4017 const gtmEvents = event.detail.data[sectionName].gtm_events; 4018 for (const [eventId, eventData] of Object.entries(gtmEvents)) { 4019 yireoGoogleTagManager2Logger('found event in section "' + sectionName + '"', eventData); 4020 4021 const metaData = eventData.meta; 4022 yireoGoogleTagManager2Pusher(eventData, 'push (customerData "' + sectionName + '" event "' + eventId + '")'); 4023 4024 if (!metaData || metaData.cacheable !== true) { 4025 yireoGoogleTagManager2Logger('removing event "' + eventId + '" from section "' + sectionName + '"') 4026 4027 let mageCacheStorage = event.detail.data; 4028 4029 if (mageCacheStorage !== null 4030 && typeof mageCacheStorage[sectionName] !== 'undefined' 4031 && typeof mageCacheStorage[sectionName].gtm_events !== 'undefined' 4032 && eventId in mageCacheStorage[sectionName].gtm_events) { 4033 delete mageCacheStorage[sectionName].gtm_events[eventId]; 4034 } 4035 4036 window.localStorage.setItem('mage-cache-storage', JSON.stringify(mageCacheStorage)); 4037 } 4038 } 4039 }); 4040 4041 if (false === isEmpty(attributes)) { 4042 yireoGoogleTagManager2Pusher(attributes, 'dataLayer (initial js) [script-additions.phtml]'); 4043 } 4044 }); 4045 4046 window.addEventListener('toggle-cart', (event) => { 4047 if (!event.detail || !event.detail.isOpen) { 4048 return; 4049 } 4050 4051 window.dispatchEvent(new CustomEvent('minicart_collapse')); 4052 }); 4053</script>
4053 4054</body> 4055</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.