Shopify Product Grouping - dreamship-dev/dream GitHub Wiki
Step-by-step guide to implement product grouping into any Shopify theme.
Product grouping effectively allows sellers to bypass the 100-variant limit by showing similar designed products on the same page in the same way a product option (color, size, etc.) is displayed. Grouped products can also be shown as thumbnail images.
The grouping is controlled using product tags
in Shopify and can easily be edited in Shopify for any product.
This guide assumes you have some knowledge of Theme development. This walkthrough uses the Dawn theme as a starting point. You may have slight differences in your code if you are using different themes.
Before starting any work its important that you create a duplicate of your existing theme to work on.
- From your Shopify admin, go to Online Store > Themes.
- For the theme that you want to duplicate, click Actions > Duplicate.
View Shopify docs for additional reference.
- From your Shopify admin, go to Online Store > Themes.
- For the theme that you want to edit, click Actions > Edit Code.
- Under Templates, click Add a new template
-
When prompted, create a new template for
collection
, selectliquid
type and call itjson
. -
Select the newly created file
collection.json.liquid
and paste the following code:
{%- layout none -%}
{%- paginate collection.products by 50 -%}
[
{%- for product in collection.products -%}
{%- if product.metafields.dreamship.product -%}
{%- assign title = product.metafields.dreamship.product -%}
{%- else -%}
{%- assign title = product.title -%}
{%- endif -%}
{"id": {{- product.id -}},"title": "{{- title -}}","url": "{{- product.url -}}","thumbnail": "{{- product.featured_image | img_url: '160x160' -}}","tags": {{- product.tags | json -}}}{%-if product.id != collection.products.last.id -%},{%-endif-%}
{%- endfor -%}
]
{%- endpaginate -%}
- Save the file
- Inside of the code editor, open the
main-product.liquid
file in theSection
folder. Note, if you are using older themes, then you might not have this file. Instead look for theproduct-template.liquid
and continue the rest of the steps in that file instead. - Paste the following code at the very bottom of the file:
{% comment %}
---------------- PRODUCT GROUPING ----------------
{% endcomment %}
{% assign group_tags = product.tags | where: "group-" %}
{% assign has_group_tags = false %}
{% if group_tags.size > 0 %}
{% assign has_group_tags = true %}
{% endif %}
{% for tag in group_tags %}
<script>
(function() {
let requestUrl = "/collections/all/{{ tag }}?view=json";
let request = new XMLHttpRequest();
request.open("GET", requestUrl);
request.onload = function() {
if (request.status >= 200 && request.status < 300) {
// Parse products JSON
let products = JSON.parse(request.response);
// Get the wrapper element
let productGroupSelectWrapper = document.getElementById('ProductGroupSelectWrapper');
// Show if items, else hide
if (products.length > 1) {
productGroupSelectWrapper.removeAttribute('hidden');
} else {
return
}
// Sort products A-Z
products.sort(function(a, b){
if(a.title < b.title) { return -1; }
if(a.title > b.title) { return 1; }
return 0;
});
// Get the select element
let productGroupSelect = document.getElementById('ProductGroupSelect');
// loop through each product
products.forEach(function(product) {
// Create a new option element
let option = document.createElement("option");
option.value = product.id;
option.setAttribute("data-url", product.url);
option.textContent = product.title;
// Append the option to the select element
productGroupSelect.appendChild(option);
});
// select functionality
productGroupSelect.value = {{ product.id }};
productGroupSelect.addEventListener('change', function() {
let url = this.options[this.selectedIndex].getAttribute('data-url');
location.replace(url);
});
// ADD ADDITIONAL CODE HERE
}
};
request.send();
})();
</script>
{% endfor %}
{% comment %}
---------------- END PRODUCT GROUPING ----------------
{% endcomment %}
At this point in time, you now have grouped product data available to use on the page, we just need to inject it into different page elements. This part will vary depending on the theme you are using. For demonstration purposes, we are working off of the Dawn theme made by Shopify.
Follow this to add a select dropdown similar to other product options (size, color, etc.)
- Inside of the code editor, open the
main-product.liquid
file in theSections
folder. - Find the product form. It probably looks something like this
{% form 'product'...
. - Add the following code inside of the form product tags.
{%- form 'product'-%}
<div hidden id="ProductGroupSelectWrapper" class="product-form__input product-form__input--dropdown">
<label class="form__label">Product</label>
<div class="select">
<select class="select__select" id="ProductGroupSelect" data-name="product-type" data-index="option0"></select>
{% render 'icon-caret' %}
</div>
</div>
...EXISTING FORM CODE...
{% endform %}
If your product is using group tags, then you should see the new Product dropdown field.
Note: You may need to edit the html structure or css class names to match your themes layout structure. It's important that you leave the ProductGroupSelectWrapper
id attribute and the hidden attribute on the top most div
element and the ProductGroupSelect
id attribute on the select
element.
Follow this to add a product thumbnails underneath the details. This section requires you write your own css styles.
- Inside of the code editor, open the
main-product.liquid
file in theSections
folder. - Find an area where you want your thumbnails to be. In this example we are putting it above the product description
- Add a new
<div>
element with the id attributeProductGroupThumbnails
. It should look something similar to below.
...
{%- when 'description' -%}
<div id="ProductGroupThumbnails"></div>
{%- if product.description != blank -%}
<div class="product__description rte quick-add-hidden" {{ block.shopify_attributes }}>
{{ product.description }}
</div>
{%- endif -%}
...
- Go back to JS code snippet at the bottom of the file and replace the code with the following:
{% comment %}
---------------- PRODUCT GROUPING ----------------
{% endcomment %}
{% assign group_tags = product.tags | where: "group-" %}
{% assign has_group_tags = false %}
{% if group_tags.size > 0 %}
{% assign has_group_tags = true %}
{% endif %}
<style>
.ProductGroupThumbnailImage.active img {
border: 1px solid black;
}
.ProductGroupThumbnailImage img {
border: 1px solid transparent;
width: 80px;
height: 80px;
}
</style>
{% for tag in group_tags %}
<script>
(function () {
let requestUrl = "/collections/all/{{ tag }}?view=json";
let request = new XMLHttpRequest();
request.open("GET", requestUrl);
request.onload = function () {
if (request.status >= 200 && request.status < 300) {
// Parse products JSON
let products = JSON.parse(request.response);
// Get the wrapper element
let productGroupSelectWrapper = document.getElementById('ProductGroupSelectWrapper');
// Show if items, else hide
if (products.length > 1) {
productGroupSelectWrapper.removeAttribute('hidden');
} else {
return
}
// Sort products A-Z
products.sort(function (a, b) {
if (a.title < b.title) {
return -1;
}
if (a.title > b.title) {
return 1;
}
return 0;
});
// Get the select element
let productGroupSelect = document.getElementById('ProductGroupSelect');
// Get the element where thumbnails will be appended
var productGroupThumbnails = document.getElementById('ProductGroupThumbnails');
// loop through each product
products.forEach(function (product) {
// Create a new option element
let option = document.createElement("option");
option.value = product.id;
option.setAttribute("data-url", product.url);
option.textContent = product.title;
// Append the option to the select element
productGroupSelect.appendChild(option);
// Create a new anchor element for the thumbnail
var anchor = document.createElement('a');
anchor.setAttribute('class', 'ProductGroupThumbnailImage');
anchor.setAttribute('href', product.url);
// Create the image element for the product thumbnail
var img = document.createElement('img');
img.setAttribute('src', product.thumbnail);
img.setAttribute('alt', product.title);
// Append the image to the anchor
anchor.appendChild(img);
// Optionally append a title or other information
// Uncomment the following lines if needed
// var titleSpan = document.createElement('span');
// titleSpan.textContent = product.title;
// anchor.appendChild(titleSpan);
// Append the anchor to the thumbnails container
productGroupThumbnails.appendChild(anchor);
// Check if the product id matches and add 'active' class if it does
if (product.id === {{ product.id }}) {
anchor.classList.add('active');
}
});
// select functionality
productGroupSelect.value = {{ product.id }};
productGroupSelect.addEventListener('change', function () {
let url = this.options[this.selectedIndex].getAttribute('data-url');
window.location.href = url;
});
}
};
request.send();
})();
</script>
{% endfor %}
{% comment %}
---------------- END PRODUCT GROUPING ----------------
{% endcomment %}
This will now add all of the thumbnails underneath your description that can be clicked.
- You now need to go in and clean up the styles for your thumbnails to match your theme.
Note: the current product's image has an
active
css class applied to it that you can use to add selected borders, etc.
Once you've tested that everything works, be sure to publish the new theme to your store.
- From your Shopify admin, go to Online Store > Themes.
- For the theme that you want to publish, click Actions > Publish.
The grouping of products is controlled using product tags
in Shopify and can be added to any product.
Note: Dreamship will automatically add group tags to products uploaded via the bulk uploader
Tags must start with group-
and look like group-TAG-NAME
where TAG-NAME can be anything you choose.
Works:
group-3902340209
, group-tropical-designs
, group-my_shirt_is_awesome
Doesn't work:
GROUP-232332
, groups-239402
, group230403
, group_320420
- Add the same group tag to every product you want to group together.
- You're done!
Group tags can be stacked, allowing you to build special product grouping if needed. For example, Product 2 will show Product 1 and Product 3 on its page, and show up on both of Product 1 and Product 2's page. However, Product 1 and Product 3 will never be on each other's pages.
Product | Group |
---|---|
Product 1 | group-a |
Product 2 | group-a, group-b |
Product 3 | group-b |
The current name used inside of the dropdown can be customzied. By default it is the title of the actual product, however, Dreamship typically adds metadata to the product on upload to created a cleaner .
Using your own metadata:
Inside of collection.json.liquid
you will see the following code:
{%- if product.metafields.dreamship.product -%}
{%- assign title = product.metafields.dreamship.product -%}
{%- else -%}
{%- assign title = product.title -%}
{%- endif -%}
Replace it with:
{% if product.metafields.NAMESPACE.KEY %}
{%- assign title = product.metafields.NAMESPACE.KEY -%}
{%- elsif product.metafields.dreamship.product -%}
{%- assign title = product.metafields.dreamship.product -%}
{%- else -%}
{%- assign title = product.title -%}
{%- endif -%}
Replace NAMESPACE
and KEY
to match the namespaces and keys you used for your metadata.
Learn more about Shopify metafields
Always using the product title: If you don't want to use metafields, and only want to use the product title follow the steps below.
Inside of collection.json.liquid
you will see the following code:
{%- if product.metafields.dreamship.product -%}
{%- assign title = product.metafields.dreamship.product -%}
{%- else -%}
{%- assign title = product.title -%}
{%- endif -%}
Replace it with:
{%- assign title = product.title -%}
If you want additional product data to be sent over you can add it yourself easily in the collection.json.liquid file. This code essentially is generating json we can consume on the product page.
Inside of collection.json.liquid
you will see the following code:
{"id": {{- product.id -}},"title": "{{- title -}}","url": "{{- product.url -}}","thumbnail": "{{- product.featured_image | img_url: '160x160' -}}","tags": {{- product.tags | json -}}}{%-if product.id != collection.products.last.id -%},{%-endif-%}
You can add any product attribute to this. See the Shopify Product API docs for reference.
For example, if I wanted to add the vendor
, I would replace the code above with:
{"id": {{- product.id -}}, "vendor": {{- product.vendor -}},"title": "{{- title -}}","url": "{{- product.url -}}","thumbnail": "{{- product.featured_image | img_url: '160x160' -}}","tags": {{- product.tags | json -}}}{%-if product.id != collection.products.last.id -%},{%-endif-%}
Note the "vendor": {{- product.vendor -}},
added after the id.
The current product grouping code supports up to 50 grouped products.
In rare cases (<1%), the javascript used to load the grouped products will not execute on the page. More works needs to be done to determine the root cause and fix the issue. If you are able to successfully reproduce the error please reach out to [email protected].