Skip to content

Latest commit

 

History

History
397 lines (307 loc) · 9.89 KB

README.md

File metadata and controls

397 lines (307 loc) · 9.89 KB

Vue plugin to ease Shopify products and cart management. Can be combined with WP-Shopify for Vue + WordPress.

Table of Contents

  1. Installation
  2. Reference
    1. Mixin
    2. Store

Installation

If you're combining this plugin with its optional WordPress backend, follow the instructions on that repo first.

1. Set up Shopify.
  1. Set up a Shopify store and create a new private app.

  2. Go to your Shopify admin panel (your-store.myshopify.com/admin).

  3. Click "Manage Private Apps" at the bottom of the screen. You'll end up on your-store.myshopify.com/admin/apps/private.

  4. Click "Create a new private app."

  5. Keep the default API permissions and make sure Allow this app to access your storefront data using the Storefront API at the bottom of the screen is checked.

  6. Hit Save to continue.

  7. Note the Storefront Token on the bottom of the next page:

    !Image of the Shopify storefront access token location

  1. npm install wp-shopify

  2. Install Vuex.

  3. Before creating your Vue instance, install the plugin with:

    import WpShopify from 'wp-shopify'
    import store from 'your-main-vuex-store'
    
    Vue.use(WpShopify, {
        // your Shopify domain, formatted: my-site.myshopify.com
        // (Vuepress default shown)
        domain: jsonData.site.shopifyDomain,
        // your Shopify storefront access token
        // (Vuepress default shown)
        token: jsonData.site.storefrontToken,
        store: store, // your Vuex store
        wordpress: true // whether or not to include WordPress data - default false
    })

That's it! You've got full access to the store module, mixin, and instance variables below.

The Mixin

The plugin automatically adds the following mixin data on all components:

{
    props: {
        productId: {
            type: String,
            default: ''
        }
    },
    data() {
        return {
            selectedVariantIndex: 0,
            product: null
        }
    },
    async mounted(){
        /*
        The mounted function checks to see if the `productId` prop is set.
        If it is, mounted() sets `this.product` to the result of `this.getProduct(this.productId)`.
        */
    },
    methods: {
        async getProduct(id){
            /*
            `getProduct` fetches and returns the product with the given ID from Shopify.
            First, it checks the cached data in `$store.state.shopify.productData[id]`;
            if nothing is found, it builds and executes a query for the product data from Shopify, caching the result.
            */
        },
        async getVariant(variant, product){
            /*
            TODO
            */
        },
        addToCart(product){
            /*
            TODO
            */
        }
    },
    computed: {
        selectedVariant(){
            /*
            Returns either the currently selected variant or,
            if none is selected or no product is present, null.
            */
        }
    }

}

Product Data

Data fetched with getProduct is formatted like this:

{
    // Shopify ID (base64-encoded string from Shopify)
    id: 'Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0Lzk1NTIzMjI1NjAzNg==',

    // Shopify title
    title: 'My Product',

    // Shopify description as raw HTML
    descriptionHtml: '',

    // List of variants
    variants: [
        {
            // Price for this variant
            price: '30.00',
            
            // Compare to sale price for this variant
            compareAtPrice: '30.00',

            // Variant title
            title: 'Small',

            // Whether or not this variant is available
            availableForSale: true,

            // Variant ID
            id: 'Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0VmFyaWFudC85NDE3NTIzMzMxMTA4'
        }
    ],

    // If `wordpress` was set to `true` when installing plugin:
    wp: {
        // relative path to this product in WordPress
        path: '/store/sample-product/'
    }
}

Store Module

WP-Shopify adds a module called shopify to the Vuex store with the following state, mutations, and actions:

state: {
    // Cache for fetched product data
    productData: {},

    // load initial cart state from localStorage (defaults to empty array)
    cart: loadCart(),

    // domain and token (set in installation)
    domain: '',
    token: '',

    // since we can't deep-watch the cart for quantities, etc, the store
    // watches this value and increment every time the cart is modified
    // (this is for internal use only)
    cartVersion: 0,

    // checkout URL as calculated from cart contents
    checkoutUrl: '',

    // subtotal of all items in cart
    subtotal: ''
},
mutations: {
    UPDATE_CACHED_RESULT({ shopifyId, data }) {
        /*
        Updates `state.productData` entry for `shopifyId` with new `data`
        */
    },
    ADD_TO_CART({ variant, quantity }) {
        /*
        Adds `quantity` (default 1) of the given `variant` to the cart.
        Updates localstorage and the checkout URL.
        */
    },
    SET_QUANTITY({ variant, quantity, changeBy }) {
        /*
        Either set the `variant` to a specific `quantity` or change its current
        `quantity` by `changeBy` units. Updates localstorage and the checkout URL.
        */
    },
    REMOVE_FROM_CART({ variant }) {
        /*
        Removes all of a given `variant` from the cart.
        Updates localstorage and the checkout URL.
        */
    },
    EMPTY_CART() {
        /*
        Removes everything from the cart.
        Updates localstorage and the checkout URL.
        */

    },
    UPDATE_CHECKOUT() {
        /*
        Manually refresh the checkout URL. Usually called internally.
        */
    },
    SET_DOMAIN_AND_TOKEN({ domain, token, force }) {
        /*
        Update the Shopify domain and token.
        Set `force` to `true` to update even if a Shopify domain and token already exist.
        */
    }
},
actions: {
    async GET_PRODUCT_DATA(
        { shopifyId, domain, token }
    ) {
        /*
        Fetch and cache a product's data given its ID.
        Should not be called manually - use `getProduct` from the mixin instead.
        */
    }
}

Product Templating

Basic Templating

All of a product's Shopify data is contained in the product object. If you know the ID of your product, you can retrieve the full object with getProduct.

<template>

    <section>
        {{ product ? product.title : '' }}
    </section>

</template>

<script>
export default {
    mounted() {
        // this is how the WordPress plugin retrieves a product...
        const productId = this.$store.getters.post.productId

        // ...but any valid ID will work
        this.product = this.getProduct(productId)
    }
}
</script>

You can also pass the ID as a prop called product-id to automatically fetch a product on a custom component:

Main.vue

<template>

    <product-example :product-id="'your product ID here'"/>

</template>

ProductExample.vue

<template>

    <div class="product-example">
        {{ product ? product.title : '' }}
    </div>

</template>

Variants

A product usually has at least one variant - different colors, sizes, etc. - that you'll need to account for.

The easiest way to handle this is to use the built-in selectedVariant system. For example:

<template>

    <h2 v-if="!product">Loading...</h2>

    <main v-else>

        <h2>{{ product.title }}</h2>

        <h3>Currently selected: {{ selectedVariant.title }} (${{ selectedVariant.price }})</h3>

        <select v-model="selectedVariantIndex">
            <option
                v-for="(variant, i) in variants"
                :key="i"
                :value="i">

                {{ variant.title }}

            </option>
        </select>

        <button @click="addToCart()">
            Add to Cart
        </button>

    </main>

</template>

And an annotated version of the above:

```html
<template>

    <!-- Product loading state -->
    <h2 v-if="!product">Loading...</h2>

    <!-- Product loaded and ready -->
    <main v-else>

        <h2>{{ product.title }}</h2>

        <h3>Currently selected: {{ selectedVariant.title }} (${{ selectedVariant.price }})</h3>

        <!-- Changing the selectedVariantIndex changes the selectedVariant -->
        <select v-model="selectedVariantIndex">
            <option
                v-for="(variant, i) in variants"
                :key="i"
                :value="i">

                {{ variant.title }}

            </option>
        </select>

        <!-- addToCart is another function from the mixin -->
        <button @click="addToCart()">
            Add to Cart
        </button>

    </main>

</template>

Cart

A shopping cart might look like this:

<template>
    <div class="shopping-cart">
        <ul>

            <li v-for="(product, i) in $store.state.shopify.cart" :key="i">
                <span>{{ product.title }}</span>
                <span>({{ product.quantity }})</span>
            </li>

        </ul>

        <a :href="checkoutUrl" target="_blank" rel="noopener noreferrer">Checkout</a>
    </div>
</template>

Annotated:

<template>
    <div class="shopping-cart">
        <ul>

            <!-- Shopify data is kept in a Vuex module called 'shopify' -->
            <li v-for="(product, i) in $store.state.shopify.cart" :key="i">
                <span>{{ product.title }}</span>
                <span>({{ product.quantity }})</span>
            </li>

        </ul>

        <!-- The checkout URL is automatically updated any time the cart changes -->
        <a :href="checkoutUrl" target="_blank" rel="noopener noreferrer">Checkout</a>
    </div>
</template>