Adding Related Products to the Product Page with Vue.js

Shopify has almost everything you want out of the box for a basic online store. But one thing that clients love to do to drive more sales is display related or recommended products on a product page.

Although this isn't a feature out of the box with Shopify, the combination of Metafields and Vue.js allows you to create this feature AND allows the shop owner to update these themselves 🎉.

The Setup

The thing that makes Vue.js ideal for this type of Shopify integration, is that you can bring it in through a CDN. So the first step is to add the CDN to the product template (or wherever you'll be writing your JS)

We'll also want to add this line in our js to indicate that we're in development mode and to allow us to use Vue developer tools.

Vue.config.productionTip = false

Now you'll want to create a div with an id. This is where the Vue.js code will be served into. We created a div with the id of app and also a class of vue-form for styling


    

The basic set up for is to create a variable and new Vue({}) and add the required info the the object. The following 3 keys are the basics of Vue. el is for the element that Vue will render into. In our case we created the

so we put code '#app'. The next is the data object - this is where we will store the data and it will be available through Vue directives. The methods object will store all the functions that will also be available through directives and act on the data.


    var demo = new Vue({
      el: '#app',
      data: {},
      methods: {}
    })
  

There's a little issue here however. We use the {{ }} for rendering in liquid, but Vue also uses {{ }} for rendering. Luckily 🍀, Vue gives us the options to change this with the delimiters key. In the code below you can see that I added delimiters, which is an array. The first value is the opening delimiter and the second is the closing. So in our case wel will now use ${ } for rendering in Vue.


    var demo = new Vue({
      delimiters: ['${', '}'],
      el: '#app',
      data: {},
      methods: {}
    })
  

The Data

We want to allow the shop owner (likely a non-coder) to update the related products on their own. We do this with Metafields.

The ShopfiyFD chrome extension will add a Metafields editor to the product page. You'll be able to add a namespace, a key, and a value


    namespace: add_product
    key: handle1  ← this has to be unique, so I just append a different number onto the end of the word
    value: latte-cup  ← this is the handle of the product and will be how we access the product info
  
images

Once the metafields are set up on the product we loop through them in JavaScript to create the data that will be added to the Vue data object.


    var custom_products = []
     {% for item in product.metafields.add_product %}
       {% assign add_handle = item | last %}
       var object = {}
       object.name = "{{ item | last }}"
       object.price = {{ all_products[add_handle].price | money_without_currency }}
       object.title = "{{ all_products[add_handle].title }}"
       object.image = "{{ all_products[add_handle].featured_image | img_url }}"
       object.id = "{{ all_products[add_handle].id }}"
       object.active = false
       object.product_class =  "line_item"
       custom_products.push(object)
     {% endfor %}
  

First we create a custom_products array, the we do a liquid loop (this works between the script tags because we're in a .liquidfile.

The metafield returns one of those confusing liquid pseudo arrays. It returns the key and value. In this case we only want the value, so we set add_handle as the last element in the item.

We create an empty object and then we add all the info we need. We again use liquid tags too capture the data using the Shopify object all_products. We grab the specific product with the handle (which has been set to variable add_handle, so as not to confuse with handle that may be potentially assigned earlier in the template). Then we access each attribute by it's name. For our purposes, we need the price, title, image url, and id. We then also add the active flag as false and a class as "line_item".

Once each object is created we push it into the custom_products array. The result will looks something like this:


    [
     {
       name: "stovetop-espresso-maker",
       price: 10,
       title: "coffee beans",
       image: "//cdn.shopify.com/s/files/1/1518/2706/products/nathan-dumlao-507143-unsplash_small.jpg?v=1531329097",
       id: "1337387286563",
       active: false,
       product_class: 'line_item'
     },
     {
       name: "coffee-beans",
       price: 16 ,
       title: "Latte Cup" ,
       image: "//cdn.shopify.com/s/files/1/1518/2706/products/nathan-dumlao-471723-unsplash_small.jpg?v=1531329345",
       id: "1337388433443",
       active: false,
       product_class: 'line_item'
     },
     {
       name: "latte-cup",
       price: 35 ,
       title: "Stovetop Espresso Maker",
       image: "//cdn.shopify.com/s/files/1/1518/2706/products/alexandra-gorn-325610-unsplash_small.jpg?v=1531328653",
       id: "1337387384867",
       active: false,
       product_class: 'line_item'
     }
           
  ]
  

Then we add the array to the data object in Vue


    var demo = new Vue({
      delimiters: ['${', '}'],
      el: '#app',
      data: {
        custom_products: custom_products
      },
      methods: {}
    })
  

Displaying the Data

Now that we have our data object set up, we can use Vue directives to bind it to the page. The following code is put inside the div with the id of app we made earler


    

Additional Products

  • ${custom_product.title }... ${custom_product.price | currency}
Total: ${total() | currency}

First we add a title and create a ul. Then we create 1 li element and we use the v-for directive to loop through the custom_products (in Vue directives you omit the data object, as it assumes that is where it is coming from). Each time it loops through, it will create an li element with the data we provide with the ${ } tags. As you can see here we're display the title and the price.

We're also displaying the image, but in Vue, we use the v-bind custom directive with src rather than using the template tags.

We've also created a filter currency to make our lives a little easier. Luckily, Vue provides a filters key in which we can add functions. We'll demonstrate by integrating with our existing Vue object.


    var demo = new Vue({
      delimiters: ['${', '}'],
      el: '#app',
      data: {
        custom_products: custom_products
      },
      methods: {},
      filters:{
        currency: function(value) {
          return '$' + value.toFixed(2);
        }
      }
      
    })
  

Adding some interactivity

Displaying the products is already a win 🎉. But let's go that extra step further and let the user select the products and see an accumulated total.

For this we'll be adding 2 methods to our Vue methods object

The first is one to toggle a class for active, if the product is select, to give a visual representation of what is selected.


    var demo = new Vue({
      delimiters: ['${', '}'],
      el: '#app',
      data: {
        custom_products: custom_products
      },
      methods: {
      toggleActive: function (s) {
        s.active = !s.active;
      }

    },
      filters:{
        currency: function(value) {
          return '$' + value.toFixed(2);
        }
      }
      
    })
  

Looking at the li that we created earlier, we're now going to v-bind classes to this. The way we do this is pass an array. The first element is saying if the custom_product.active is true put a class of active. The second element is putting the custom_product.product_class, which will be there always.

We then added a v-on:click and use the method we just created, passing in the custom_product object. In the function we toggle the active value.


    
  • ${custom_product.title }... ${custom_product.price | currency}
  • And finally, one more function to accumulate the total of active custom_products.

    
        var demo = new Vue({
          delimiters: ['${', '}'],
          el: '#app',
          data: {
            custom_products: custom_products
          },
          methods: {
          toggleActive: function (s) {
            s.active = !s.active;
          },
          total: function () {
    
            var total = 0;
    
            this.custom_products.forEach(function (s) {
              if (s.active) {
                total += s.price;
              }
            });
    
            return total;
          },
    
        },
          filters:{
            currency: function(value) {
              return '$' + value.toFixed(2);
            }
          }
          
        })
      

    Here we loop through the custom_products and accumulate the price for each active product. Then we return the total. If we look at the markup again, you'll notice when we display the total, we're actually calling the function total(). This way the total keeps updated with every change in the data.

    
        
    Total: ${total() | currency}

    Here's the styles that I'm using, but obviously you can style it to fit with your theme.

    
        
      

    Now it's not in the scope of this tutorial to show how to add all the products to the cart with just one click. However, we have made a tutorial for How to add multiple products with an AJAX cart, which you could combine with the knowledge here to create to full experience. You'd likely want to start by adding a method that would collect the variant ids in Vue, then you could follow that tutorial to pass them to the cart.

    Also, I'd like to thank 5 Practical examples for learning Vue.js from tutorialzine.com for providing the inspiration for the order form.