Da Usability in der heutigen Zeit immer wichtiger wird, möchten wir mit dieser Serie von Blogposts die Entwicklung eines Produktkonfigurators für einen fiktiven Webshop von der Konzeption bis zur Umsetzung veranschaulichen.
Unser Produkt ist eine konfigurierbare Postkarte. Sie können den Text, die Form der Karte und das Papierformat ändern. Während der Konfiguration wird der Preis in Echtzeit entsprechend Ihrer Wahl aktualisiert.
Im ersten Teil stellten wir das Konzept vor, skizzierten ein einfaches Design und überlegten uns die Komponentenstruktur. Der zweite Teil führte den Vuex Store zur Verwaltung des Zustands ein.
In diesem Teil werden wir das Frontend implementieren und mit dem Store verbinden. Wir werden alle Details Schritt für Schritt durchgehen. Das Projekt wird mit @vue/cli erstellt und der Code ist auf GitHub verfügbar.
Datei- und Ordnerstruktur.
Im zweiten Teil haben wir ein Konfigurator- und Account-Modul im Vuex Store erstellt. Ersterer ist für den Produktkonfigurator, das Account-Modul hingegen für die Authentifizierung (An- und Abmeldung) verantwortlich. Da die beiden Module vollständig getrennt sind, ist es sinnvoll, zwei Containerkomponenten zu erstellen: KontoNav und Postkartenkonfigurator.
Diese Containerkomponenten kümmern sich um alles rund um das Lager. Das bedeutet, dass sie die einzigen Komponenten sind, die Aktionen auslösen. Sie geben die Props an ihre Childs weiter. Die untergeordneten Komponenten sind rein darstellerisch, sie rendern ihre Templates nur auf Basis von Props.
Die Benutzerinteraktion mit diesen Komponenten (d.h. ein Klick auf einen Button) führt zu einem Event, das nach oben zum übergeordneten Element gesendet wird. Die Events gehen aufwärts und abwärts durch den Komponentenbaum, bis sie bei der Containerkomponente ankommen, die mit dem Speicher kommuniziert, um den Zustand zu aktualisieren.
In der App.vue-Komponente sieht es folgendermaßen aus:
Die Komponente <AccountNav /> ist eine einfache Bootstrap-Navbar mit einem Anmeldeformular darin. Der <PostCardConfigurator /> ist der Konfigurator.
Da diese beiden Komponenten als Grundlage für ihre jeweiligen Verantwortlichkeiten dienen, haben wir im Ordner /components zwei Ordner „configurator“ und „account“ angelegt. Alle konfiguratorbezogenen Komponenten gehen in den ersten Ordner und die kontobezogenen Komponenten werden in den Kontoverzeichnis gespeichert. Auf diese Weise trennen wir nicht nur die Logik, sondern auch die Views (d.h. Komponenten).
Implementierung der Account Navbar.
Es handelt sich im Grunde genommen um eine Bootstrap-Navbar mit benutzerdefiniertem Design und einer <LoginForm />-Komponente. Die Anmeldeformularkomponente zeigt entweder ein Anmeldeformular (nicht angemeldet) oder eine Meldung „Sie sind angemeldet“ mit einem Anmeldebutton. Wir wissen, dass es ein künstliches Beispiel ist, aber das Hinzufügen dieser einfachen An- und Abmeldefunktion zeigt, wie die Verwendung von Modulen die Wartbarkeit des Codes verbessert.
Die <LoginForm />, eine Präsentationskomponente, muss über den Status der Anmeldung Bescheid wissen, um dem Benutzer die richtige Sache zu zeigen. Also müssen wir den Authen-Status als Prop übergeben. Dieser Authentifizierungsstatus kommt aus dem Shop und ist in <AccountNav /> über den Vuex-Helfer mapState verfügbar. Darüber hinaus sind die Vuex-Aktionen in Bezug auf Konten auch in <AccountNav /> mit mapActions verfügbar:
<LoginForm /> wird wie folgt dem Templateteil von <AccountNav /> hinzugefügt:
Anscheinend soll die Komponente <LoginForm/> zwei Events ausgeben: „An- und Abmeldung“. Anbei der Code dieser Komponente:
Es rendert das Formular grundsätzlich basierend auf dem Authentifizierungsstatus. Wenn Sie angemeldet sind, wird die Meldung „Sie sind angemeldet“ mit einem Button zum Abmelden angezeigt.
Die An- und Abmeldefunktionen sind einfach, sie senden die Events nur an den Parentteil, der wiederum mit dem Shop kommuniziert und den aktualisierten Authentifizierungsstatus als Prop zurückgibt.
Implementierung des Produktkonfigurators.
Genau wie die Komponente <AccountNav /> ist die Komponente <PostCardConfigurator /> der Container und die einzige Komponente, die mit dem Shop kommuniziert. Für einen Kunden, der zwei Postkarten bestellt, sieht es folgendermaßen aus:
Sie besteht im Wesentlichen aus einer <ProductList />, einem <PriceContainer /> und einem <ProceedToCheckoutButton />. Auch hier kommen Informationen über den Konfigurator aus dem Store (wieder mit mapState) und werden als Props an diese Komponenten weitergegeben. Darüber hinaus führt die Benutzerinteraktion mit diesen Komponenten (oder deren Childs) zu Events, die nach oben abgegeben werden.
Dieser Flow wird unten in der folgenden Abbildung dargestellt:
Mit einer tief verschachtelten Komponentenstruktur ist dies ein wenig ärgerlich, da viele der Komponenten nur Props weitergeben und Events auftauchen. Sie können jedoch weitere intelligente Komponenten hinzufügen, die mit dem Shop kommunizieren, wenn dies besser zur Struktur Ihrer Anwendung passt. Im Extremfall könnte man sich entscheiden, jede Komponente mit dem Store kommunizieren zu lassen, aber dies führt langfristig zu einer nicht wartbaren Codebasis. Für diese eher kleine Anwendung haben wir uns entschieden, nur 2 intelligenten Komponenten zu verwenden.
Nachdem wir diese Aufgabe erledigt haben, verschaffen wir uns einen kurzen Überblick über die Komponenten PriceContainer und ProceedToCheckoutButton. Ersteres gibt nur den Preis wieder, der als Prop übergeben wird, Letzteres hingegen rendert den Button. Die Nutzer haben in diesem Beispiel keine Möglichkeit eine Postkarte zu Ihrem Warenkorb hinzuzufügen, diese Teilaufgabe lassen wir in diesem Beispiel außen vor. Als nächstes werden wir die <ProductList />-Komponente besprechen.
Implementierung der <ProductList />
Die Komponente <ProductList /> enthält eine Reihe von Produkten als Props und trägt die Verantwortung für die Erstellung dieser Liste von Produkten. Darüber hinaus sollte es einen „Produkt hinzufügen“-Button anzeigen, der dafür sorgt, dass eine neue leere Postkarte in die Liste aufgenommen wird.
Das klingt also so, als würden wir zwei neue Komponenten erstellen: <SingleProduct />, das wir einen Eintrag aus dem Produktbereich und <AddProduct /> übergeben.
Letzteres ist sehr einfach: Es rendert einen Button und sendet per Klick ein „Add-Product“-Event an seinen übergeordneten Benutzer. Die <ProductList /> hört auf dieses Event und sendet es nach oben an seinen Smart-Partner. Dann wird die richtige Vuex-Aktion und -Mutation ausgelöst und ein neues Produkt mit einer Standardkonfiguration wird der Produktreihe im Array hinzugefügt.
Ersteres ist jedoch nicht viel komplexer: Wir müssen eine Liste von Produkten erstellen und damit die v-for Richtlinie nutzen:
Wir fügen id als Prop hinzu, wie wir sie später beim Zurücksetzen oder Entfernen eines Produkts benötigen. Zusätzlich fügen wir v-model=“product.config“ hinzu. In unserer ersten Implementierung haben wir versucht, das gesamte Produktobjekt als V-Modell zu setzen, aber das hat nicht funktioniert. V-Modell-Richtlinien können die Iterationsvariable „product“ nicht selbst aktualisieren. Deshalb haben wir unserer Produkt-Objektebene eine zusätzlichen Eigenschaft (config) hinzugefügt und diese als V-Modell verwendet. Dieses Problem werden wir an anderer Stelle ein wenig näher vertiefen.
Werfen wir nun einen Blick auf die Implementierung der <SingleProduct />-Komponente, da es von hier aus interessant wird.
<SingleProduct />
Diese Komponente ist dafür verantwortlich, ein einzelnes Produkt zu rendern und die Interaktionen des Benutzers zu tracken.
Auch hier teilen wir die Komponente in untergeordnete Komponenten auf, eine für jede Eingabe. Darüber hinaus müssen wir Klicks auf den Button „Entfernen“ oben rechts und den Link „Konfiguration löschen“ unten rechts behandeln. Ersteres sollte das Produkt aus der Liste entfernen, während Letzteres die Konfiguration wieder auf den Standard zurücksetzt. Beide geben nur ein Event nach oben aus, wobei die Produkt-ID als Argument dient. Auf diese Weise kann die „intelligente“ Root-Komponente das richtige Produkt basierend auf dieser ID entfernen oder zurücksetzen.
Anbei der vollständige Code der <SingleProduct />-Komponente:
Die von uns verwendeten untergeordneten Komponenten sind <CardChooseShape />, <CardChoosePapersize />, <CardChooseAmount />, <CardChoosePaperquality />, <CardChooseHeadline /> und CardChooseMaintext />. Eigentlich ist nur <CardChooseShape /> etwas komplexer, daher behandeln wir es im nächsten Abschnitt etwas ausführlicher. Für die anderen fünf Komponenten setzen wir einfach ein V-Modell als entsprechende Eigenschaft im product.config-Objekt.
<CardChooseShape />
Die CardChooseShape-Komponente ist komplexer, da sie untergeordnete Komponenten enthält. Wir brauchen dies aber, um die verschiedenen Papierformen zeichnen zu können, aus denen unsere Benutzer wählen können. Wir hätten uns das Leben mit gewöhnlichen Auswahlbuttons leichter machen können, aber so bereitet es mehr Freude.
Im Template der übergeordneten Komponente wird die Komponente <CardChooseShape /> wie folgt dargestellt:
Was die Geschwindigkeitskomponenten betrifft, so verwendet diese Komponente eine einzige Eigenschaft aus dem Konfigurationsobjekt als V-Modell. Darüber hinaus sendet es ein „geändertes“ Event, das von der updateParent Methode, die wir zuvor besprochen haben, nach oben emittiert wird.
Der Komponentencode <CardChooseShape /> sieht wie folgt aus:
Eingehende Props für dieses Bauteil sind die „Form“ und die „id“. Eine V-Modell-Implementierung geht von einem eingehenden Prop-Wert und einem emittierten Event „Input“ aus. Mit der Modelloption können wir diese überschreiben. Hier verwenden wir „Shape“ als Prop (anstelle von „value“) und „changed“ als Event.
Der Templateteil der Komponente zeigt die Liste der <CardCustomRadioShape />-Komponenten, mit denen wir dem Benutzer die verschiedenen Papierformen zeigen. Diese Gruppe von Komponenten ist als Funkgruppe zu verstehen. Jeder hat einen anderen Wert („theshape“ prop) und der geprüfte ist die Instanz, bei der der „aktuelle“ Prop dem „theshape“ Prop entspricht.
Der Code für die Komponente <CardCustomRadioShape /> ist unten dargestellt:
Wie versprochen, verwenden wir einfach ein <input type=“radio“>-Tag. Das Label ist so gestaltet, das es die richtigen Formen erhält. Das Attribut „checked“ des Radioelements wird durch die Gleichheit der Props „current“ und „theshape“ definiert.
Beachten Sie, dass die berechnete Eigenschaft „inputID“ notwenig ist, um für jede Instanz von <CardCustomRadioShape /> eindeutige Identifier zu erhalten. Die Verwendung der „theshape“-Prop würde hier nicht ausreichen, wenn wir mehrere <SingleProduct />-Instanzen auf der Seite haben. Daher wird die „ID“ verwendet, um eine eindeutige Kennung zu erzeugen.
Die Wahl einer Form bedeutet, dass die gewählte Methode ausgeführt wird, die die gewählte Form an das übergeordnete System sendet. Der Parent-Teil hört auf dieses Event und sendet einen Layer bis zur Komponente <SingleProduct />. Auf diese Weise wird die config.shape über die V-Modell-Bindung aktualisiert.
Zusammenfassung und abschließende Beurteilung.
Die Verwendung des V-Modells auf der <SingleProduct />-Komponente und ihren Childs führt zu einigen Komplexitäten, aber die Anwendung des V-Modells in benutzerdefinierten Komponenten, wie wir es bei Komponenten wie <CardChooseShape /> getan haben, ergibt einen perfekt funktionierenden Produktkonfigurator.
Rückblickend auf die Implementierung des Produktkonfigurators müssen wir zugeben, dass das Denken und Handeln danach wirklich gut funktioniert hat. Ausgehend von den verwendeten Skizzen fühlte sich die Definition der Komponenten und untergeordneten Komponenten wirklich natürlich an.
Ein naheliegender nächster Schritt für dieses kleine Projekt wäre die Implementierung in einem echten Webshop. Dies könnte ein Vue only SPA mit einer API und einem Backend sein, aber es ist auch möglich, den Produktkonfigurator zu einem typischen Laravel-Projekt hinzuzufügen.
Vielen Dank für Ihren Besuch.