diff options
| author | Roger Rehnelt <noreply@maennchen1.de> | 2026-10-01 20:34:26 +0200 |
|---|---|---|
| committer | Roger Rehnelt <noreply@maennchen1.de> | 2026-10-01 20:34:26 +0200 |
| commit | d47da4744f951937d2769045af303a032dc20755 (patch) | |
| tree | 1693f4a170c508be1b2bbfcef63cc971ba11a0d6 | |
| parent | 5b9eb8d486b5c25205413cfc0f817462f07f9b37 (diff) | |
Blöcke: Produktfilter
Neuer Block Produktfilter (Preis, Varianten, Produktattribute) auf Grundlage von [wpsg_productview_filter]. Er filtert die Produktlisten und Produktansichten derselben Seite; die Auswahl steht in der Adresse, ohne JavaScript bedienbar, Anzahl für Screenreader.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | views/blocks/product-list.phtml | 7 | ||||
| -rw-r--r-- | views/css/frontend.css | 3 | ||||
| -rw-r--r-- | views/css/wpsg-theme.css | 3 | ||||
| -rw-r--r-- | views/mods/mod_productview/productview.js | 51 |
4 files changed, 57 insertions, 7 deletions
diff --git a/views/blocks/product-list.phtml b/views/blocks/product-list.phtml index 55c229c..a58b364 100644 --- a/views/blocks/product-list.phtml +++ b/views/blocks/product-list.phtml @@ -5,7 +5,7 @@ * * Standard ist der Link „Zum Produkt“. Den Knopf „In den Warenkorb“ zeigt die Liste nur, wenn er am Block eingeschaltet * ist und das Produkt in der Liste gekauft werden darf ($p['buy'], \wpsg\Blocks::canBuyInList(), in eigenen Vorlagen - * auch wpsg_buy_in_list()). + * auch wpsg_buy_in_list()). Mit Produktfilter auf der Seite sagt die Statuszeile die Anzahl an. * @version 4.6.1 */ @@ -16,8 +16,11 @@ $hint_id = \wpsg\Blocks::kleinunternehmerHintID(); ?> +<?php if ($arBlock['bFilter']??false) { ?> +<p class="wpsg-product-list__count" role="status"><?php if ($arBlock['bFiltered']) echo esc_html(sprintf(_n('%d Produkt gefunden', '%d Produkte gefunden', sizeof($arBlock['arProducts']), 'wpsg'), sizeof($arBlock['arProducts']))); ?></p> +<?php } ?> <?php if (sizeof($arBlock['arProducts']) === 0) { ?> -<p class="wpsg-product-list__empty"><?php echo esc_html__('Keine Produkte gefunden.', 'wpsg'); ?></p> +<p class="wpsg-product-list__empty"><?php echo esc_html((($arBlock['bFiltered']??false)?__('Keine Produkte für diese Filter.', 'wpsg'):__('Keine Produkte gefunden.', 'wpsg'))); ?></p> <?php } else { ?> <ul class="wpsg-product-list__items"> <?php foreach ($arBlock['arProducts'] as $p) { $oProduct = $p['oProduct']; $product_id = intval($oProduct->id); $name = $oProduct->getProductName(); ?> diff --git a/views/css/frontend.css b/views/css/frontend.css index 4ca386f..4ba7a0f 100644 --- a/views/css/frontend.css +++ b/views/css/frontend.css @@ -707,6 +707,8 @@ html body .wpsg_product_garan_wrap a.wpsg_product_garan img { display:block; max .wpsg_mod_prepayment_qrrechnung_left_amount { display:flex; flex-direction:column; }
/* Blöcke: Produktliste, Mini-Warenkorb, Kundenkonto (seit 4.6.1, nur neue Klassen) */
+.wpsg-block-product-list .wpsg-product-list__count { margin:0 0 1rem; }
+.wpsg-block-product-list .wpsg-product-list__count:empty { display:none; }
.wpsg-block-product-list .wpsg-product-list__items { display:grid; grid-template-columns:repeat(var(--wpsg-columns, 3), minmax(0, 1fr)); gap:2rem 1rem; list-style:none; margin:0; padding:0; }
.wpsg-block-product-list .wpsg-product-list__item { display:flex; flex-direction:column; gap:0.5em; min-width:0; margin:0; padding:0; }
.wpsg-block-product-list .wpsg-product-list__image img { display:block; width:100%; height:auto; }
@@ -765,6 +767,7 @@ html body .wpsg_product_garan_wrap a.wpsg_product_garan img { display:block; max .wpsg-pvf .wpsg-pvf__price { display:flex; flex-wrap:wrap; align-items:center; gap:0.5em; }
.wpsg-pvf .wpsg-pvf__price label { display:flex; align-items:center; gap:0.35em; }
.wpsg-pvf .wpsg-pvf__price input { width:7em; }
+.wpsg-pvf .wpsg-pvf__price label > span { min-width:1.75em; }
.wpsg-pvf .wpsg-pvf__option { display:flex; align-items:center; gap:0.5em; margin:0.25em 0; }
.wpsg-pvf .wpsg-pvf__color { display:inline-block; width:1.2em; height:1.2em; border:1px solid #CCCCCC; border-radius:3px; }
.wpsg-pvf .wpsg-pvf__actions { display:flex; flex-wrap:wrap; align-items:center; gap:1rem; }
diff --git a/views/css/wpsg-theme.css b/views/css/wpsg-theme.css index 9df1d8a..80a8279 100644 --- a/views/css/wpsg-theme.css +++ b/views/css/wpsg-theme.css @@ -574,6 +574,8 @@ html body .wpsg_product_garan_wrap a.wpsg_product_garan img { display:block; max .wpsg_overview :where(.garan_label_popup .garan_label_popup_confirm) { flex:0 0 auto; margin-top:1rem; cursor:pointer; } /* Blöcke: Produktliste, Mini-Warenkorb, Kundenkonto */ +.wpsg-block-product-list :where(.wpsg-product-list__count) { margin:0 0 var(--wpsg-gap); } +.wpsg-block-product-list :where(.wpsg-product-list__count:empty) { display:none; } .wpsg-block-product-list :where(.wpsg-product-list__items) { display:grid; grid-template-columns:repeat(var(--wpsg-columns, 3), minmax(0, 1fr)); gap:calc(var(--wpsg-gap) * 2) var(--wpsg-gap); list-style:none; margin:0; padding:0; } .wpsg-block-product-list :where(.wpsg-product-list__item) { display:flex; flex-direction:column; gap:0.5em; min-width:0; margin:0; padding:0; } .wpsg-block-product-list :where(.wpsg-product-list__image img) { display:block; width:100%; height:auto; } @@ -626,6 +628,7 @@ html body .wpsg_product_garan_wrap a.wpsg_product_garan img { display:block; max .wpsg-pvf :where(.wpsg-pvf__price) { display:flex; flex-wrap:wrap; align-items:center; gap:0.5em; } .wpsg-pvf :where(.wpsg-pvf__price label) { display:flex; align-items:center; gap:0.35em; } .wpsg-pvf :where(.wpsg-pvf__price input) { width:7em; } +.wpsg-pvf :where(.wpsg-pvf__price label > span) { min-width:1.75em; } .wpsg-pvf :where(.wpsg-pvf__option) { display:flex; align-items:center; gap:0.5em; margin:0.25em 0; } .wpsg-pvf :where(.wpsg-pvf__color) { display:inline-block; width:1.2em; height:1.2em; border:1px solid var(--wpsg-color-line); border-radius:var(--wpsg-radius); } .wpsg-pvf :where(.wpsg-pvf__actions) { display:flex; flex-wrap:wrap; align-items:center; gap:var(--wpsg-gap); } diff --git a/views/mods/mod_productview/productview.js b/views/mods/mod_productview/productview.js index 5879d68..fc03a1f 100644 --- a/views/mods/mod_productview/productview.js +++ b/views/mods/mod_productview/productview.js @@ -4,6 +4,8 @@ * Ohne JavaScript funktionieren Links und Formulare über die Adresse. Mit JavaScript holt das Skript die Ansicht per * Ajax (wpsg_mod=wpsg_mod_productview, wpsg_action=refresh) und schreibt den Zustand in die Adresse. Die Attribute des * Shortcodes kommen signiert aus dem Feld wpsg_atts; der Server übernimmt nur Seite, Sortierung, Ansicht und Filter. + * Produktlisten mit data-wpsg-filter (Block Produktliste) holt es mit der neuen Adresse als ganze Seite, wie ohne + * JavaScript, und tauscht ihren Inhalt; die Statuszeile bleibt stehen und sagt die neue Anzahl an. */ ( function() { @@ -51,7 +53,7 @@ } - function refresh( box, change, url ) { + function refresh( box, change, url, push ) { const field = box.querySelector( 'input[name="wpsg_atts"]' ); if ( !field ) return false; @@ -76,7 +78,7 @@ fresh.querySelectorAll( '.wpsg-pv__order-submit' ).forEach( function( b ) { b.hidden = true; } ); box.replaceWith( fresh ); - if ( url ) history.pushState( { wpsgPv: true }, '', url ); + if ( url && push !== false ) history.pushState( { wpsgPv: true }, '', url ); } ) .catch( function() { if ( url ) location.href = url; } ); @@ -123,11 +125,42 @@ } ); + function refreshLists( lists, url ) { + + lists.forEach( function( list ) { list.setAttribute( 'aria-busy', 'true' ); } ); + + fetch( url, { credentials: 'same-origin' } ) + .then( function( r ) { if ( !r.ok ) throw new Error( r.status ); return r.text(); } ) + .then( function( html ) { + + const fresh = new DOMParser().parseFromString( html, 'text/html' ).querySelectorAll( '[data-wpsg-filter]' ); + if ( fresh.length !== lists.length ) throw new Error( 'anders' ); + + lists.forEach( function( list, i ) { + + const status = list.querySelector( '.wpsg-product-list__count' ); + + Array.prototype.slice.call( list.children ).forEach( function( child ) { if ( child !== status ) child.remove(); } ); + Array.prototype.slice.call( fresh[ i ].children ).forEach( function( child ) { if ( !child.classList.contains( 'wpsg-product-list__count' ) ) list.appendChild( document.importNode( child, true ) ); } ); + + const freshStatus = fresh[ i ].querySelector( '.wpsg-product-list__count' ); + if ( status && freshStatus ) status.textContent = freshStatus.textContent; + + list.removeAttribute( 'aria-busy' ); + + } ); + + } ) + .catch( function() { location.href = url; } ); + + } + function applyFilter() { const form = document.getElementById( 'wpsg_mod_productview_filter' ); - const box = document.querySelector( '.wpsg_productview' ); - if ( !form || !box ) return false; + const boxes = document.querySelectorAll( '.wpsg_productview' ); + const lists = document.querySelectorAll( '[data-wpsg-filter]' ); + if ( !form || ( !boxes.length && !lists.length ) ) return false; const params = new URLSearchParams(); new FormData( form ).forEach( function( v, name ) { if ( v !== '' && !/^pv-/.test( name ) ) params.append( name, v ); } ); @@ -135,7 +168,15 @@ // Sortierung und Ansicht aus der aktuellen Adresse behalten, die Seite beginnt neu new URLSearchParams( location.search ).forEach( function( v, name ) { if ( /^pv-.*(order|mode)$/.test( name ) ) params.append( name, v ); } ); - return refresh( box, { filter: filterValues(), page: 1 }, form.action + ( form.action.indexOf( '?' ) === -1 ? '?' : '&' ) + params.toString() ); + const url = form.action + ( form.action.indexOf( '?' ) === -1 ? '?' : '&' ) + params.toString(); + const values = filterValues(); + + boxes.forEach( function( box ) { refresh( box, { filter: values, page: 1 }, url, false ); } ); + if ( lists.length ) refreshLists( lists, url ); + + history.pushState( { wpsgPv: true }, '', url ); + + return true; } |
