Updating Product Lists with Ajax and Handlebars
You may be working through the creation of a store and want to add the ability to filter through products based on different criteria without constantly directing the user to a new page with their filtered results. Amazing, we have been there! In this post we are going to go over how you can implement filtering using Ajax. We will filter on product tags, but the same principles can apply to different criteria.
This tutorial assumes you have a store set up with some different products and the products have been given some tags.
The Files we will be working with
To start let's create the files we will need to accomplish the fitlering. We are going to make two new files. First, let's create a js file to store the javascript for the filtering. You can add this to an existing js file, but to keep things organized and for the purpose of this tutorial we will make a new one
- your site
-- assets
--- product-filter.js.liquid
We are going to call ours product-filter.js.liquid you can name it whatever you like, what is important is the .js.liquid extension
Next we are going to make a new snippet file. This is where we will create the Handlebars template for the product.
- your site
-- snippets
--- product-filtered-template.liquid
We named ours product-filtered-template.liquid
Once you've created both files open your layout > theme.liquid and link the js file and include the template file in the head as follows:
{% include 'product-filtered-template' %}
{{ 'product-filter.js' | asset_url | script_tag }}
Filtering Products by Tags in the Collection Template
Before we jump in to the code there are a few setup things we'll go over. First open the template where you want to include the filtering. You could do this on the collection.liquid or a custom collection template or maybe your index.liquid. Make sure whatever template you choose there is a collection object available. If not, assign the collection to whatever collection you want to filter as follows:
{% assign collection = collections['the_collection_name'] %}
This will assign collection to whatever collection name you specify.
We will display our filtered products on the home page (index.liquid). We are also going to filter through the products in all the collections. For this we will assign collection at the top of the index page like this:
{% assign collection = collections['all'] %}
Next make sure there are products in the collection you are filtering through and these products have some tags assigned.
Let's get the markup for our filters all set. We are going to set up our filters on templates > index.liquid where we will be filtering through the products by tags on the home page. We are going to use a select tag to create a drop down and fitler for one tag at a time. You could adapt this same strategy to use a different type of element (for example a radio button).
Add the following block of code at the top of the page (or wherever you would like your filter drop down).
This should create a select drop down with a list of all the tags you have assigned to your products in the given collection.
Using the Shopify API
Now that we can select which product we want from the filter it's time to actually get the filtered data for our products.
Let's take a look at the Ajax API reference for the products. You can find the full documentation here.
We want to return products that match the tag the suer selects from the drop down.
With that in mind our endpoint will look something like this:
http://mystore.myshopify.com/products.json
Test this out in the browser. You should get back an object with an array of all the products in the shop. We are going to filter through this in the upcoming step to just get the products that whose tags match the selected tag.
Setting Up the Ajax Request
Great! We know what endpoint we need to hit. Let's set up some javascript to:
- Make the Ajax request to get all the products.
- Filter through the results of the ajax request to get only products that are tagged with the selected tag.
- Retrieve the user's selection from the dropdown select and pass it to our request function.
1. Function for Ajax Request
Open up product-filter.js.liquid. First we are going to make a function that we can call to make the Ajax request to get all our products. It will look like this
function getFilterProducts(tag){
$.ajax({
type: 'GET',
url: '/products.json',
dataType: 'json',
success: function(res){
console.log(res);
},
error: function(status){
alert(status);
}
})
}
Our function takes in one parameter for the tag that the user selected. We won't use this right away in our Ajax request, but we will use it later for filtering.
We are using the jQuery.ajax() method to facilitate the request. Here we have access to the success function, that will be called if the request succeeds. We are passing this one argument, the res, the data returned from the server. For now we will just log res to the console to take a look at our returned data.
Open up your console and try testing out the getFilterProducts() function. Pass in an argument of a string matching one of your tags, though this will not yet have any bearing on the results. If your request is successful you should see some product data logged in the console. The Ajax API returns an object with a key products that has a value of an array with all of your store products. Open it up and take a look around 👀
2. Function to Filter Products
Next let's make a filtering function that we will call in the success of our Ajax request. This function will filter all of the product results and make a new products array that includes only products with a tag matching the selected tag. It will look something like this:
function filterProducts(products, tag) {
var filteredProducts = products.filter(function(product){
return product.tags.indexOf(tag) > -1;
});
}
Here we are using the filter method to filter through our array of resutls to create a new array, filteredProducts, that only holds products where the selected tag is included in the product tags.
The function takes 2 parameters: first the array of products returned from server and second the tag selected by the user.
3. Retrieving the users drop down selection
The final step to bring these two functions together is to retrieve the users selected tag. Inside of a jQuery document.ready(). The code will look soemthing like this:
$(function(){
...
$('.js-collection-filter').on('change', function(){
var tag = $(this).val()
getFilterProducts(tag)
});
...
});
Every time a change is made to the select we will run the getFilterProducts() function passing in the selected value as the tag.
4. Pulling it all together
Now that we have our three functions let's see how we can pull them all together to make a request based on a user's selection and get filtered results. The code will look like this:
function getFilterProducts(tag){
$.ajax({
type: 'GET',
url: '/products.json',
dataType: 'json',
success: function(res){
filterProducts(res.products, tag)
},
error: function(status){
alert(status);
}
})
}
function filterProducts(products, tag) {
var filteredProducts = products.filter(function(product){
return product.tags.indexOf(tag) > -1;
});
}
$(function(){
$('.js-collection-filter').on('change', function(){
var tag = $(this).val()
getFilterProducts(tag)
});
});
On change of the select menu call the getFilterProducts function passing in the selected tag. Then on success of the Ajax call, call filterProducts and pass in the server response products array and the tag. Finally the filter function will go through each product objects in the products array and include it in the new filteredProducts array if the tags array on the product includes the selected tag.
If you log filteredProducts to the console you should get an array of all the products that have a tag matching the selected one. This is great, but how do you display the products on the page?
Templating with Handlebars.js
Handlebars.js is a great minimal templating language. We can pass a data object to the handlebars template and append the template to our view. An added bonus: the syntax is not dissimilar from liquid.
To start include the Handlebars minified js file in your assets folder. You can download it here. Next link the file in the head of your layout > theme.liquid like this:
...
{{ 'handlebars.min.js' | asset_url | script_tag }}
...
Now you are ready to start using Handlebars. Open up the tempalte file we made at the beginning in snippets > product-filtered-template.liquid. We are going to serve the template to the browser sing a script tag and then compile it with javascript. Let's start by adding the script tag to our template file like this:
We'll give it an id to reference later in our js file and a type attribute of text/template.
Let's also go into our index.liquid and add an element that will be the container where we append our filtered products. We will give ours an id="js-collection-filter3". You can nest the product loop in this element so when you filter you will replace all products with just the filtered ones.
{% for product in collection.products %}
{% include 'product-loop' %}
{% endfor %}
Now, back in our product-filter.js.liquid, we are going to make a function that will set up all our product data and pass it to our Handlebars template. It will look like this:
function buildFilteredProducts(filteredProds) {
var $productContainer = $('#js-product-container');
$productContainer.empty();
var products = [],
product = {},
data = {},
source = $("#ProductTemplate").html(),
template = Handlebars.compile(source);
products = filteredProds.map(function(productItem) {
return product = {
id: productItem.id,
description: productItem.body_html.replace(/<\/?[^>]+(>|$)/g, "").split(' ').join(' '),
title: productItem.title,
price: Shopify.formatMoney(productItem.variants[0].price, {{ shop.money_format | json }}),
featuredImg: productItem.images[0].src
}
});
data = {
product: products
}
$productContainer.append(template(data));
}
This function will take one parameter, the array of filtered products. Let's break down what we are doing here. First we create a variable for the container element. This is where we will append the the products that we create with the handlebars template. In this case we are going to use an element with an id of js-product-container that we just made in the index.liquid.
Amazing! Jumping back to our buildFilteredProducts function, we can see the next thing we do is empty the product container before appending the filtered products.
The next thing we do is define some variables we will be using.
- products is an empty array we will push each product into.
- product is an empty object that will hold the data for each product.
- data is an empty object that will store all of the data we will pass to our handlebars template.
- source is the source for the Handlebars template. This is what we set up earlier in our snippet.
- template is the Handlebars compiled template that we will append to the page.
Once we have defined all of our variables we are going to map through the array of filtered products and create our new array called products with only the info we need for each product object. Once we are done mapping we will set a key called product in our data object that will have a value of our new products array. Finally, we pass the data object to the template and append it to the product container.
Setting Up the Handlebars Template
Back in product-filtered-template.liquid let's add some code to our template. We'll start by wrapping all of our handlebars content between raw tags. We are then going to iterate through each object in for the data.product and add the content for the product like this:
{{#product}}
{{title}}
{{price}}
{{description}}
{{/product}}
and that's should be it!
go ahead and test out your filtering 😃