> ## 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.

# Produktvergleich

> Der Produktvergleich lässt Kunden mehrere Produkte derselben Kategorie nebeneinander vergleichen. Diese Anleitung zeigt, wie Sie ihn im Template einrichten.

Der Produktvergleich sammelt Produkte in einer sitzungsweiten Liste und stellt sie auf einer eigenen Vergleichsseite tabellarisch gegenüber. Ein Kunde fügt Produkte von der jeweiligen Produktdetailseite aus hinzu und ruft dann die Vergleichsseite auf. Dort sieht er alle Felder der ausgewählten Produkte in einer Tabelle nebeneinander. Zeilen, in denen sich die Werte unterscheiden, können farblich hervorgehoben werden.

Um sinnfreie Vergleiche (beispielsweise eine Hose neben einem Laptop) zu vermeiden, erlaubt der Shop nur Produkte derselben Kategorie in der Vergleichsliste. Die Kategorie wird über ein Produktfeld festgelegt.

<Info>
  Die Funktion ist eine Template-Erweiterung und keine Kernfunktion des Shopsystems. Es gibt dafür keine eigene Konfiguration im Admin-Interface - die Steuerung erfolgt ausschließlich über die hier beschriebenen Template-Dateien.
</Info>

## Voraussetzungen

Damit der Produktvergleich sinnvoll funktioniert, muss das Kategoriefeld am Produkt gepflegt sein. Fehlt es, lassen sich Produkte weiterhin vergleichen, allerdings ohne Schutz vor kategorieübergreifenden Vergleichen. Der Shop benötigt ein freies Produktfeld, das die Kategorie eines Produkts als Text enthält (beispielsweise `Bekleidung`, `Elektronik`, `Katzenfutter`). Im BestPractice-Shop trägt dieses Feld den technischen Namen `mainCategory`. Ist das Feld bei einem Produkt leer, wird dieses Produkt beim Hinzufügen zu einer bestehenden Vergleichsliste abgelehnt, sobald die Liste bereits eine Kategorie festgelegt hat (siehe [Wie der Produktvergleich funktioniert](#wie-der-produktvergleich-funktioniert)).

<Warning>
  Der technische Feldname ist entscheidend, nicht der im Admin-Interface angezeigte Label-Name. Lässt sich der Name nicht auf Anhieb bestimmen, hilft eine Ausgabe aller freien Felder eines Produkts auf der Produktseite: `{{= $cProduct.custom | json }}`. Die Ausgabe zeigt alle technischen Feldnamen mit ihren aktuellen Werten.
</Warning>

## Begriffe und technische Namen auf einen Blick

| Konzept                            | Session                               | Template (product.htm / compare.htm)                               |
| ---------------------------------- | ------------------------------------- | ------------------------------------------------------------------ |
| Liste der verglichenen Produkt-IDs | `compareIds` (kommagetrennter String) | `$compareIds` (als Array aufbereitet)                              |
| Aktuell festgelegte Kategorie      | `compareType`                         | `$compareType`                                                     |
| Kategorie eines Produkts           | —                                     | `$cProduct.custom.mainCategory`                                    |
| Aktion zum Schreiben der Session   | —                                     | `$wsActions.create("SessionUpdate")`                               |
| Vergleichsseite (eigene View)      | —                                     | `views/compare.htm`, Aufruf über `$wsViews.viewUrl('compare.htm')` |

<Note>
  Die Session speichert `compareIds` als kommagetrennten String (`"123,456,789"`), nicht als Array. Grund dafür ist, dass `$wsSession.set()` bei einem leeren Array keinen Wert schreibt; ein leerer String lässt sich dagegen zuverlässig speichern und auslesen. Im Template wird der String bei jedem Aufruf über `split(",")` wieder in ein Array zerlegt.
</Note>

## Wie der Produktvergleich funktioniert

Beim Versuch, ein Produkt zur Vergleichsliste hinzuzufügen, prüft der Shop die Kategorie in folgender Reihenfolge:

1. **Die Liste ist leer.** Das Produkt wird unabhängig von seiner Kategorie aufgenommen. Seine Kategorie (`mainCategory`) wird dabei als sitzungsweite Vergleichskategorie (`compareType`) festgelegt.
2. **Die Liste enthält bereits Produkte und die Kategorie des neuen Produkts stimmt mit der festgelegten Vergleichskategorie überein.** Das Produkt wird aufgenommen.
3. **Die Kategorie stimmt nicht überein (oder ist am Produkt leer).** Das Produkt wird nicht aufgenommen. Auf der Produktseite erscheint an Stelle des „Hinzufügen"-Links ein Hinweistext mit der aktuell geltenden Kategorie.

Wird die Liste vollständig geleert (letztes Produkt entfernt oder über „Vergleich leeren"), wird auch die festgelegte Vergleichskategorie zurückgesetzt. Danach kann der Kunde erneut mit einer beliebigen Kategorie beginnen.

Zusätzlich ist die Liste auf eine bestimmte Anzahl an Produkten begrenzt, die im Template beliebig angepasst werden kann. Ist das Limit erreicht, erscheint statt des „Hinzufügen"-Links ein entsprechender Hinweis.

<Note>
  Die maximale Anzahl der vergleichbaren Produkte ist im Template-Code fest hinterlegt (`len($compareIds) >= 4`) und keine Einstellung im Admin-Interface. Ein anderer Grenzwert lässt sich nur durch Ändern dieser Zahl direkt im Template erreichen.
</Note>

## Anzeige im Shop

### Button auf der Produktdetailseite

Auf der Produktseite erscheint je nach Zustand einer von vier Varianten:

| Zustand                                                    | Anzeige                                            |
| ---------------------------------------------------------- | -------------------------------------------------- |
| Produkt ist bereits in der Liste                           | Link „Bereits im Vergleich – entfernen"            |
| Kategorie weicht von der festgelegten Kategorie ab         | Hinweistext mit der geltenden Kategorie, kein Link |
| Die Liste enthält bereits die maximale Anzahl an Produkten | Hinweis „Max. Produkte im Vergleich erreicht"      |
| Keiner der obigen Fälle                                    | Link „Zum Vergleich hinzufügen"                    |

### Badge im Header

Im Header zeigt ein Icon mit Zähler die aktuelle Anzahl der verglichenen Produkte an und verlinkt auf die Vergleichsseite. Der Zähler erscheint nur, wenn mindestens ein Produkt in der Liste ist.

### Vergleichsseite

Die Vergleichsseite (`views/compare.htm`) listet alle Produkte der aktuellen Vergleichsliste in einer Tabelle: eine Produktkarte pro Spalte (Bild, Name, Preis, Link zur Produktseite), darunter Preis, Artikelnummer, Beschreibung, Kategorie und alle weiteren gepflegten freien Felder. Felder, die bei keinem der verglichenen Produkte einen Wert tragen, werden nicht angezeigt. Zeilen, in denen sich die Werte unterscheiden, werden farblich hervorgehoben, damit Unterschiede auch bei vielen Feldern auf einen Blick erkennbar sind.

Ist die Liste leer, erscheint ein Leerzustand mit einem Link zurück zum Shop.

## Module

Für die Umsetzung des Produktvergleichs werden folgende Module verwendet:

* [\$wsSession](/frontend/referenz/module/wssession) - Speichern und Auslesen der Vergleichsliste (`compareIds`) und der festgelegten Kategorie (`compareType`).
* [Aktionen - Übersicht](/frontend/referenz/aktionen) - Aktion `SessionUpdate`, um die Session vor dem Rendern der Seite zu schreiben.
* [\$wsProducts](/frontend/referenz/module/wsproducts) - Laden der Produktdaten für jede ID in der Vergleichsliste auf der Vergleichsseite.

## Einrichtung

Der Produktvergleich betrifft drei Template-Dateien: die Produktdetailseite (`product.htm`), den Header (`header.htm` bzw. die eingebundene Header-Komponente) und eine neu anzulegende Vergleichsseite (`views/compare.htm`). Die folgenden Schritte bauen aufeinander auf und sollten in dieser Reihenfolge umgesetzt werden.

### Schritt 1: Kategoriefeld am Produkt bereitstellen

Legen Sie, falls noch nicht vorhanden, ein freies Produktfeld für die Kategorie an (siehe [Voraussetzungen](#voraussetzungen)). In dieser Anleitung heißt das Feld `mainCategory`. Pflegen Sie es bei allen Produkten, die am Vergleich teilnehmen sollen.

#### Benutzerdefiniertes Feld anlegen

Falls solch ein Feld in Ihrem Shop noch nicht existiert, können Sie es wie folgt im Admin-Interface anlegen.

1. Unter *Katalog → Produkte → Einstellungen → „+ Neu"* kann ein neues Produktfeld angelegt werden.
   <Frame>
     <img src="https://mintcdn.com/websaleag-44ee7ea6/W45TXPOqP8igr_zC/images/websale-admin-produktfeld-hinzufuegen.png?fit=max&auto=format&n=W45TXPOqP8igr_zC&q=85&s=14cba35f5df763574950a53e03a51272" alt="Websale Admin Produktfeld Hinzufuegen" width="2548" height="1373" data-path="images/websale-admin-produktfeld-hinzufuegen.png" />
   </Frame>
2. Vergeben Sie einen eindeutigen technischen Namen für Ihr Produktfeld und speichern Sie es.
   <Frame>
     <img src="https://mintcdn.com/websaleag-44ee7ea6/W45TXPOqP8igr_zC/images/websale-admin-produktfeld-hinzufuegen-speichern.png?fit=max&auto=format&n=W45TXPOqP8igr_zC&q=85&s=9f9e906044b942fece8f50923f7c5c48" alt="Websale Admin Produktfeld Hinzufuegen Speichern" width="571" height="370" data-path="images/websale-admin-produktfeld-hinzufuegen-speichern.png" />
   </Frame>
3. Anschließend ist das Feld am Produkt verfügbar und kann befüllt werden. In diesem Beispiel wird der Produkttyp **Flyer** verwendet, passend zum Produkt **Flyer Option 1**.
   <Frame>
     <img src="https://mintcdn.com/websaleag-44ee7ea6/W45TXPOqP8igr_zC/images/websale-admin-produktfeld-hinzufuegen-eintragung.png?fit=max&auto=format&n=W45TXPOqP8igr_zC&q=85&s=01274152cb0f2a89ef4ad51e330feacb" alt="Websale Admin Produktfeld Hinzufuegen Eintragung" width="2418" height="833" data-path="images/websale-admin-produktfeld-hinzufuegen-eintragung.png" />
   </Frame>

#### Feld an Produkten pflegen

Damit die Kategorieprüfung zuverlässig funktioniert, muss das Feld bei allen Produkten mit einem Wert befüllt sein. Produkte ohne Wert werden vom Vergleich ausgeschlossen, sobald bereits ein Produkt mit einem Wert in der Vergleichsliste liegt.

| Situation                                                          | Verhalten                                                                                                                                                                          |
| ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Erstes Produkt in der Vergleichsliste hat den Wert „Bekleidung"    | Kategorie wird als Vergleichstyp gesetzt.<br />Ab jetzt wird bei jedem weiteren Produkt geprüft, ob es den gleichen Wert hat und somit auch zum Vergleich hinzugefügt werden kann. |
| Weiteres Produkt für die Vergleichsliste hat den Wert „Bekleidung" | Das Produkt kann zur Vergleichsliste hinzugefügt werden.                                                                                                                           |
| Weiteres Produkt hat den Wert „Elektronik"                         | Das Produkt kann nicht zur Vergleichsliste hinzugefügt werden. Ein entsprechender Hinweis wird am Produkt ausgegeben.                                                              |
| Weiteres Produkt hat keinen eingetragenen Wert                     | Das Produkt kann nicht zur Vergleichsliste hinzugefügt werden. Ein entsprechender Hinweis wird am Produkt ausgegeben.                                                              |
| Vergleichsliste wird geleert                                       | Jedes beliebige Produkt kann als nächstes zur Vergleichsliste hinzugefügt werden.                                                                                                  |

### Schritt 2: Vergleichsliste in `product.htm` einlesen

Öffnen Sie `product.htm` und fügen Sie ganz oben im Block `content_main`, vor der eigentlichen Seitenausgabe, den folgenden Abschnitt ein. Er liest den aktuellen Stand der Vergleichsliste aus der Session und bereitet ihn für die weitere Verwendung auf:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}} theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $compareIdsRaw = $wsSession.get("compareIds") | ifNull("") }}
{{ var $compareIds = [] }}
{{ if $compareIdsRaw != "" }}
  {{ foreach $id in split($compareIdsRaw, ",") }}
    {{ push($compareIds, $id) }}
  {{ /foreach }}
{{ /if }}
{{ var $compareTypeRaw = $wsSession.get("compareType") }}
{{ var $compareType = "" }}
{{ if $compareTypeRaw }}
  {{ $compareType = $compareTypeRaw }}
{{ /if }}
```

**Ergebnis** <br />`$compareIds` steht ab dieser Stelle als Array von Produkt-IDs zur Verfügung, `$compareType` enthält die aktuell festgelegte Kategorie oder einen leeren String, wenn die Liste leer ist. Beide Variablen werden in den nächsten Schritten benötigt.

### Schritt 3: Button-Bereich in `product.htm` einbauen

Fügen Sie an der Stelle, an der der Vergleichs-Button erscheinen soll (beispielsweise unterhalb des Warenkorb-Formulars), folgenden Block ein. Er berechnet je nach Zustand den passenden Formularinhalt und übergibt ihn an die Aktion `SessionUpdate`. Die maximale Anzahl von 4 Produkten (`$maxReached`) kann hier durch Ändern der Zahl an Ihren gewünschten Grenzwert angepasst werden:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}} theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $alreadyInCompare = $cProduct.id in $compareIds }}
{{ var $maxReached = len($compareIds) >= 4 }}
{{ var $productCategory = $cProduct.custom.mainCategory | ifNull("") }}
{{ var $typeMismatch = $compareType != "" and $productCategory != "" and $compareType != $productCategory }}

{{ var $myActionCompareUpdate = $wsActions.create("SessionUpdate") }}

<div id="wsCompareWidget">
  {{ if $alreadyInCompare }}
    {{ var $removeIds = [] }}
    {{ foreach $id in $compareIds }}
      {{ if $id != $cProduct.id }}
        {{ push($removeIds, $id) }}
      {{ /if }}
    {{ /foreach }}
    {{ var $removeIdsStr = join($removeIds, ",") }}
    {{ var $removeTypeStr = $compareType }}
    {{ if len($removeIds) == 0 }}
      {{ $removeTypeStr = "" }}
    {{ /if }}
    <form method="post" action="{{= $wsViews.current.url() }}" data-ws-ajax-form>
      <input type="hidden" name="wsReplaceIds" value="wsCompareWidget,wsCompareHeaderBadge">
      <input type="hidden" name="wscsrf" value="{{= $myActionCompareUpdate.csrf }}">
      <input type="hidden" name="wsact" value="{{= $myActionCompareUpdate.id }}">
      <input type="hidden" name="session.compareIds" value="{{= $removeIdsStr }}">
      <input type="hidden" name="session.compareType" value="{{= $removeTypeStr }}">
      <button type="submit">Bereits im Vergleich – entfernen</button>
    </form>
  {{ elseif $typeMismatch }}
    <p>Nur „{{= $compareType }}" vergleichbar</p>
  {{ elseif $maxReached }}
    <p>Max. Produkte im Vergleich erreicht</p>
  {{ else }}
    {{ var $addIds = [] }}
    {{ foreach $id in $compareIds }}
      {{ push($addIds, $id) }}
    {{ /foreach }}
    {{ push($addIds, $cProduct.id) }}
    {{ var $addIdsStr = join($addIds, ",") }}
    {{ var $addTypeStr = $compareType }}
    {{ if $addTypeStr == "" }}
      {{ $addTypeStr = $productCategory }}
    {{ /if }}
    <form method="post" action="{{= $wsViews.current.url() }}" data-ws-ajax-form>
      <input type="hidden" name="wsReplaceIds" value="wsCompareWidget,wsCompareHeaderBadge">
      <input type="hidden" name="wscsrf" value="{{= $myActionCompareUpdate.csrf }}">
      <input type="hidden" name="wsact" value="{{= $myActionCompareUpdate.id }}">
      <input type="hidden" name="session.compareIds" value="{{= $addIdsStr }}">
      <input type="hidden" name="session.compareType" value="{{= $addTypeStr }}">
      <button type="submit">Zum Vergleich hinzufügen</button>
    </form>
  {{ /if }}
</div>
```

<Warning>
  Der umgebende `<div id="wsCompareWidget">` ist erforderlich. Über diese ID ersetzt `wsReplaceIds` beim Absenden des Formulars gezielt diesen Bereich, ohne die restliche Seite neu zu laden.
</Warning>

**Ergebnis** <br />Auf der Produktseite erscheint abhängig vom aktuellen Zustand automatisch der passende Button oder Hinweistext (siehe [Anzeige im Shop](#button-auf-der-produktdetailseite)).

### Schritt 4: Badge im Header einbauen

Öffnen Sie die Header-Datei (`header.htm` bzw. die entsprechend eingebundene Komponente Ihres Templates) und fügen Sie im Bereich der übrigen Icons (Merkliste, Warenkorb) folgenden Block ein:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}} theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $compareIdsRaw = $wsSession.get("compareIds") | ifNull("") }}
{{ var $compareCount = 0 }}
{{ if $compareIdsRaw != "" }}
  {{ foreach $id in split($compareIdsRaw, ",") }}
    {{ $compareCount = $compareCount + 1 }}
  {{ /foreach }}
{{ /if }}
<span id="wsCompareHeaderBadge">
  <a href="{{= $wsViews.viewUrl('compare.htm') }}" title="Produktvergleich">
    {{# Icon nach Wahl #}}
    {{ if $compareCount > 0 }}
      <div class="badge">{{= $compareCount }}</div>
    {{ /if }}
  </a>
</span>
```

<Warning>
  Auch hier ist die umgebende ID (`wsCompareHeaderBadge`) erforderlich, damit der Zähler beim Hinzufügen oder Entfernen eines Produkts per AJAX aktualisiert wird (siehe `wsReplaceIds` in Schritt 3 und Schritt 5).
</Warning>

**Ergebnis** <br />Im Header erscheint ein Icon mit Zähler, das auf die Vergleichsseite verlinkt. Der Zähler aktualisiert sich beim Hinzufügen oder Entfernen eines Produkts ohne Neuladen der Seite.

### Schritt 5: Vergleichsseite anlegen

Legen Sie eine neue Datei `views/compare.htm` an. Sie ist eine eigenständige View und wird über `{{= $wsViews.viewUrl('compare.htm') }}` aufgerufen (siehe Schritt 4). Die Datei liest zunächst den Session-Stand ein - identisch zu Schritt 2:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}} theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $compareIdsRaw = $wsSession.get("compareIds") | ifNull("") }}
{{ var $compareIds = [] }}
{{ if $compareIdsRaw != "" }}
  {{ foreach $id in split($compareIdsRaw, ",") }}
    {{ push($compareIds, $id) }}
  {{ /foreach }}
{{ /if }}
{{ var $compareTypeRaw = $wsSession.get("compareType") }}
{{ var $compareType = "" }}
{{ if $compareTypeRaw }}
  {{ $compareType = $compareTypeRaw }}
{{ /if }}
```

Darauf folgt der übrige HTML-Aufbau der Seite (`<!DOCTYPE html>`, eigenes `<head>` mit Stylesheet, `<body>`). Da `compare.htm` eine eigenständige View ist, wird kein Shop-Layout eingebunden - Kopf- und Fußbereich der Seite müssen, sofern gewünscht, selbst ergänzt werden.

Innerhalb des Seitenkörpers folgt zunächst der „Vergleich leeren"-Button:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}} theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ if len($compareIds) > 0 }}
{{ var $myActionCompareClear = $wsActions.create("SessionUpdate") }}
<form method="post" action="{{= $wsViews.viewUrl('compare.htm') }}" data-ws-ajax-form>
  <input type="hidden" name="wsReplaceIds" value="wsCompareWrapper,wsCompareHeaderBadge">
  <input type="hidden" name="wscsrf" value="{{= $myActionCompareClear.csrf }}">
  <input type="hidden" name="wsact" value="{{= $myActionCompareClear.id }}">
  <input type="hidden" name="session.compareIds" value="">
  <input type="hidden" name="session.compareType" value="">
  <button type="submit">Vergleich leeren</button>
</form>
{{ /if }}
```

Ist die Liste leer, wird an dieser Stelle ein Leerzustand mit Link zurück zum Shop angezeigt (`{{ if len($compareIds) == 0 }} ... {{ else }} ...`). Andernfalls werden die Produktdaten geladen:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}} theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $products = [] }}
{{ foreach $id in $compareIds }}
  {{ var $p = $wsProducts.load($id) }}
  {{ push($products, $p) }}
{{ /foreach }}
```

Für jedes Produkt wird eine Produktkarte mit einem eigenen Entfernen-Button ausgegeben. Da mehrere solcher Formulare auf derselben Seite vorkommen, erhält jede Aktion eine `tag`, um sie voneinander zu unterscheiden:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}} theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ foreach $p in $products }}
  {{ var $keepIds = [] }}
  {{ foreach $id2 in $compareIds }}
    {{ if $id2 != $p.id }}
      {{ push($keepIds, $id2) }}
    {{ /if }}
  {{ /foreach }}
  {{ var $keepIdsStr = join($keepIds, ",") }}
  {{ var $keepTypeStr = $compareType }}
  {{ if len($keepIds) == 0 }}
    {{ $keepTypeStr = "" }}
  {{ /if }}
  {{ var $myActionCompareRemove = $wsActions.create("SessionUpdate", tag=$p.id) }}

  <form method="post" action="{{= $wsViews.viewUrl('compare.htm') }}" data-ws-ajax-form>
    <input type="hidden" name="wsReplaceIds" value="wsCompareWrapper,wsCompareHeaderBadge">
    <input type="hidden" name="wscsrf" value="{{= $myActionCompareRemove.csrf }}">
    <input type="hidden" name="wsact" value="{{= $myActionCompareRemove.id }}">
    <input type="hidden" name="session.compareIds" value="{{= $keepIdsStr }}">
    <input type="hidden" name="session.compareType" value="{{= $keepTypeStr }}">
    <button type="submit">Entfernen</button>
  </form>
  {{# Bild, Name, Preis, Link zur Produktseite folgen hier #}}
{{ /foreach }}
```

<Warning>
  Der gesamte Seiteninhalt (Button „Vergleich leeren", Leerzustand und Produkttabelle) muss in einen gemeinsamen Container mit der ID `wsCompareWrapper` eingeschlossen werden. Nur so kann `wsReplaceIds` beim Entfernen oder Leeren die komplette Vergleichsseite in einem Zug aktualisieren.
</Warning>

**Ergebnis** <br />Die Vergleichsseite zeigt alle Produkte der Liste mit einem eigenen Entfernen-Button pro Produkt sowie einem Button zum vollständigen Leeren der Liste.

### Schritt 6: Vergleichstabelle mit dynamischen Feldern aufbauen

Nach den Produktkarten folgt die eigentliche Vergleichstabelle. Feste Zeilen wie Preis, Artikelnummer und Kategorie werden direkt ausgegeben. Für die übrigen, frei gepflegten Produktfelder wird zunächst ermittelt, welche Feldnamen bei mindestens einem der Produkte einen Wert tragen:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}} theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ var $skipFields = ["image", "mainCategory", "brand", "crossSelling", "crosslinks"] }}

{{ var $allKeys = [] }}
{{ foreach $p in $products }}
  {{ foreach $key in keys($p.custom) }}
    {{ if not ($key in $skipFields) }}
      {{ if not ($key in $allKeys) }}
        {{ push($allKeys, $key) }}
      {{ /if }}
    {{ /if }}
  {{ /foreach }}
{{ /foreach }}
```

<Note>
  `$skipFields` enthält die Felder, die bereits über eigene, fest formulierte Tabellenzeilen ausgegeben werden (beispielsweise das Bild oder die Kategorie), damit sie nicht ein zweites Mal in der dynamischen Liste erscheinen. Passen Sie diese Liste an die in Ihrem Shop verwendeten Feldnamen an.
</Note>

Für jedes verbleibende Feld wird geprüft, ob mindestens ein Produkt einen Wert trägt, und ob sich die Werte zwischen den Produkten unterscheiden:

```html theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}} theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/websale.json"]}}
{{ foreach $key in $allKeys }}
  {{ var $hasValue = false }}
  {{ foreach $p in $products }}
    {{ if $p.custom[$key] }}
      {{ $hasValue = true }}
    {{ /if }}
  {{ /foreach }}

  {{ if $hasValue }}
    {{ var $firstVal = $products[0].custom[$key] | ifNull("") }}
    {{ var $allSame = true }}
    {{ foreach $p in $products }}
      {{ if ($p.custom[$key] | ifNull("")) != $firstVal }}
        {{ $allSame = false }}
      {{ /if }}
    {{ /foreach }}
    <tr>
      <td class="{{ if not $allSame }}highlight{{ /if }}">{{= $key }}</td>
      {{ foreach $p in $products }}
      <td class="{{ if not $allSame }}highlight{{ /if }}">
        {{ if $p.custom[$key] }}{{= $p.custom[$key] }}{{ else }}–{{ /if }}
      </td>
      {{ /foreach }}
    </tr>
  {{ /if }}
{{ /foreach }}
```

**Ergebnis** <br />Felder, die bei keinem der verglichenen Produkte einen Wert tragen, erscheinen nicht in der Tabelle. Zeilen, in denen sich die Werte zwischen den Produkten unterscheiden, erhalten die CSS-Klasse `highlight` und lassen sich damit optisch hervorheben (beispielsweise mit einem farbigen Hintergrund).

## Weiterführende Links

* [\$wsSession](/frontend/referenz/module/wssession) - Speichern und Lesen sitzungsweiter Werte.
* [Aktionen - Übersicht](/frontend/referenz/aktionen) - Funktionsweise von Aktionen, insbesondere die Verarbeitung vor dem Rendern und die Ausführung per AJAX über `wsReplaceIds`.
* [\$wsProducts](/frontend/referenz/module/wsproducts) - Laden einzelner Produkte anhand ihrer ID.
* [\$wsViews](/frontend/referenz/module/wsviews) - Erzeugen von View-URLs für eigene Views wie `compare.htm`.
