> ## Documentation Index
> Fetch the complete documentation index at: https://dokumentation.websale.de/llms.txt
> Use this file to discover all available pages before exploring further.

# Menge im Warenkorb ändern

> Plus- und Minusschaltflächen direkt auf der Warenkorbseite anbieten, über ein je Position nachgeladenes verstecktes Produktformular.

Um die Menge eines Produkts im Warenkorb zu ändern, muss der Käufer normalerweise zurück auf die Produktdetailansicht. Mit Plus- und Minusschaltflächen direkt in der Warenkorbzeile geht das in einem Schritt.

![Hmfile Hash 9b280d3d](https://mintcdn.com/websaleag-44ee7ea6/HQq41690ljopUotf/images/hmfile_hash_9b280d3d.jpg?fit=max\&auto=format\&n=HQq41690ljopUotf\&q=85\&s=ddc10b8e26b8d1db2d0748d06913952a)

## Warum ein eigenes Formular nötig ist

Die Warenkorbseite kann eine Menge nicht selbst ändern. Zuständig dafür ist das Produktformular, dasselbe, mit dem ein Produkt in den Warenkorb gelegt wird. Es erwartet das Feld `PR-Quantity_Input` und gehört zur Produktdetailansicht, nicht zum Warenkorb.

Die Lösung besteht deshalb aus zwei Formularen je Warenkorbzeile:

| Teil | Wo er steht | Aufgabe |
| - | - | - |
| sichtbare Bedienelemente | in der Warenkorbzeile | der Käufer klickt auf Plus oder Minus oder tippt eine Zahl |
| verstecktes Produktformular | in einem unsichtbaren Bereich der Seite | nimmt den Wert auf und sendet ihn an den Shop |

Das versteckte Formular steht nicht von Anfang an auf der Seite. Es wird erst beim ersten Antippen der Bedienelemente für diese eine Position nachgeladen, über `BASKET-PR-ChangeLink` und ein eigenes kleines Template. Danach spiegelt das Skript jede Änderung in dieses Formular und sendet es ab. Die Antwort lädt den gesamten Warenkorb neu.

> [!WARNING]
>
> **Jeder Klick auf Plus oder Minus ist ein eigener AJAX-Aufruf und verbraucht Leistungseinheiten.**
>
> Wer eine Menge von 1 auf 10 klickt, löst zehn Aufrufe aus. Das Beispiel unten fasst schnell aufeinanderfolgende Klicks deshalb zu einem Aufruf zusammen.

Wegweiser: [Produkt per AJAX in den Warenkorb legen](https://dokumentation.websale.de/v8s/warenkorb/produkt-per-ajax-in-den-warenkorb-legen.md) · [AJAX-Funktionen](https://dokumentation.websale.de/v8s/templates/ajax-funktionen.md)

## Schritt 1: Die AJAX-Bibliothek einbinden

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<script src="/$WS/ws_sysdata/js/ws-module-ajaxgeneric-1.8.5.js"></script>
```

> [!NOTE]
>
> Die Versionsnummer im Dateinamen ist der Stand zum Zeitpunkt dieser Beschreibung. Prüfen Sie vor der Einbindung, welche Fassung aktuell ist. Bequemer ist die Einbindung über das PageSpeed-Werkzeug, das die Datei mitverwaltet.
>
> Ist das Modul in Ihrem Shop bereits global eingebunden, entfällt dieser Schritt.

> [!WARNING]
>
> Die Skripte auf dieser Seite verwenden jQuery. Prüfen Sie, ob Ihr Template es einbindet. Fehlt es, bleiben die Schaltflächen wirkungslos, und in der Konsole des Browsers steht `$ is not defined`.

## Schritt 2: Die Anzeigebereiche anlegen

Innerhalb des Warenkorbformulars auf `ws_basket.htm`:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<form action="~FORM-Basket~">
   <div id="basket--quantity--change--error"></div>

   <div id="ws--basket" aria-live="polite">
      ~WS-Fast_Include(Incl-Basket)~
   </div>
</form>
```

Außerhalb des Formulars, ein Zielcontainer je Warenkorbposition:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<div style="display: none">
   {@BASKET-PR-Data}
      <div id="quantity--change--ajax--~BASKET-PR-Number~"></div>
   {/@BASKET-PR-Data}
</div>
```

Die Zielcontainer gehören **außerhalb** des Warenkorbformulars. In sie wird später je ein eigenes Formular geladen, und Formulare dürfen in HTML nicht ineinander verschachtelt sein.

`aria-live="polite"` sorgt dafür, dass Hilfsmittel die Änderung ansagen. Ohne diese Angabe tauscht sich der Warenkorb lautlos aus, und wer die Seite nicht sieht, merkt nicht, dass die Menge übernommen wurde.

## Schritt 3: Die Bedienelemente in die Warenkorbzeile

Im Include `{Incl-Basket}`, innerhalb der Schleife über die Positionen:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{!ST-BasketPage}~BASKET-PR-Quantity~{/!ST-BasketPage}

{ST-BasketPage}
   <div class="basket--quantity--change"
        data-basketprnumber="~BASKET-PR-Number~"
        data-basketchangelink="~WS-EnsureNextLinkContainsURLParams~~BASKET-PR-ChangeLink~">
      <input type="hidden" basket-pr-currentval="~BASKET-PR-Quantity~">

      <button type="button" data-quantity="down" aria-label="Menge verringern">−</button>
      <input type="number" data-quantity="input" inputmode="numeric" min="1"
             value="~BASKET-PR-Quantity~">
      <button type="button" data-quantity="up" aria-label="Menge erhöhen">+</button>
   </div>
{/ST-BasketPage}
```

Die Klammerung `{ST-BasketPage}` sorgt dafür, dass die Bedienelemente nur auf der Warenkorbseite erscheinen. Dasselbe Include wird an anderen Stellen für den Kurzwarenkorb verwendet, und dort genügt die reine Mengenangabe.

> [!WARNING]
>
> Die Schaltflächen brauchen `aria-label`. Enthalten sie nur ein Symbol, beispielsweise ein Icon aus einer Symbolschrift, hat die Schaltfläche für Hilfsmittel keinen Namen und wird als "Schaltfläche" angesagt.

`type="number"` mit `min="1"` gehört zum Mengenfeld, nicht `type="text"`. Bei `type="text"` bleibt `min` wirkungslos, und auf Mobilgeräten erscheint die Buchstabentastatur.

Referenz: [BASKET-PR-Number](https://dokumentation.websale.de/v8s/referenz/tags/basket/besondere-produktarten-im-warenkorb.md) · [BASKET-PR-Quantity](https://dokumentation.websale.de/v8s/referenz/konfigurationen/shopkonfiguration-shop-config/warenkorb-und-bestellprozess.md#weitere-einstellungen) · [BASKET-PR-ChangeLink](https://dokumentation.websale.de/v8s/referenz/tags/basket/besondere-produktarten-im-warenkorb.md) · [ST-BasketPage](https://dokumentation.websale.de/v8s/referenz/tags/st/seitenzustand-und-navigation.md#auf-welcher-seite-steht-der-kaufer)

## Schritt 4: Das versteckte Produktformular

Legen Sie ein Template `incl_basket_quantity_change.htm` in Ihrem Templateverzeichnis an:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<WS-Ajax-quantity--change--ajax--~WS-OTPProductNumber~>
{PR-Data}
   <form action="~FORM-Product~" method="post"
         id="productform--basket--quantity--~PR-Number~">
      {ST-PR_Change}
         ~PR-PrintHiddenDepVariations~
         <input type="hidden" name="~BT-ProductAddToBasket_Ajax~" value="~PR-Number~">
         <input type="hidden" class="ajax-quantity-hidden-change"
                name="~PR-Quantity_Input~" value="~PR-Quantity~">
      {/ST-PR_Change}
   </form>
{/PR-Data}
</WS-Ajax-quantity--change--ajax--~WS-OTPProductNumber~>
```

| Element | Aufgabe |
| - | - |
| `WS-OTPProductNumber` | nimmt die Produktnummer auf, die im Aufruf als Parameter `otpproductnumber` übergeben wird. Dadurch trifft der Bereichsname genau den Zielcontainer dieser einen Position |
| `BT-ProductAddToBasket_Ajax` | sorgt dafür, dass der Shop auf die AJAX-Templates antwortet statt auf eine ganze Seite |
| `PR-PrintHiddenDepVariations` | führt die gewählte Variante mit. Ohne diese Zeile verliert ein Variantenprodukt bei jeder Mengenänderung seine Auswahl |
| `ST-PR_Change` | greift nur, wenn das Produkt tatsächlich schon im Warenkorb liegt |

Referenz: WS-OTPX · [FORM-Product](https://dokumentation.websale.de/v8s/referenz/tags/form-formulare.md#bestellablauf) · [BT-ProductAddToBasket\_Ajax](https://dokumentation.websale.de/v8s/referenz/tags/bt/produkt-und-warenkorb.md#bt-productaddtobasketajax) · [PR-PrintHiddenDepVariations](https://dokumentation.websale.de/v8s/referenz/tags/pr/grundlagen-und-produktdaten.md) · [ST-PR\_Change](https://dokumentation.websale.de/v8s/referenz/tags/st/seitenzustand-und-navigation.md#die-tags-im-uberblick)

## Schritt 5: Die beiden AJAX-Antworttemplates

Auf `ws_basket_ajax_error.htm`:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<WS-Ajax-basket--quantity--change--error>
   ~MSG-Notify~
</WS-Ajax-basket--quantity--change--error>
```

Auf `ws_basket_ajax_success.htm`:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<WS-Ajax-ws--basket>
   ~WS-Fast_Include(Incl-Basket)~
</WS-Ajax-ws--basket>
```

Der Erfolgsfall lädt den gesamten Warenkorb neu und nicht nur die geänderte Zeile. Das ist nötig, weil sich mit der Menge auch Zwischensumme, Versandkosten, Staffelpreise und Hinweise zu Mindestmengen ändern können.

Wegweiser: [Produkt per AJAX in den Warenkorb legen, Aufbau der AJAX-Templates](https://dokumentation.websale.de/v8s/warenkorb/produkt-per-ajax-in-den-warenkorb-legen.md)

## Schritt 6: Das Skript

Der erste Teil lädt das versteckte Formular nach, sobald der Käufer die Bedienelemente einer Position zum ersten Mal berührt:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<script>
$(document).on('mousedown', '.basket--quantity--change button, .basket--quantity--change input', function () {
   var container = $(this).closest('.basket--quantity--change');
   var number = container.data('basketprnumber');

   if ($('#productform--basket--quantity--' + number).length === 0) {
      ws_AJAXloadTemplate(
         container.data('basketchangelink') +
            '&tpl=incl_basket_quantity_change.htm&otpproductnumber=' + number,
         '~WS-Charset~',
         function () {}, function () {},
         'mirrorBasketInputs()', ''
      );
   }
});
</script>
```

Der zweite Teil spiegelt den Wert und sendet ab:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
<script>
var basketQuantityTimer = null;

function mirrorBasketInputs() {
   $('.basket--quantity--change').each(function () {
      var number = $(this).data('basketprnumber');
      var value = $(this).find('input[data-quantity="input"]').val();
      var hidden = $('#productform--basket--quantity--' + number)
                     .find('.ajax-quantity-hidden-change');

      if (hidden.length > 0 && hidden.val() !== value) {
         hidden.val(value);
         ws_AJAXsendForm('productform--basket--quantity--' + number, '~WS-Charset~',
            function () { $('#ws--basket').addClass('ajaxreload'); },
            function () { $('#ws--basket').removeClass('ajaxreload'); },
            'basketQuantitySuccess()', 'basketQuantityError()');
      }
   });
}

function scheduleMirror() {
   window.clearTimeout(basketQuantityTimer);
   basketQuantityTimer = window.setTimeout(mirrorBasketInputs, 400);
}

function readQuantity(input) {
   var value = parseInt($(input).val(), 10);
   if (isNaN(value) || value < 1) {
      value = 1;
   }
   return value;
}

$(document).on('click', '.basket--quantity--change button[data-quantity="down"]', function () {
   var input = $(this).siblings('input[data-quantity="input"]');
   var value = readQuantity(input);
   if (value > 1) {
      input.val(value - 1);
      scheduleMirror();
   }
});

$(document).on('click', '.basket--quantity--change button[data-quantity="up"]', function () {
   var input = $(this).siblings('input[data-quantity="input"]');
   input.val(readQuantity(input) + 1);
   scheduleMirror();
});

$(document).on('focusout', 'input[data-quantity="input"]', function () {
   $(this).val(readQuantity(this));
   mirrorBasketInputs();
});

function basketQuantitySuccess() {
   $('#ws--basket').removeClass('ajaxreload');
   $('#basket--quantity--change--error').empty();
}

function basketQuantityError() {
   $('#ws--basket').removeClass('ajaxreload');

   $('.basket--quantity--change').each(function () {
      var number = $(this).data('basketprnumber');
      var current = $(this).find('[basket-pr-currentval]').attr('basket-pr-currentval');

      $(this).find('input[data-quantity="input"]').val(current);
      $('#productform--basket--quantity--' + number + ' .ajax-quantity-hidden-change').val(current);
   });
}
</script>
```

### Was die einzelnen Teile leisten

| Funktion | Aufgabe |
| - | - |
| `mirrorBasketInputs` | geht alle Positionen durch und sendet nur die, deren Wert sich geändert hat |
| `scheduleMirror` | sammelt schnell aufeinanderfolgende Klicks und löst erst 400 Millisekunden nach dem letzten einen Aufruf aus |
| `readQuantity` | liest das Feld als Zahl und fängt leere oder unsinnige Eingaben ab |
| `basketQuantityError` | setzt alle Felder auf den zuletzt bestätigten Wert zurück |

Das versteckte Feld `basket-pr-currentval` hält den Wert, den der Shop zuletzt bestätigt hat. Lehnt der Shop eine Menge ab, beispielsweise wegen einer Höchstbestellmenge oder weil der Lagerbestand nicht reicht, springt die Anzeige damit auf den gültigen Stand zurück.

> [!WARNING]
>
> Die Plusschaltfläche kennt keine Obergrenze. Ist für das Produkt eine Höchstbestellmenge hinterlegt, merkt der Käufer das erst, wenn der Shop die Eingabe ablehnt und die Anzeige zurückspringt. Wenn Sie das vermeiden wollen, setzen Sie zusätzlich ein `max`-Attribut aus der Höchstbestellmenge des Produkts und blenden die Schaltfläche beim Erreichen aus.

Der Auslöser für das Nachladen ist `mousedown` und nicht `click`. Dadurch ist das versteckte Formular in der Regel schon da, wenn der Klick ausgewertet wird. Auf Geräten mit Touch-Bedienung löst der erste Druck dasselbe Ereignis aus.

## Verwandte Themen

* Wegweiser: [Produkt per AJAX in den Warenkorb legen](https://dokumentation.websale.de/v8s/warenkorb/produkt-per-ajax-in-den-warenkorb-legen.md)
* Wegweiser: [AJAX-Funktionen](https://dokumentation.websale.de/v8s/templates/ajax-funktionen.md)
* Wegweiser: [Warenkorb anzeigen und löschen](https://dokumentation.websale.de/v8s/warenkorb/warenkorb-anzeigen-loeschen.md)
* Wegweiser: [Bestellmenge eines Produkts](https://dokumentation.websale.de/v8s/produkte/bestellmenge-eines-produkts.md)
* Referenz: [BASKET- (Warenkorb)](https://dokumentation.websale.de/v8s/referenz/tags/basket/besondere-produktarten-im-warenkorb.md)


## Related topics

- [Anwendungsbeispiele](https://dokumentation.websale.de/v8s/anwendungsbeispiele/anwendungsbeispiele.md)
- [Storefront API Warenkorb](https://dokumentation.websale.de/schnittstellen/storefront-api/storefront-api-warenkorb.md)
- [Produkte mittels Gutschein zusteuern](https://dokumentation.websale.de/v8s/gutscheine-anbieten/produkte-mittels-gutschein-zusteuern.md)
- [$wsBasket - Warenkorb](https://dokumentation.websale.de/frontend/referenz/module/wsbasket.md)
- [Besondere Produktarten im Warenkorb](https://dokumentation.websale.de/v8s/referenz/tags/basket/besondere-produktarten-im-warenkorb.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.