# Ein neue Route (Seite) hinzufügen

Nun, da die Funktionalität zur Speicherung und Anzeige der zuletzten angesehenen Produkte existiert, möchten wir diese auf einer eigenen Seintanansicht darstellen. Somit gilt es, hierfür eine separate Route anzulegen. Wie dies funktioniert, soll im Folgenden aufgezeigt werden.

# Beschreibung

Die neue Seite soll über die Produktdetailseite erreichbar sein und eine längere Liste an zuletzt angesehenen Produkten anzeigen. Falls es keine Produkte auf der Liste gibt, soll ein ein Link zur Shop Startseite dargestellt werden.

Dadurch existieren also folgende Kriterien an die Implementierung:

  • Erstellen der Route
  • Erweitern der bestehenden LastViewedProducts Komponente, um je nach Route mehr Listenelemente anzuzeigen
  • Erstellen einer Weiterleitung auf die Startseite, wenn es keine zuletzt angesehenen Produkte gibt
  • Erstellen von Variablen für die erlaubten Listenlängen
  • Ergämnzen eines Button auf der Produktdetailseite, damit die Route mit der Liste von dort erreicht werden kannn

# Hinzufügen der Seite

Um ein Mapping zwischen Komponenten und einer Route zu erhalten, wird lediglich eine neue Datei innerhalb von ~/pages/customer benötigt:

touch viewedproducts.vue
1

Jede in diese Datei hinzugefügte Komponente ist danach beim Besuch der Seite http://localhost:3336/customer/viewedproducts sichtbar. Da wir vorhaben die zuletzt angesehenen Produkte anzuzeigen, kann die im vorherigen Abschnitt erstellte LastViewedProducts Komponente wiederverwendet werden. Im Folgenden befindet sich ein Ausschnitt des relevanten Programmcodes:

<!-- ~/pages/customer/viewedproducts.vue -->
<!-- simplified -->
<template>
    <div class="container">
        <client-only>
            <div v-if="viewedProducts.length">
                <last-viewed-products />
            </div>
        </client-only>
    </div>
</template>
1
2
3
4
5
6
7
8
9
10
11
<script>
    import { mapState } from 'vuex';

    export default {
        name: "ViewedProductsPage",

        layout: 'hubble',

        components: {
            LastViewedProducts: () => import(/* webpackChunkName: "LastViewedProductsChunk" */ "../../components/productutils/LastViewedProducts"),
        },

        computed: {
            ...mapState({
                viewedProducts: state => state.modLastViewed.viewedProducts
            })
        },
        // ...
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Auch hier stehen die zuletzt angesehenen Produkte - durch die Referenzierung des Vuex Stores - zur Verfügung. Jedoch fehlt im Template der Case v-else für den Fall, dass es keine zuletzt angesehenen Produkte gibt. Denn die LastViewedProducts wird nur angezeigt, wenn es auch Listenelemente gibt.

Daher können unterhalb des <div> Tags, welcher die <last-viewerd-products /> Komponente umrahmt, folgende Zeilen hinzugefügt werden:

<div v-else>
    <nuxt-link :to="localePath('index')">
        <button class="button-primary">
            {{ $t('Discover our products') }}
            <material-ripple />
        </button>
    </nuxt-link>
</div>
1
2
3
4
5
6
7
8

Somit ist eindeutig, dass die obere Komponente nicht immer Teil der DOM ist und weder das zugehörige Template, noch das Skript immer parat stehen müssen. Es bietet sich somit gemäß des Kapitels Lazy Loading an, diese per dynamischen Import erst einzubinden, wenn benötigt.

Um dieses Verhalten auch im Network Tab des Browser Inspektors zu beobachten, ist es möglich sogenannte Webpack Magic Comments (opens new window) zu verwenden. Diese ermöglichen es, dem Chunk einen Namen zu geben (hier wurde der Name LastViewedProductsChunk gewählt). Ohne Vergabe eines Namen, erhalten die Chunks meist eine numerische ID (siehe Webpack Dynamic Imports (opens new window) für Details) deren Inhalt somit nicht aus dem Namen hervorgeht.

# Erweiterung der Komponente LastViewedProducts

Damit die LastViewedProducts Komponente nun auf unserer neuen Seite mehr Elemente anzeigt, als auf der Produktdetailseite, kann dafür das Feld props verwendet werden:

// ~/components/productutils/LastViewedProducts.vue
props: {
    numberOfItems: {
        type: Number,
        required: true
    }
},
1
2
3
4
5
6
7

Im Template muss diese Unterscheidung der anzuzeigenden Anzahl an Elementen ebenfalls berücksichtigt werden. Hier eignet sich dafür eine computed Property, welche den als Prop übergebenen Wert verwendet:

// ~/components/productutils/LastViewedProducts.vue 
computed: {
    // ...
    selectionOfViewedProducts: function () {
        return this.viewedProducts.filter((viewedProduct, index) => index < this.numberOfItems)
    }
}
1
2
3
4
5
6
7

Nun zu der Verwendung der angelegten computed Property selectionOfViewedProducts. Da es nicht immer alle Elemente der Liste anzuzeigen gilt, muss das Basis Array (viewedProducts), welches zur Iteration verwendet wird, mit der gefilterten Liste (selectionOfViewedProducts) ausgetauscht werden.



 




<!-- ~/components/productutils/LastViewedProducts.vue -->
<div class="last-viewed--list">
    <div v-for="product in selectionOfViewedProducts" :key="product.id" class="last-viewed--clickable">
        <!-- ... -->
    </div>
</div>
1
2
3
4
5
6

Bisher gibt es keine Möglichkeit über eine In-Page Navigation auf die neue Route zu gelangen, außer über einen direkten Aufruf. Um also eine clientseitige Navigation für den Anwednder zu ermöglichen, kann dem Template der Komponente LastViewedProducts ein Button hinzugefügt werden. Jedoch soll dieser nur auf der ProduktDetailseite sichtbar sein und das Wechseln auf die Seite http://localhost:3336/customer/viewedproducts erlauben.

Somit kann erneut die computed Property, welche die aktuelle URL überprüft (onViewedProductsPage), verwendet werden:

<!-- ~/components/productutils/LastViewedProducts.vue -->
<button v-if="!onViewedProductsPage"
        class="last-viewed--button--show-all"
        @click="showAllViewedProducts"
>
    Show All
</button>
1
2
3
4
5
6
7

Außerdem wurde bereits eine Funktion referenziert, die für den Routenwechsel zuständig sein soll (showAllViewedProducts):

// ~/components/productutils/LastViewedProducts.vue
methods: {
    showAllViewedProducts: function () {
        this.$router.push({
            path: this.localePath('customer-viewedproducts')
        })
    }
},
1
2
3
4
5
6
7
8

Beim Aufruf der Seite http://localhost:3336/customer/viewedproducts, werden dem viewedProducts Array, keine weiteren Elemente hinzugefügt, wodurch ein Aufruf der action modLastViewed/saveViewedProductsToLocalForage nicht benötigt wird. Somit ist also eine Abfrage der aktuellen Route notwendig. Dafür eignet es sich in der LastViewedProducts eine weitere computed Property anzulegen:

// ~/components/productutils/LastViewedProducts.vue 
computed: {
    onViewedProductsPage: function () {
        return this.$route.path.includes('/customer/viewedproducts');
    },
    // ...
}
1
2
3
4
5
6
7

Diese kann nun in der created Lifecycle Methode der LastViewedProducts Komponente verwendet werden:

// ~/components/productutils/LastViewedProducts.vue
created() {
    if (process.client && !(this.onViewedProductsPage)) this.$store.dispatch('modLastViewed/saveViewedProductsToLocalForage');
}
1
2
3
4

Damit nun die gewünschte Anzahl angezeigt wird, muss diese auch an den Stellen, in der die LastViewedProducts Komponente eingebunden wird, per Prop übergeben werden. Da durch die Anforderungen an die Funktionalität, die Komponente nur an zwei Stellen eingebunden wird, können die unterschiedlichen Werte zentral im Vuex Store State angelegt werden und somit auch innerhalb des Vuex Stores über state.minToShow und state.maxSaved referenziert werden.

// ~/store/modLastViewed.js
export const state = () => ({
    // ...
    minToShow: 5,
    maxSaved: 8
})
1
2
3
4
5
6

Dabei ist maxSaved, die maximale Anzahl an zu speichernden Elementen und minToShow, die maximale Anzahl der anzuzeigenden Elemente auf der ProduktDetailseite.

Die konkreten Anpassungen dazu können dem Repository entnommen werden: ~/store/modLastViewed.js (opens new window).

Somit lässt sich zum einen die ViewProduct.vue anpassen, in der die LastViewedProducts eingebunden ist, wofür über mapState, die im Vuex Store angelegte minToShow Variable referenziert werden muss, damit diese als Prop Wert gesetzt werden kann:

// ~/components/productdetail/ViewProduct.vue 
...mapState({
    // ...
    minToShow: state => state.modLastViewed.minToShow,
}),
1
2
3
4
5
<!-- ~/components/productdetail/ViewProduct.vue --> 
<last-viewed-products :number-of-items="minToShow" />
1
2

Analog dazu ist die Anpassung der viewedproducts.vue. Hier ist der Unterschied, dass die maxSaved verwendet wird:

// ~/pages/customer/viewedproducts.vue 
...mapState({
    // ...
    minToShow: state => state.modLastViewed.maxSaved,
}),
1
2
3
4
5
<!-- ~/pages/customer/viewedproducts.vue -->
<last-viewed-products :number-of-items="maxSaved" />
1
2