Related products based on grouping tags limit memory. Use instead collection with certain handle tag - shopify

By starting from this point https://www.shopify.com/partners/blog/related-products
I end up to this custom and working code:
{% comment %}
Get dynamic tag from product and create static collection handle
{% endcomment %}
{% for tag in product.tags %}
{% if tag contains 'IDSeries_' %}
{% assign ser_tag = tag %}
<li>
<a href="/collections/all/{{ ser_tag | handleize }}">
<h2>{{ ser_tag | remove: "IDSeries_" | prepend: "Go to products with Serie ID number:" }}</h2>
</a>
</li>
{% endif %}
{% endfor %}
{% comment %}
Return products by using the extracted tag
{% endcomment %}
{% for product in collections.all.products %}
{% if product.tags contains ser_tag %}
{{ product.title }}
{% endif %}
{% endfor %}
This above does work. But it's not the solution!
because limited memory, it works only for the first bunch of products and not for all.
By considering I have twenty five thousands products all with an incremental "IDSeries_'INT'"
My goal is: to show products with same specific "IDSeries_'INT'".
Since when this already happens at collection tag url like this <a href="/collections/all/{{ ser_tag | handleize }}">
I was wondering if I can return products (into a section products) that are already filtered at the collection handle tag url layer.
So at Themes/Debut/Sections/product-template.liquid
I am trying the following with no luck:
{% assign collection_handle = ser_tag %}
{% for product in collections.all[collection_handle].products %}
{{ product.title }}
{% endfor %}
Question:
Any clue on how to achieve a consistent result for such a big stock of products?
Extra question:
For a store with 30k products each with five tags (so 150k tags in
total).
Does tags have any limitation?
I also created a Shopify community topic https://community.shopify.com/c/Technical-Q-A/Related-products-based-on-grouping-tags-limit-memory-Use-instead/td-p/759705

I can think of two solutions for your problem, that doesn't affect the speed of the site.
Search based + Javascript
The search page on Shopify can search for tags as well.
So if you make a fetch request to the search page like so:
fetch('/search?q=IDSeries_*').then(res => res).then(res => console.log(res))
This will return you the products that include this tag combination and you can append it with javascript.
So you will populate the products with javascript.
GraphQL
You can use the Storefront GraphQL API to make a request and get products by specific tag, like so:
{
products(first: 10, query:"tag:>'IDSeries_'"){
edges {
cursor
node {
title
}
}
}
}
This will require understanding of GraphQL and especially their Store-Front API (not the Admin API).
For a store with 30k products each with five tags (so 150k tags in total).
Does tags have any limitation?
The all_tags object for the collection can return up to 1000 tags, the rest will be skipped. They need to be unique tags, repeatable tags doesn't count.
Other than that there are no limitations to my knowledge.

So at the end of the day to display tag search results in to a Shopify section I used fetch() and script looks like this:
<script>
{% for tag in product.tags %}
{% if tag contains 'IDSeries_' %}
{% assign ser_tag = tag %}
{% endif %}
{% endfor %}
const url = '/search?view=serie_list&q={{ ser_tag }}';
fetch(url)
.then(response => response.text(
))
.then(data => {
$('#serie_list_id').html(data);
});
</script>
Also as the magic #drip said I created a separate search template with {% layout none %} at the top and some other style

Related

Shopify' s all_products[ ] does only accept title

For a client I'm building a way to choose what products are shown in the recommended products section on the product page. To achieve this I thought about inserting the handles from the products I'd like to display into the tag part of the product.
So I'm able to loop over all the tags from the product and then for every tag I'd take the product object and display it. The problem is this code doesn't seem to work:
{% for tag in tags %}
{% for single_product in all_products[tag] %}
{{ single_product }}
{% endfor %}
{% endfor %}
This code does work:
{% for tag in tags %}
{% for single_product in all_products[tag].title %} <---- Added .title
{{ single_product }}
{% endfor %}
{% endfor %}
Sadly, I need the entire product object to display it instead of only the product title. How do I achieve this in shopify?
Side info: This code is placed inside of the framework--product-recommendations.liquid file
The solution was using the collection instead of the all_product. I create a collection containing all product and was perfectly able to loop over it.
My code looks like this now:
{% for tag in tags %}
{% for single_product in all_the_products %}
{% if tag == single_product.handle %}
<div class="product-recommendations--item">
{%
render 'framework--product--item',
product: single_product,
view: 'grid'
%}
</div>
{% endif %}
{% endfor %}
{% endfor %}
all_the_products is assigned to a collection containing all the products.

Vendor list page - Only list vendors with items in stock

I have a page on my site that lists all the vendors in my shop like so:
<div class="vendor-list" id="designers-a-to-z">
<ul>
{% assign current = "" %}
{% capture alphabet %}
-A-B-C-D-E-F-G-H-I-J-K-L-M-N-O-P-Q-R-S-T-U-V-W-X-Y-Z
{% endcapture %}
{% assign letters = alphabet | split: '-' %}
{% assign its_a_letter = false %}
{% for vendor in shop.vendors %}
{% assign vendor_first_letter = vendor | strip | upcase | slice : 0 %}
{% for letter in letters %}
{% if vendor_first_letter == letter %}
{% assign its_a_letter = true %}
{% break %}
{% endif %}
{% endfor %}
{% if its_a_letter %}
{% unless vendor_first_letter == current %}
<h3><span class="anchor" id="designers-{{ vendor_first_letter }}"></span>{{ vendor_first_letter }}</h3>
{% endunless %}
<li class="vendor-list-item">
{{ vendor }}
</li>
{% else %}
{% assign vendor_first_letter = "#" %}
{% unless vendor_first_letter == current %}
<h3><span class="anchor" id="designers-{{ vendor_first_letter }}"></span>{{ vendor_first_letter }}</h3>
{% endunless %}
<li class="vendor-list-item">
{{ vendor }}
</li>
{% endif %}
{% assign current = vendor_first_letter %}
{% endfor %}
</ul>
</div>
Some of these vendors don't currently have any items in stock so it's pointless to have them show up here. Is it possible to display only the vendors that have items in stock within their collection?
I currently have 2 collection tags in my store for 'in-stock' and 'sold-out' to help filter them with our filter menu and that is appended to the url so we only show customers in stock items.
Since a product has a vendor, and a product has inventory, you could just check that way. It would be insanely slow and obnoxious but hey! That is what a hosted platform is for, to turn crazy Liquid into the right tight HTML.
Note the Liquid for product.available
Returns true if a product is available for purchase. Returns false if all of the products variants' inventory_quantity values are zero or less, and their inventory_policy is not set to "Allow users to purchase this item, even if it is no longer in stock."
So while you loop through your vendor list, loop through all the products, checking the product vendor for a match, and the availability. If they don't suit you... skip the vendor.
Would be interesting to see how slow this is... but you never know till you try...
Another approach, perhaps smarter, is to iterate all your in-stock products once. Build your vendor list out of that, instead of shop.vendors.
I was able to get this working by writing it like this:
{% for vendor in shop.vendors %}
{% for collection in collections %}
{% if collection.title == vendor %}
{% if collection.all_tags contains 'in-stock' %}
then the conditions for checking the first letter and displaying the vendor, etc. nothing new
It only worked, however, on my dev site that I was testing it on and when I pushed it to production I got liquid error: memory limits exceeded. My dev site is definitely lacking in collections and products compared to my live site so this may work for people who have smaller sites.

Filter out collection name from product tag list (display) shopify

I would love to know how to remove from display tags that are used to automatically create collections (they appear in other product section).
For example:
$125
COLLECTION : BUSINESS
TAGS: AUTUMN BUSINESS BLACK
So that BUSINESS tag (which is used to form a BUSINESS collection ) is filtered out.
Tries the following 'straightforward' solution:
{% for tag in product.tags %}
{% unless tag == 'Business' or tag == 'Vintage' or tag == or tag == 'Boho' %}
display {{ tag }} link
{% endunless %}
{% endfor %}
Which did not see to Work
Thanks
Sometimes multiple conditions in if statements don't work so well in liquid. (See here and here.)
You could try something like this instead:
{% assign excluded_tags = "Business,Vintage,Boho" | split: "," %}
{% for tag in product.tags %}
{% unless excluded_tags contains tag %}
{{ tag }}
{% endunless %}
{% endfor %}
EDIT:
I just tested your code again, and even with the typo or tag == or..., it still works fine for me (just a warning on save).
Another suggestion is perhaps it's got to do with capitalisation. E.g. If you have a tag BUSINESS, then tag == 'Business' is not going to work. The capitalisation needs to be consistent.
EDIT 2:
From comment below:
I was thinking how to automatically populate excluded tags by the names of the collections of a product.
You can use map to get an array of collection titles:
{% assign excluded_tags = product.collections | map: 'title' %}
Your straightforward is incomplete.
{% for tag in product.tags %}
{% unless tag == 'Business' or tag == 'Vintage' or tag == <missing_value_here> or tag == 'Boho' %}
display {{ tag }} link
{% endunless %}
{% endfor %}
The third condition is empty hence could be the reason it is failing.
Also as mentioned in another answer by Steph, multiple conditions may be unstable at times, if that the case try this:
{% for tag in product.tags %}
{% unless tag == collection.title %}
display {{ tag }} link
{% endif %}
{% endfor %}
It will take care of any future collections as well.
P.S. This is assuming that the page where the tags are displaying contains a collection url.

how to limit the numbers of product to show

I'm new to shopify. I'm trying to limit the output of the product for example 4 product for each tag. but seems like there is variables for me to loop..
anyone can help? thx.
{% for tag in collection.all_tags %}
{% if current_tags contains tag %}
<li class="active">{{ tag | link_to_remove_tag: tag }}</li>
{% else %}
<li>{{ tag | link_to_add_tag: tag }}</li>
{{ tag }}
{% if collection.products.size > 0 %}
<ul class="product-grid just">
{% for product in collection.products %}
{% if product.tags contains tag %}
<li>{% include 'product-grid-item' %}</li>
{% endif %}
{% endfor %}
</ul>
{% else %}
<p><strong><br/>No products found in this collection.</strong></p>
{% endif %}
{% endif %}
{% endfor %}
The limit parameter can be used to display the first 4 products in a collection:
{% for product in collection.products limit:4 %}
However if you need to display a limited number of products with a given tag, you will need to implement your own counter. See this discussion on the Shopify forums for an example of how to do this.
E.g.
{% for tag in collection.all_tags %}
{{ tag }}
{% assign counter = 0 %}
{% for product in collection.products %}
{% if product.tags contains tag and counter < 4 %}
<li>{% include 'product-grid-item' %}</li>
{% assign counter = counter | plus: 1 %}
{% endif %}
{% endfor %}
{% endfor %}
For a good separation the logic how many items are to be displayed is better put somewhere the "List" is made ready for output.
This way you don't have to do content related maths in the view, which is never a good idea since it doesn't put code where it belong and other people wanting to change something may have difficulties to find it. That being said you can either put the limitations on the collection.products object based on a configured backend parameter just before handing the object to the rendering or you go further back and intervene at the "grab products from database" point and limit/alter the result. ;)
There sure is a possibility to use an incrementing counter and stop at 4 in the template but I can hardly recommend to use that since it's not where the logic should be. The template should show, not think what to show, otherwise it's hardly reusable.

Shopify If in collection then display this

I am trying to write a simple if statement, but always struggle with shopify's system.
Essentially I want it to do this:
{% if collection.product == 'discontinued' %}
This Product is Discontinued.
{% endif %}
If it's in this collection, then display this text/html. Otherwise it wouldn't display anything. This would be in the product.liquid template.
Any ideas?
This is what ended up working:
{% for c in product.collections %}
{% if c.handle == "discontinued" %}
This product is Discontinued
{% endif %}
{% endfor %}
You can create an array of the collections for a product using map on product.collections. This which will create a new array with your specified property, i.e. the handles of each collection.
You can then check if this new array contains the handle you want to work with.
{% assign productCollections = product.collections | map: "handle" %}
{% if productCollections contains 'your-collection-handle' %}
{% comment %} DoSomething {% endcomment %}
{% endif %}
So for your example:
{% assign productCollections = product.collections | map: "handle" %}
{% if productCollections contains 'discontinued' %}
This product is Discontinued
{% endif %}
You can map other fields if your case is different, such as the title.
I guess this will help any one, I have used in the sidebar of shopify website.
The current collection page will get checked by this below code.
<div class="row-fluid not-animated" data-animate="fadeInUp">
<div class="title">By Collections</div>
<form class="coll">
{% assign col_tags = collection.title %}
{% for collection in collections %}
<input type="radio" value="{{ collection.url }}" name="collections" {% if col_tags contains collection.title %} checked {% endif %} >{{ collection.title | escape }} <br/>
{% endfor %}
</form>
If I understand how liquid collections work in Shopify, you will need to iterate over all of your products.
You'd need to do something similar to this if you are working with collections directly:
{% for product in collection.product %}
{% if product.tags contains 'discontinued' %}
This product has been discontinued :(
{% endif %}
{% endfor %}
If you are just working with a single product you can probably just use the inner if liquid tag part.
References:
Collection.liquid
Product.liquid
You can indeed add discontinued products to a collection called discontinued.
When rendering a product, you could do as csaunders suggests, simply loop through all the products in the discontinued collection, and check if the id of the current product matches any of the products in that collection. If so, do what you must do. No need to use tags.