Afosto Storefront integreren in jouw Afosto CMS webshop

Onze nieuwe storefront kun je ook gebruiken in je bestaande Afosto webshop. Dit vereist een aantal aanpassingen aan het template van je shop en de installatie van een plugin. In deze handleiding leggen we je stapsgewijs uit hoe je jouw shop koppelt aan de Afosto Storefront.

Storefront verkoopkanaal aanmaken

De eerste stap is om een Storefront verkoopkanaal aan te maken. Het aanmaken van een verkoopkanaal bepaalt hoe je online winkel communiceert met de Afosto Storefront. De configuratie van het verkoopkanaal omvat details zoals de naam van je storefront, branding, betaal- en verzendmethoden, en standaardtaal.

Vul in de volgende stap de benodigde informatie in en maak je verkoopkanaal aan.

Storefront credentials aanmaken

Nu je een verkoopkanaal hebt, kunnen we API credentials aanmaken. Dit doe je onder “Mijn organisatie” in de Admin App bij “API instellingen”. Hier zie je een overzicht van je Storefront credentials en kun je een nieuwe aanmaken.

Kies het verkoopkanaal waarvoor je de credentials wilt aanmaken en geef een goede omschrijving op. Klik op toevoegen om de credentials aan te maken.

Kopieer in het venster dat je ziet na het aanmaken, de sleutel in het veld. Let op: Dit is de enige keer dat je de volledige sleutel ziet. Na het sluiten van het venster is de sleutel niet meer volledig in te zien. Ben je hem kwijt? Dan kun je een nieuwe maken voor hetzelfde verkoopkanaal en de bestaande verwijderen.

Plugin instellen

Met de gekopieerde sleutel kunnen we nu de webshop koppelen. Controleer eerst of de juiste plugin wel is ingesteld. Zoek op https://app.afosto.com/plugins naar ‘Afosto.app - ORM’. Is deze app niet geïnstalleerd? Neem dan contact op via support@afosto.com.

Open app.afosto.com en ga naar jouw webshop. Klik onder de kop "Apps" op "afosto.app -ORM". Je ziet nu de instellingen voor het koppelen van de webshop aan de Afosto Storefront. Plak in het API-token veld de gekopieerde sleutel. Na het opslaan van deze instellingen kunnen we aan de slag met het template.

Wat is er anders?

In onze voorbeeld shop voor de Afosto Storefront maken we gebruik van Preact. Dit is een lichtere versie van React, een erg populair JavaScript framework. Hierdoor zijn bewerkingen aan de winkelwagen snel zichtbaar, zonder de pagina opnieuw te hoeven laden. Dit betekent ook dat we eigenlijk niet meer de HTML in twig schrijven, maar in Preact componenten. In de volgende stappen laten we zien hoe dit werkt.

Om op een moderne manier JavaScript te schrijven, maken we ook gebruik van de JavaScript module syntax. Dit houdt in dat we moderne JavaScript kunnen schrijven met import statements. Hierdoor kunnen we bijvoorbeeld het volgende doen.

1<script type="module">
2  import { h, render } from 'https://esm.sh/preact';
3  import { html } from 'https://esm.sh/htm/preact';
4
5  render(html`<h1>Hello World!</h1>`, document.getElementById('app'));
6</script>
7<div id="app"></div>

Om dit te kunnen doen werken we niet met de JavaScript bestanden die je in de assets folder ziet, maar vanuit een twig bestand.

Storefront logica

Het toevoegen van de storefront logica is een uitgebreide klus. Het makkelijkste is het script overnemen van onze voorbeeld shop. Deze kun je vinden op: https://gist.github.com/gijsbotje/cb631ff6e59095d142f875c0eab7a6e3.

In dit bestand worden een aantal dingen opgezet.

  • De Storefront client wordt geïnitialiseerd met de ingestelde Storefront credentials en de taal van de shop zodat errors in de juiste taal terugkomen.
  • De session ID voor het tracken van de user via Server Side Events wordt goed gezet.
  • De CartDrawer wordt geïnitialiseerd en haalt de huidige winkelwagen op, als deze er is.
  • Als we op ‘/cart’ zitten wordt de CartPage geïnitialiseerd.
  • Een link naar de account omgeving of het menu voor ingelogde gebruikers wordt geïnitialiseerd.

Nu we de logica hebben kunnen we de huidige shop aanpassen, zodat deze gebruik gaat maken van de Afosto Storefront.

Scripts inladen

Open het bestand twig/layouts/index.twig en voeg de volgende regel toe.

1{% include 'layouts/storefrontScripts.twig' %}

Dit zorgt ervoor dat het script dat we in de vorige stap hebben aangemaakt, ook daadwerkelijk in de shop gebruikt gaan worden.

Body attributen controleren

Controleer in het bestand twig/layouts/index.twig of er op het body-element de attributen 'data-af-currency' en 'data-af-currency-iso' heeft. Dit moet er ongeveer zo uitzien.

1 <body class="type-{{type}}" data-af-currency="{{properties.currency_symbol}}" data-af-currency-iso="{{properties.currency}}">

Header aanpassen

Open het bestand twig/layouts/header.twig. Pas hier de volgende wijzigingen toe.

Winkelwagen preview button aanpassen

De knop om de Winkelwagen te openen dient ook aangepast te worden. Dit zit in hetzelfde stukje code als het account menu. Pas het volgende aan.

1    <li class="text-center">
2        <a class="cart-toggle" data-toggle="drawer" data-target="#cart-drawer">
3            <i class="fa fa-shopping-cart"></i>
4-           <span class="badge">{{ cart.count }}</span><br>
5+           <span class="badge">-</span><br>
6            <span class="hidden-xs">{{ "Winkelwagen"|t }}</span>
7        </a>
8    </li>	

Winkelwagen drawer aanpassen

Onderaan de header.twig file staat een include regel met bijvoorbeeld:

1{% include 'layouts/cartDrawer.twig' %}

of

1{% include 'layouts/cart-overview.twig' %}
2<div class="cart-overview-underlay"></div>

Vervang deze regel(s) door onderstaande <div /> element. Op deze div wordt namelijk de winkelwagen drawer gekoppeld vanuit de storefront logica.

1<div id="cart-drawer-container"></div>

Account menu aanpassen

Wanneer je verkoopkanaal ondersteuning heeft voor accounts kun je het account menu in de header met onderstaande wijzigingen bruikbaar maken voor de storefront. We lezen in de Afosto shops de cookie van de gebruiker uit en zetten die in de pluginData van de Twig data. Hierdoor kunnen we server side al een naam tonen en checken of iemand is ingelogd. Op basis van die data zou je ook andere functionaliteiten kunnen bouwen voor ingelogde gebruikers.

Als je verkoopkanaal geen gebruik maakt van accounts dan dien je alleen de regels te verwijderen die hieronder rood zijn.

1    <div class="navbar-utilities">
2        <ul class="nav navbar-nav pull-right">
3-           {% if account.is_logged_in is not empty %}
4-               <li class="text-center hidden-xs">
5-                   <a data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="fa fa-user"></i><br><span>{{"Account"|t}}</span></a>
6-                   <ul class="dropdown-menu">
7-                       <li>
8-                           <a href="{{home_url}}account">
9-                               {{"Mijn overzicht"|t}}
10-                           </a>
11-                       </li>
12-                       <li>
13-                           <a href="{{home_url}}account/address">
14-                               {{"Mijn gegevens"|t}}
15-                           </a>
16-                       </li>
17-                       <li>
18-                           <a href="{{home_url}}account/orders">
19-                               {{"Mijn bestellingen"|t}}
20-                           </a>
21-                       </li>
22-                       <li>
23-                           <a href="{{home_url}}account/settings">
24-                               {{"Mijn instellingen"|t}}
25-                           </a>
26-                       </li>
27-                       <li role="separator" class="divider"></li>
28-                       <li>
29-                           <a href="{{account.logout_url}}">{{"Uitloggen"|t}}</a>
30-                       </li>
31-                   </ul>
32-               </li>
33-           {% else %}
34-               <li class="text-center hidden-xs">
35-                   <a href="{{home_url}}login"><i class="fa fa-user"></i><br><span>{{"Inloggen"|t}}</span></a>
36-               </li>
37-           {% endif %}
38+           <li class="text-center" id="account-display">
39+               <a href="#" id="account-link">
40+                   <i class="fa fa-user{% if pluginData['af-sid'].given_name %}-check{% else %}-times{% endif %}"></i>
41+                   <br />
42+                   <span class="hidden-xs account-link-label">
43+                       {% if pluginData['af-sid'].given_name %}{{pluginData['af-sid'].given_name}}{% else %}{{'Inloggen'|t}}{% endif %}
44+                   </span>
45+               </a>
46+           </li>
47            {% if has_app('myWishlist') %}
48                <li class="text-center hidden-xs">
49                    <a href="{{home_url}}wishlist"><i class="fa fa-heart"></i> <span class="badge">{{wishlist.count}}</span><br><span class="hidden-xs">{{"Verlanglijstje"|t}}</span></a>
50                </li>
51            {% endif %}
52            <li class="text-center">
53                <a class="cart-toggle" data-toggle="drawer" data-target="#cart-drawer"><i class="fa fa-shopping-cart"></i> <span class="badge">{{cart.count}}</span><br><span class="hidden-xs">{{"Winkelwagen"|t}}</span></a>
54            </li>
55        </ul>
56    </div>

Mobiele menu aanpassen

In het mobiele menu staat ook een account menu. Het eerder gekopieerde script heeft helaas geen ondersteuning om deze item ook bij te werken. Dit zou je wel kunnen toevoegen, maar gaan we in deze handleiding niet op in omdat hier een andere oplossing voor is gemaakt.

1    <nav id="mobile-menu-container" class="visible-xs visible-sm" style="opacity: 0" data-search-url="{{search_url}}">
2        <ul>
3            {% if not menu %}
4                <li>
5                    <a>
6                        <strong>
7                            No menu defined for 'mmenu' macro
8                        </strong>
9                    </a>
10                </li>
11            {% else %}
12                {% for menuitem in menu.items %}
13                    <li class="{% if menuitem.active %} selected{% endif %} {% if loop.last %} no-border{% endif %}">
14                        <a data-af-href="{{menuitem.url}}" title="{{menuitem.title}}">{{menuitem.label}}</a>
15                        {% if menuitem.items %}
16                            <ul class="vertical">
17                                {% for subitem in menuitem.items %}
18                                    {% if subitem.url == "divider" %}
19                                        <li class="mm-divider">
20                                            {{subitem.label}}
21                                        </li>
22                                    {% else %}
23                                        <li>
24                                            <a data-af-href="{{subitem.url}}" title="{{subitem.title}}">{{subitem.label}}</a>
25                                        </li>
26                                    {% endif %}
27                                {% endfor %}
28                            </ul>
29                        {% endif %}
30                    </li>
31                {% endfor %}
32            {% endif %}
33-           {% if account_logged_in is not empty %}
34-               <li class="text-center mt-auto">
35-                   <a data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="w-fit-content">
36-                       <i class="fa fa-user mr-8"></i>
37-                       <span>{{"Account"|t}}</span>
38-                   </a>
39-                   <ul class="vertical">
40-                       <li>
41-                           <a href="{{home_url}}account">
42-                               {{"Mijn overzicht"|t}}
43-                           </a>
44-                       </li>
45-                       <li>
46-                           <a href="{{home_url}}account/address">
47-                               {{"Mijn gegevens"|t}}
48-                           </a>
49-                       </li>
50-                       <li>
51-                           <a href="{{home_url}}account/orders">
52-                               {{"Mijn bestellingen"|t}}
53-                           </a>
54-                       </li>
55-                       <li>
56-                           <a href="{{home_url}}account/settings">
57-                               {{"Mijn instellingen"|t}}
58-                           </a>
59-                       </li>
60-                       <li role="separator" class="divider"></li>
61-                       <li>
62-                           <a href="{{account_logout_url}}">{{"Uitloggen"|t}}</a>
63-                       </li>
64-                   </ul>
65-               </li>
66-           {% else %}
67-               <li class="d-flex justufy-content-center">
68-                   <a href="{{home_url}}login" class="text-center w-fit-content">
69-                       <i class="fa fa-user mr-8"></i><span>{{"Inloggen"|t}}</span>
70-                   </a>
71-               </li>
72-           {% endif %}
73        </ul>
74    </nav>

Winkelwagen pagina aanpassen

Maak in de map twig/cart het bestand cart-storefront.twig bestand aan. Plak in dit bestand de code die je op onderstaande link kunt vinden.

https://gist.github.com/gijsbotje/d495615762ac9e15fb28b8d866ff97b5

Open index.twig in dezelfde map en verander de waarde van code naar storefront.

1{% set code = "storefront" %}
2
3{% include ['cart/'~ code ~ '.twig','cart/default.twig'] %}

Product pagina aanpassen

Voor de product pagina dient er alleen een extra verborgen veld te worden toegevoegd. Naast product_id en price zijn we ook de sku nodig voor het toevoegen aan de winkelwagen. Zoek in de code van jouw product pagina naar het formulier met action="{{cart_url}}". Dit is het formulier dat het product aan je winkelwagen toevoegt. Zoek hier het veld product_id of price op en voeg onderstaande regel toe binnen het formulier.

1  <input type="hidden" value="{{ price }}" name="price"/>
2  <input type="hidden" value="{{ id }}" name="product_id"/>
3+ <input type="hidden" value="{{ sku }}" name="sku"/>

Controleer ook of het formulier de volgende attributen heeft, zodat de JavaScript de juiste referentie heeft voor het formulier.

1<form
2    action="{{ cart_url }}"
3    method="post"
4    id="product-form"
5    data-cart-url="{{ home_url }}cart"
6    data-product-url="{{ url }}"
7>

Quickview content aanpassen

Mocht je webshop gebruik maken van de quickview functionaliteit, dan dien je hier dezelfde aanpassen te doen aan het formulier als op de productpagina. Dit kun je vinden in het bestand twig/product/quickview.twig.

Voeg het SKU-veld toe naast de product_id en price velden.

Controleer of het formulier de volgende attributen heeft, zodat de JavaScript de juiste referentie heeft voor het formulier.

1<form
2    action="{{ cart_url }}"
3    method="post"
4    id="quick-view-form"
5    data-product-url="{{ url }}"
6    data-cart-url="{{ home_url }}cart"
7    data-form-input="[{'product_id' : '{{ id }}', 'quantity' : '1', 'price' : '{{ price }}'}]"
8>

Wishlist aanpassen

Als jouw shop gebruik maakt van de wishlist functionaliteit, moet deze ook aangepast worden voor de Afosto Storefront. Hier dien je ook een SKU-veld toe te voegen.

1  <input type="hidden" value="{{ product.id }}" name="product_id"/>
2  <input type="hidden" value="{{ product.price }}" name="price"/>
3+ <input type="hidden" value="{{ product.sku }}" name="sku"/>
4  <input type="hidden" value="1" name="quantity"/>

Controleer of het formulier de volgende attributen heeft, zodat de JavaScript de juiste referentie heeft voor het formulier.

1<form
2    action="{{cart_url}}"
3    method="post"
4    id="add-product-{{product.id}}"
5    class="wishlist-product-form"
6    data-cart-url="{{home_url}}cart"
7    data-product-url="{{url}}"
8>

Product grid aanpassen

Veel webshops hebben in het producten grid een knop voor direct toevoegen aan de winkelwagen. Voor dit formulier moeten we ook zorgen dat de 3 benodigde velden er in staan en het formulier de juiste attributen heeft.

1+ <form
2+     action="{{product.cart_url}}"
3+     method="POST"
4+     id="product-form-{{product.id}}"
5+     data-cart-url="{{home_url}}cart"
6+     class="grid-product-form"
7+     data-product-url="{{product.url}}"
8+     data-form-input="[{'product_id' : '{{product.id}}', 'quantity' : '1', 'price' : '{{product.price}}'}]"
9+ >
10-     <input type="hidden" name="product_id" value="{{product.id}}"/>
11+     <input type="hidden" value="{{ product.price }}" name="price"/>
12+     <input type="hidden" value="{{ product.id }}" name="product_id"/>
13+     <input type="hidden" value="{{ product.sku }}" name="sku"/>
14      <button type="submit" class="btn btn-primary btn-sm" aria-label="{{'Toevoegen aan winkelwagen'|t}}">
15          <i class="fa fa-shopping-cart fa-lg"> </i> &nbsp;
16          <i class="fa fa-plus fa-lg"> </i>
17      </button>&nbsp;
18  </form>

Mocht je op nog meer plekken een product formulier hebben staan, pas die dan ook aan.

Cart JavaScript aanpassen

Open het bestand assets/js/cart.js en verander de inhoud naar het volgende. De functies die hier werden aangemaakt zijn in storefrontScripts.twig aangemaakt voor de communicatie met de Afosto Storefront.

1$('body').on('cart:updated', function(event, data) {
2    if (!!data) {
3        var count = ((data && data.items) || []).reduce(function(acc, item) {
4            return acc + item.quantity;
5        }, 0);
6        
7        $('.cart-toggle').find('.badge').text(count);
8    }
9});

Product JavaScript aanpassen

Zoek de functie retrieveFormInput in het bestand assets/js/product.js. Vul de array approvedAttributes aan met SKU.

1- var approvedAttributes = ['product_id', 'price', 'quantity'];
2+ var approvedAttributes = ['product_id', 'price', 'quantity', 'sku'];

Voeg in hetzelfde bestand ergens bovenaan onderstaande functie toe. Deze functie transformeert een error response van de Afosto Storefront naar een leesbare melding.

1function getErrorMessage(error) {
2    var errorResponse = error.response || {};
3    var errorResponseData = errorResponse.data || {};
4    var errorResponseError = errorResponseData.error || {};
5    var gqlResponseErrors = errorResponse.errors || [];
6    var firstGqlError = (gqlResponseErrors || [])[0];
7    var gqlErrorExtensions = firstGqlError.extensions || {};
8    var pointers = (errorResponseError.details && errorResponseError.details.pointers) || (firstGqlError.extensions && firstGqlError.extensions.pointers);
9    var firstPointer = (pointers || [])[0];
10
11    return (
12        (firstPointer && firstPointer.message) ||
13        (errorResponseError && errorResponseError.message) ||
14        (errorResponseData && errorResponseData.message) ||
15        (firstGqlError && firstGqlError.message)
16    );
17}

Zoek de functie handleAddToCartAjax in hetzelfde bestand op. Vervang deze hele functie door onderstaande code. Dit zorgt er voor dat het toevoegen van de producten via de formulieren wordt gedaan via de Afosto Storefront.

1function handleAddToCartAjax(e) {
2    var form = e.data.form;
3    var modal = e.data.modal;
4    var url = $(this).attr('action');
5    
6    formid = "#" + $(this).attr('id');
7    form = $(formid);
8    
9    var form_data = form.serialize();
10    
11    var product_url = form.data('product-url');
12    var cart_url = form.data('cart-url');
13    
14    var event_data = retrieveFormInput(form);
15    var formattedItems = event_data.map(function(item) {
16       return { sku: item.sku, quantity: parseInt(item.quantity, 10) };
17    });
18    
19    if(modal == '#quick-view-modal') {
20        $(modal).modal('show');
21        $(modal + " .modal-body").html('<i class="fas fa-cog fa-spin fa-4x text-muted"></i>');
22        $(modal + " .modal-body").addClass('text-center');
23        $(modal + " .modal-header .modal-title").html($(modal).data("loading-title"));
24        $(modal + " .modal-footer").addClass('hidden');
25    }
26    
27    Storefront.addCartItems(formattedItems)
28        .then(function(result) {
29            console.log(result);
30            $(modal).modal('hide');
31            openDrawer('#cart-drawer');
32            $('.cart-toggle').trigger('cart:updated', result);
33        })
34        .catch(function(error) {
35            $(modal + " .modal-header .modal-title").html($("#add-response-modal").data("error-title"));
36            $(modal + " .modal-body").addClass('text-center');
37            $(modal + " .modal-body").html('<span class="fa-stack fa-3x text-warning"><i class="far fa-circle fa-stack-2x"></i><i class="fas fa-exclamation fa-stack-1x"></i></span><p class="lead mt-15">' + getErrorMessage(error) + '</p>');
38            $(modal).modal('show');
39        });
40    
41    e.preventDefault();
42}

Controleer in hetzelfde bestand of de volgende regels in de functie startProductPage staan. Hier wordt de logica gekoppeld aan de formulieren die we in voorgaande stappen bij langs zijn gegaan.

1if ($('#product-form').length > 0) {
2    AddToCartAjax('#product-form', '#add-response-modal');
3}
4if ($('.grid-product-form').length > 0) {
5    AddToCartAjax('.grid-product-form', '#add-response-modal');
6}
7if ($('.wishlist-product-form').length > 0) {
8    AddToCartAjax('.wishlist-product-form', '#add-response-modal');
9}
10if ($('#wishlist-to-cart-form').length > 0) {
11    AddToCartAjax('#wishlist-to-cart-form', '#add-response-modal');
12}

Heb je nog meer custom formulieren, voeg deze hier dan ook toe en zorg dat de benodigde attributen op het formulier staan.

Integratie testen

Met deze wijzigingen is je webshop klaar voor de Afosto Storefront. Test alle formulieren en de winkelwagen zodat je zeker weet dat alles werkt. Test ook de gehele flow tot aan de checkout.

Kom je er niet uit of heb je vragen? Stuur ons dan een bericht op support@afosto.com.