Storefront API
The add-on includes three theme files that can help you quickly spin up an example integration. These are rather simple use cases that give us enough to integrate the JavaScript and control adding products to our basket, updating it, and handing it off to Shopify for checkout.
The following breaks down the JavaScript included and the steps needed to get everything working.
Publishing Assets
Before we can talk about what anything achieves, we need to publish the boilerplate assets. Run the following command to get the uncompiled JavaScript into your resources directory.
php artisan vendor:publish --tag="shopify-scripts"
For the theme files you can also run:
php artisan vendor:publish --tag="shopify-theme"
Initiating
The first thing you need is to setup the client.
The client.js
uses the tokens output by {{ shopify:tokens }}
. You will need to install the @shopify/storefront-api-client
before anything will compile.
yarn add @shopify/storefront-api-client
npm install @shopify/storefront-api-client --save
client.js
Our client.js
is a rather simple file, it uses the shopifyConfig
created by {{ shopify:tokens }}
to create a connection to Shopify's GraphQL API. You'll need to use this whenever you want to interact with the API, so we have separated it into it's own JS file so you can import it where needed.
import client from 'vendor/shopify/client';
cart.js
This file provides methods for creating, updating and reading from the cart associated with the current user. You can import any of the following methods. Note that all methods are async
.
createFreshCart
createFreshCart(?array: lines)
Creates a brand new cart. If an array of cart lines are passed into the function, they will be added to the cart.
getExistingCart
getExistingCart(string: cartId)
Returns the cart identified by the passed id.
getOrCreateCart
getOrCreateCart(?string: cartId)
Gets a cart that matches the id. If one cannot be found, then a new cart will be returned instead.
setCartAttributes
setCartAttributes(string: cartId, array: lines)
Set attributes associated with the cart. This will overwrite previously set attributes if you have any. This should be an array of objects consisting of a key
and value
. Eg:
[
{
key: 'my_key',
value: 'my_value'
},
{
key: 'my_key',
value: 'my_value'
}
]
setCartNote
setCartNote(string: cartId, string: note)
Sets a note that will be attached to the cart. This will overwrite existing cart notes.
addLines
addLines(string: cartId, array: lines)
Adds an array of product lines to the cart. See Shopify's documentation for more information.
removeLine
removeLine(string cartId, string lineId)
Remove the line identified by line id from the cart.
updateLineQuantity
updateLineQuantity(string cartId, string lineId, int quantity)
Updates the quantity of a specified cart line.
alpine.js
This file provides an Alpine.js store and helper to make getting up and running that much quicker. The published views assume this code is available and being included in your site.js:
import { createData, createStore } from './vendor/shopify/alpine';
import Alpine from 'alpinejs';
window.Alpine = Alpine;
createStore();
createData();
Alpine.start();
Alpine.data('shopifyProduct')
This Alpine helper helps you take the output of {{ shopify:variant:generate }}
and turn into a dependent set of drop downs. In addition it provides some useful methods for checking if the selected variant is in stock, or requires further options to be selected.
To use it:
{{ $variants = {shopify:variants} }}
<div x-data='statamic.shopify.product({{ options | to_json }}, {{ variants | to_json }})'>
<form @submit.prevent="handleSubmit($event.target)">
<input type="hidden" name="product_id" id="ss-product-id" value="{{ product_id }}" />
{{ shopify:variants:generate show_price="true" show_out_of_stock="true" }}
<div x-show="variants.length > 1">
<template x-for="(option, index) in options">
<div>
<label x-text="option"></label>
<select @change="optionChange(index, $event.target.value)">
<option disabled x-text="'Choose ' + option"></option>
<template x-for="value in getOptions(index)">
<option :value="value" x-text="value" :selected="(selected[index] ?? false) == value">
</template>
</select>
</div>
</template>
</div>
<div>
<input type="number" min="1" value="1" x-ref="qty" name="quantity" />
</div>
<button type="submit" :disabled="! (allOptionsSelected() && variantExistsAndIsInStock())">Add to Cart</button>
</form>
</div>
The handleSubmit
method makes use of the cart.js
methods to update the cart with the line items to be added.
Alpine.store('statamic.shopify.cart')
This store provides client side cart functionality and methods that are needed to allow users to update their cart on the site.
The cart reference is stored in localStorage so that it persists across page loads and new carts are not unecessarily created.
This snippet from the cart.antlers.html shows you how it can be used to display the line items in the basket to the end user.
<template x-for="line in Alpine.store('statamic.shopify.cart').lineItems">
<tr>
<td colspan="2">
<div class="flex items-center">
<div class="mr-3">
<picture class="aspect-square overflow-hidden block relative w-20 h-20" x-show="line.image">
<img :src="line.image" :alt="line.title" loading="lazy" class="pin-0 absolute object-cover">
</picture>
</div>
<div>
<span class="block font-semibold" x-text="line.title"></span>
<spanx-text="line.variant.title"></span>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap" x-html="line.price"></td>
<td class="px-6 py-4 whitespace-nowrap">
<input type="number" name="qty" min="1" class="border w-20 p-1" :value="line.qty" @change="Alpine.store('cart').updateQuantity(line.id, $event.target.value)" />
</td>
<td class="px-6 py-4 whitespace-nowrap" x-html="line.subtotal"></td>
<td class="px-6 py-4 whitespace-nowrap">
<a href="#" @click.prevent="Alpine.store('cart').removeLine(line.id)" class="text-red-600"><svg class="w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg></a>
</td>
</tr>
</template>