Shopify(Liquid) multiple conditions in if statement - shopify

I am looping through all collections, and creating a preview item with each collection title, image and link. But I have 15 collections I would like to exclude.
Currently I am using 'contains' to exclude the 15 I don't want, but am wondering if theres a cleaner way to write this since its a really long if condition.
Thanks in advance!
Example below:
{% for collection in collections %}
{% if collection.title contains 'collection-1' or collection.title
contains 'collection-2' or collection.title contains 'collection-3'
or collection.title contains 'collection-4' or collection.title
contains 'collection-5' %}
{% else %}
// build item here
{% endif %}
{% endfor %}

I would create an array of exclusions and check to see if my exclusion array contains the collection in question. (And rather than the title, I would use the collection handle as the handle is guaranteed to only be 'clean' names and guaranteed to be unique)
Example:
{% assign collection_exclusion_array = 'collection-1, collection-2, collection-3, collection-4, collection-5' | remove: ' ' | split: ',' %}
{% for collection in collections %}
{% if collection_exclusion_array contains collection.handle %}
{% continue %}
{% endif %}
{% comment %} Build items here {% endcomment %}
{% endfor %}
How it works:
We cannot directly create arrays in Liquid - we can only make one by taking a string and using the split filter to create our array.
By using handles, we guarantee that our list values only contains letters, numbers and hyphens - there's no chance that our delimiter (in this case, the comma) can accidentally show up as part of the value.
We don't want spaces to be part of the array values, so we remove them before we use the split filter. We could instead just not put spaces between each value, but in my brain that reads like a terrible abuse of grammar. Either omitting spaces the first time or removing them after creating your string will work.
Now that we have our array of exclusions, when we loop through collections we can check to see if the current collection's handle shows up in the list.
If found, skip to the next collection using the continue statement - this saves a layer of indentation since we don't have to have an empty if followed by an else that contains everything that we want to do.
And there you go! Hope it helps :)
NB: For more information on handles in Shopify, see https://help.shopify.com/en/themes/liquid/basics/handle

An alternate method to achieve your exclusions:
If you give your collections some sort of flag that indicates that they shouldn't show up in your collection loop, you can manage each collection directly, rather than maintaining a separate list.
If we look at the collection page in your admin, though, we don't get a lot that's helpful: all we see are things like title, description, etc. Not even a place to give the collection a specific tag!
Fortunately, collections are able to have metafields - Shopify just has that feature hidden from normal users. Metafields allow you to create additional information for objects in your store (products, collections, pages, etc.), which you can then reference through Liquid.
You can read more about Shopify's use of metafields here: https://www.shopify.com/partners/blog/110057030-using-metafields-in-your-shopify-theme
My previous favourite plugin for accessing metafields was ShopifyFD, a browser extension that would let you view and edit that metadata right on your collection page, but unfortunately Shopify's recent changes to the admin have broken that plugin. The author is working on a new version, but it's not ready at the time of writing: https://freakdesign.com.au/blogs/news/shopifyfd-and-the-current-case-of-the-broken-tool
(Note: I haven't tried any of the other metafield-editing tools listed in the above linked article - when ShopifyFD started having trouble, I started doing my metafield editing using the admin API and creating/posting the requests myself: https://help.shopify.com/en/api/reference/metafield)
Once you have a way to easily set metafields (which, surprisingly, seems to be the hard part right now), your for-loop logic is extremely simple. Let's assume that the metafield you create for this purpose has the namespace 'preview' and the key 'exclude':
{% for collection in collections %}
{% if collection.metafields.preview.exclude %}
{% continue %}
{% endif %}
{% comment %} Do stuff! {% endcomment %}
{% endfor %}
This will now skip any collection that has any value set in your custom field, so if you change your mind about any current or future collection all that needs to change is the one metafield on the collection itself.

Related

Cycle inside render tag

I am looping through products and I need the cycle tag based on loop.
{% for product in collection.products %}
{% render 'product-grid-item', product: product %}
{% endfor %}
Inside the "product-grid-item", I have:
{% assign class_1 = 'small-6 medium-4' %}
{% assign class_2 = 'small-6 medium-3' %}
{% capture grid_item_width %}
{% cycle class_1, class_1, class_1, class_2, class_2, class_2, class_2 %}
{% endcapture %}
The cycle is not working, because it is not directly inside the "for loop". Any idea how to get this working?
I am aware of alternatives, I am just trying to make "cycle" work inside a render tag.
Render is a closed piece of code, it can't read what is happening outside of it.
So at the moment you not only don't have access to the cycle but you don't have access to the forloop object as well.
You are looking for how the include works but that is deprecated and you shouldn't use it.
So the short answer is you can't make it work, since the main logic of the render is to work this way.
The only way to make the render aware of something outside it is to pass a variable to it, so you need to make your cycle logic outside of it and pass the resulting variable inside of it.
What you are trying to do is possible as long as you rearrange your approach slightly. You will just need to do your math outside of the snippet and pass an appropriate value as a variable into the snippet.
{% assign class_array = 'class-1,class-1,class-1,class-2,class-2,class-2,class-2' | split: ',' %}
{% for product in collection.products %}
{% assign loop_position = forloop.index0 | modulo: class_array.size %}
{% render 'product-grid-item', product: product, class_name: class_array[loop_position] %}
{% endfor %}
How this works
Just like before, we make a comma-separated array of class names that we want to cycle through. (We cannot make an array directly, but we can turn a delimited string into an array pretty easily using the split filter) - but this time we assign that to a variable.
We then use the forloop index and the modulo operator to get a value between 0 and the last index position of our array list and use that number as the lookup value for our array. That value is passed into the rendered snippet so that product-grid-item can access it.
If we ever need to change our cycling class names, all we have to do is update the array with the new values. Even if the number of values changes in the future, the code will still work to cycle through all of the values provided.
Cheers!

Modify Shopify search - display product variants, correct pagination

I'm trying to find a way for the following:
I'm modifying a liquid template to also include found product variants on a search result page. But when I do the amount of visible products/product variants on a search results varies greatly and leads to bad UX.
We have a pagination break at 28 items. But this only factors in the amount of products it has already displayed. As I now show variants of the products as well, the pages have a very large amount of items on them. If I decrease the pagination limit to - let's say 4 - I end up with pages that have exactly 4 entries and pages that have let's say 500 entries depending on the matching variations for a given search term.
This is the overall goal:
Display product variants in the search result page as well as the default products and still have a correct pagination that display a fixed amount of products/variants/search result items per page.
My approach was this:
{% capture total_results %}
{%- for result in search.results -%}
{%- case result.object_type -%}
{%- when 'product' -%}
// Go over all the variants and match to the search terms
// Include product-grid-item template in case it matches
{%- when 'article' -%}
{%- when 'page' -%}
{%- endcase -%}
<!-- Divider: #### -->
{%- endfor -%}
{% endcapture %}
{% comment %}Break the captured string at the divider string into an array{% endcomment %}
{%- assign total_results = total_resuls | split: "<!-- Divider: #### -->" -%}
<div class="page__description page__description--centered">
<span>{{ 'search.general.results_count' | t: count: total_results.size, terms: search_terms }}</span>
</div>
{% paginate total_result by 28 %}
{% for result in total_resuls %}
{{ result }}
{% endfor %}
{% endpaginate %}
I get the following liquid error in the frontend: Array 'total_results' is not paginateable.
Is there a way to make it paginateble?
Or is there a way to modify the search.results collection directly so that the pagination doesn't only consider the amount of results from the shopify search but also the variations?
The reason we do this btw is that shopify does seem to find the products based on an information where only the variation matches. But in this case it only links to the product and the user whould need to go to the correct variation manually again which is very bad UX.
Even if the user searches by ID directly they get linked to the normal product page. And we want them to be linked directly to the correct variant that was the reason the product was included in the search results in the first place.
Any help would be appreciated. Or pointers on how else I could achieve this.
Unfortunately, there's no way to paginate through your array. paginate tag can only be used with search.results, collection.products on some other predefined objects.
The way you're trying to implement it doesn't seem possible to me. But here are a couple of ideas came to my mind that might help you:
Option 1. Use search.terms object to build a link to the correct variant. As the main issue is that Shopify search result
links to the product and the user would need to go to the correct variation manually
I would just suggest displaying search results as is but apply your logic from
// Go over all the variants and match to the search terms
to add a ?variant_id=xxx attribute to the search result URL. Then once customers get to the product page, the variant will be selected based on this query parameter. This logic would also perfectly suit the case when customers search by variant ID.
Option 2. Build custom search. It would require more efforts and implies not using the Shopify search at all. You would need to sync store products and return the paginated results from your database based on a query from the search form. This option would give you flexibility in displaying your product results.
I would go with option 1 if the only you want to do is select the correct variant based on the user's search query. But it may not work if you have multiple matching variants and you either want to display all of them separately or be able to redirect the customer to every matching variant from the results page.
P.S. Just a hint: you're using {%- when 'product' -%} block to filter product results only, but you can use ?type=product in your query to search only through products entities, ignoring articles and pages.
Maybe you can try this app https://apps.shopify.com/ultimate-search-and-filter-1 to show variants as separate products on Collection and Search result page without affecting the paginiation in Shopify.

How to serve template based on collection handle (Shopify)

I have a number of different collection templates. They differ in the filters that they contain. Some have three filter dropdowns, some only have one.
I would like to display different templates based on the collection type (handle). I know that you can manually change template in the admin, but I want to handle this programmatically within Liquid so the client doesn't have to worry about toggling alternate templates.
I have had no luck in the documentation or Shopify community forums.
An example of the logic:
If collection.handle = handle-name,
Then use collection template X,
Else default collection template
Grateful for any pointers.
For detailed information you may refer to Shopify documentation on Creating Alternate Templates or a similar blog post on Partners Blog.
These articles suggest creating files with particular collection handle, that can be selected manually. Since you do not want to do that and have specified that only filters change based on different collections, so instead of replacing the whole template you can simply include the different snippet or section based on collection handle.
Example, inside collection.liquid
{% if collection.handle == 'collection-1' %}
{% section 'collection-filters-1' %}
{% elsif collection.handle == 'collection-2' %}
{% section 'collection-filters-2' %}
{% else %}
{% section 'collection-filters-default' %}
{% endif %}

Show products on article

How can I show a list of products (like a recipe) on a article page.
Can you give me some guidance on what should i use to achieve that behavior?
How can i link those products dynamically to the article?
Getting the products once we know what they are
There are two ways to bring up products on an arbitrary page in Shopify:
1) Using all_products[handle]
Using the product handle to get the product from the all_products global object.
Example:
{% assign ingredient = all_products['lavender-oil'] %}
This works well for small numbers of products, but for large numbers of products it may cause delays in page-loading times. We are also limited to only 20 (I think) calls to all_products per page, so this wouldn't work for recipes with a ton of ingredients in them.
2) Using a collection
Use a collection that contains only the products required. You can reference any arbitrary collection if you know the collection's handle. When making collections, you can also sort products manually to control the order that they appear in when you loop through it. Collections can contain an arbitrary number of products, and I believe the default pagination will give you is either the first 20 or 50 products if you don't specify any other limit. If required you can adjust the number of products served to as high as 1000 by wrapping your collection-product loop with paginate tags (though that upper limit is definitely not recommended for performance reasons)
Example:
{% assign ingredients = collections['love-potion-number-9'] %}
{% for product in ingredients.products %}
<h2>{{ product.name }}!!</h2>
{% endfor %}
The downside for both of these is that you can't write Liquid code inside your article content in Shopify, so this ingredients section would need to be written as a snippet or a section in your theme files and included in your article template used for these recipes.
This leads me to consider the next issue - you would want to include a concept of quantity with the ingredients, and so far neither of the above give us that. So now, the hard part:
Getting that information into a Liquid snippet/section in the first place
There are a few different ways that I can think of offhand that would help you out here. No one is perfect, unfortunately.
Using Metafields
Metafields are a great tool available in Shopify, but unfortunately Shopify doesn't make them easy to use [1].
Once you have a metafield-editing tool, come up with a naming structure for the 'namespace' and 'key' values. For example, you might create the following metafields for the recipe you provided. (Note: How these would be entered will depend on what metafield-editing tool you're using)
namespace: 'ingredients', // We'll use this as the 'box' that holds all our ingredients
key: 'juniper-berry-oil', // This will be the product handle for the product in question
value: '2 drops' // The quantity used for the recipe
namespace: 'ingredients',
key: 'rosemary-ct-camphor-oil',
value: '1 drop'
namespace: 'ingredients',
key: 'cypress-oil',
value: '1 drop'
... (etc) ...
Then, in your theme file where you are creating your ingredient list, you would have code that looks something like this:
{% assign ingredients = article.metafields.ingredients %}
{% for ingredient in ingredients %}
{% assign handle = ingredient.first %}
{% assign amount = ingredient.last %}
{% assign product = all_products[handle] %}
<!-- HTML code here -->
{% endfor %}
Using Tags and Products
If you create a tag-naming scheme, you can loop through those and use them to build your ingredient list. For example, if you give the article a number of tags in the form ingredient_[product-handle]_[amount], you would be able to reference them as:
{% for tag in article.tags %}
{% if tag contains 'ingredient' %}
{% assign breakdown = tag | split: '_' %}
{% assign handle = breakdown[1] %}
{% assign product = all_products[handle] %}
{% assign amount = breakdown | last %}
<!-- HTML Code -->
{% endif %}
{% endfor %}
The downside to this method is that there's no easy way to reorder the tags if done this way - using a collection will give you better control of that.
Getting recipe amounts into a Collection loop
The easiest way to reference a collection would be to have a collection with the same handle as the article - then you can reference the collection and its products as:
{% assign ingredients = collections[article.handle] %}
{% for product in ingredients.products %}
<!-- HTML Code here -->
{% endfor %}
This has the advantage of letting you easily sort the ingredients by setting the collection to have a Manual sorting method, but the corresponding downside is that there's no obvious place to put the quantity information.
One way to get that information in would be to use either tags or metafields - metafields would have the advantage of being able to directly access the quantity for the product - if using the naming convention above in the metafields part of this answer, you could use:
{% assign ingredients = collections[article.handle] %}
{% for product in ingredients.products %}
{% assign amount = article.metafields.ingredients[product.handle] %}
<!-- HTML Code here -->
{% endfor %}
If using tags, you would need a format that could be split up like in the tag section and loop through all your tags each time to find the one for your product. If your tags were set up as ingredient_[product-handle]_[amount]
If using tags, you would need a format that could be split up like in the tag section and loop through all your tags each time to find the one for your product. If your tags were set up as the example above:
{% for tag in article.tags %}
{% if tag contains 'ingredient' and tag contains product.handle %}
{% assign amount = tag | split: '_' | last %}
{% endif %}
<!-- HTML Code -->
{% endfor %}
Hopefully this helps you get going!
[1] Using Metafields: There are several possible solutions for editing metafields in Shopify - my personal preference is the 'Shopify FD' extension for Chrome, but the recent updates to the Shopify admin screens are interfering with this extension's ability to load & show its metafield panels on some pages. I know that product pages still work, but some pages (like collections) don't anymore.
There are also a number of apps available for your store to edit metafields - I haven't used any of them, so I can't speak to their value, but you can view the list here: https://apps.shopify.com/search?q=metafield&st_source=
If you have a coding background, you can also create and update metafields yourself by sending the right data to Shopify's Admin API - see the documentation at https://help.shopify.com/en/api/reference/metafield if you want to try doing it yourself.
Here is a sure to succeed recipe. Write your Article. It will have a handle, unique to it. Save that in your head, clipboard, etc. Now create a manual collection. Give it the same handle. Now you can reference an empty collection by the Liquid:
collection['some-handle-to-an-article']
What if you now filled that collection with the products in your recipe? Eureka. Genial! You can then list them in a simple Liquid for next loop like this:
{% for product in collection['some-handle-to-an-article'] %}
{{ product.title }}
{% endfor %}
Or you could be a smarty pants and gather the handles of the products manually yourself. Store them in a metafield resource assigned to the article. For example, a string like 'a-blah,b-blahblah,c-zigo-von-goober' and then use Liquid to find that metafield in the article template. Split that string by commas. Now use the most excellent all_products like this:
{% assign fizzbuzz = all_products['a-blah'] %}
{{ fizzbuzz.title }}
And there are many more creative options. Shopify Liquid is ripe for play like this. No limits, except on all_products at 20... you cannot go more than that.

Displaying Collection thumbnails by matching Variant

One of the Collections on the Shopify theme that I'm editing uses the settings: Products must match - Variant's Title contains Red
How would I go about tweaking the collection-loop.liquid template (or other snippets?) to have this collection use the relevant variant product images associated with Red as the thumbnails, without messing up the other collections?
A: Make an alternate template and apply it to the collection(s) in question
Shopify allows you to make multiple templates for each of the main types of pages and set which one you want to use in the admin.
Step 1: Open the theme editor for your live theme
Go to [your-shopify-store].myshopify.com/admin/themes
The top half of the admin screen should be a showcase of your live theme. Click 'Actions' (right above the main preview) and select 'Edit HTML/CSS'
Step 2: Create your 'red' template
In the folder list on the left-hand side of the editor, open the 'Templates' folder and click 'New Template'
Select 'Collection' as the type and give it a name that makes sense.
Step 3: Make any desired updates to the file to show off your redness.
Eg: Where images are being displayed, first loop through the variants on the product and get the image of the first variant with 'red' as an option value.
Depending on how your theme is set up, you may need to edit and/or duplicate-and-change one or more snippets. You can find which one(s) by following the 'include' and 'section' tags. The 'include' tag points to files in the 'snippets' folder, and the 'section' tag points to files in the 'sections' folder.
For drastic changes, I would recommend creating new snippets and including those in your alternate template instead. For minor changes, though, you can find out if you're on an alternate template through the liquid variable template.suffix
For example, you could loop through variants to find one where variant.title contains template.suffix - then you could make collections.blue, collections.green, etc. and not need to make further edits to the snippet.
Step 4: Preview your alternate template to make sure it does what you want
If your collection is called 'shirts' and your alternate template is simply called 'red', you would preview your template as: /collections/shirts?view=red - the view=red part is what tells Shopify to load the alternate template.
Step 5: Repeat steps 3 & 4 until you're happy with the results.
Step 6: Apply the new collection template to the collection(s) you want to default to this cool (hot?) new style.
In the Shopify admin, select (from the left-hand navigation) 'Products' then 'Collections'
Select the collection you want to apply the template to
Now that you have at least 1 alternate template, a template selector should now be visible in the right-hand column.
Change the template from the default to your new one, click save, kick back and relax!
Finding an appropriate image to show
This part will vary in complexity depending on how your products are set up. I am going to assume that there is an option on each product named 'Color', but that 'Color' can be any of the three option columns on the product (ie, that we can't assume that the first option will always be the colour option)
Step 1: Make a new snippet to contain the image-finding logic (it keeps our code clean and reusable)
In the theme editor, make sure the 'Snippets' folder is expanded in the right-hand pane
Click the 'Add new snippet' link
Give the file a meaningful name (such as 'find-color-image')
Step 2: In your alternate collection template, find your product loop and include the new snippet
First, find the product loop. It should look something like {% for product in collection.products %}
Immediately after the above line, add {% include 'find-color-image', color: template.suffix, product:product %}(assuming that your template name matches the colour you are looking for - otherwise, change template.suffix to the colour you want (wrapped in quotes), such as 'red')
Step 3: Build the find-color-image snippet. The following code should do what you're looking for.
{% comment %} find-color-image.liquid: Given a product and a colour,
set a variable named color_image as the first variant image that matches the specified colour {% endcomment %}
{% comment %} Clear any leftover variables from previous includes {% endcomment %}
{% assign color_image = nil %}
{% comment %} Loop through the variants to find one matching our colour {% endcomment %}
{% for variant in product.variants %}
{% comment %} We don't care about any variants without a featured image - skip those using continue {% endcomment %}
{% unless variant.featured_image %}{% continue %}{% endunless %}
{% comment %}Make sure the comparison is case-insensitive. The variable named color is expected to be passed when this snippet is included {% endcomment %}
{% assign opt1 = variant.option1 | downcase %}
{% assign opt2 = variant.option2 | downcase %}
{% assign opt3 = variant.option3 | downcase %}
{% assign target_color = color | downcase %}
{% comment %} Check to see if one of the above options matches our target colour {% endcomment %}
{% if opt1 == target_color or opt2 == target_color or opt3 == target_color %}
{% assign color_image = variant.featured_image %}
{% break %}
{% endfor %}
{% endfor %}
Step 4: Update image links in your collection template
Change references to the product's image to the color_image variable set in the above snippet.
Hope this helps!