Unless Clause Leaving Blank Space in Collection Page - shopify

I asked about including a where clause in the collections page the other day but decided to scrap going that route. I'm now just using unless logic in the page. This route has worked except for one tiny flaw. When I include the unless clause in the collection.liquid page, the grid still leaves an empty space where the product that's being ignored would normally sit, and also including it in the count. How can I get this space to go away and how can I get it to not include it in the product count? It seems like the unless clause is just keeping it from appearing, not truly excluding it.
Here is the code:
<div class="{% if settings.show_collection_sidebar %}desktop-10{% else
%}desktop-12{% endif %} tablet-6 mobile-3" id="bside">
<div id="product-loop">
{% for product in collection.products %}
{% unless product.title contains "Sampler" %}
<div class="product {% if settings.products_per_row == '3'
%}desktop-4{% cycle ' first', '', ' last' %}{% elsif
settings.products_per_row == '4' %}desktop-3{% cycle ' first',
'', '', ' last' %}{% endif %} tablet-half mobile-half" id="prod-
{{ product.id }}" data-alpha="{{ product.title }}" data-price="
{{ product.price }}">
{% include 'product-listing' %}
</div>
{% endunless %}
{% endfor %}
</div>
</div>
I've tried moving around where the unless code is, outside the for loop, outside the product loop, but nothing has worked.
Thanks,
RDV

What you've written should be working - I would check your product-listing snippet for anything referencing forloop.index, since the index is a variable that will be incremented even if you're not doing anything on that iteration.
For cleanliness in the liquid code, I usually use the continue statement so that I'm not wrapping potentially huge blocks of code with liquid tags - I find that it gets hard enough to keep track of all the openings/closings in liquid if I'm doing moderately complex stuff otherwise. So with this example, instead of using unless you could consider {% if product.title contains 'Sampler' %}{% continue %}{% endif %}
But all of this is ignoring the most powerful tool that we have at our disposal - making sure the collection only contains the right products in the first place!
I'm assuming that you're looking at your "All Products" collection with this code. By default, there's an invisible collection in your store called "All" that, true to its name, contains every product visible in the online store. However, you can override this by making your own collection named 'All' (specifically, with a handle of 'all' - the actual title doesn't matter as long as this is the handle). If you want "All" to mean "Everything that's not a sampler", just make a "Smart Collection" with the condition "Product title DOES NOT CONTAIN 'Sample'" - and voilĂ ! There's nothing to skip and the total count will always be correct!

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!

Changing shopify labels in theme

I'm quite new to Shopify themes and I'm needing to change the label for the Shopify "Product Size" & "Color" in the theme i'm using but i'm confused by the loop where i change it.
This is the code which im closest to
{% form 'product', product, class:form_classes, novalidate: 'novalidate' %}
{% unless product.has_only_default_variant %}
{% for option in product.options_with_values %}
<div class="selector-wrapper js product-form__item">
<label {% if option.name == 'default' %}class="label--hidden" {% endif %}for="SingleOptionSelector-{{ forloop.index0 }}">
{{ option.name }}
</label>
{% assign optionName = option.name | downcase %}
If i change the option.name it changes both of the label names not just individual ones. I've attached a screenshot of the product page to help explain this. I assume this is a daft question for a seasoned shopify pro. Any help is really appreciated.
BTW the reason it's using these labels is because all stock is being imported from a third party stock management system.
It sounds like you will have to rely on string manipulation to convert the external names to customer-friendly ones. There are several ways to do so - the best way forward will probably depend on how consistent or varied the imported option names are.
For a full list of string manipulation functions available to you in Liquid, I will also point you towards the Shopify Liquid Reference where you will be able to find a lot more detail.
Approach 1: Simple removal filters
Use | remove to get rid of known bits that we don't want to keep. Like all filters, these can be chained together.
Example:
{{ option.name | remove: 'PA_' | remove: 'CLOTHING-' }}
Approach 2: Split on delimiter characters
The | split filter might be useful if there are lots of different prefixes that you need to remove. This command breaks the string up into an array of pieces, and we can grab the bits that we need using things like the first and last filters.
Example:
{{ option.name | split: '_' | last | split: '-' | last }}
Approach 3: Extensive manual mapping
If the values coming in for your products don't follow any regular patterns, you may be stuck in a position where you have to manually map some or all of your option names to friendly values. If you find yourself in a corner, the case statement is probably your best-of-the-bad options:
Example:
{% case option.name %}
{% when 'PA_CLOTHING-SIZE' %}
{% assign friendly_name = 'Size' %}
{% when 'PA_COLOR' %}
{% assign friendly_name = 'Color' %}
{% default %}
{% assign friendly_name = option.name %}
{% endcase %}
{{ friendly_name }}

Shopify(Liquid) multiple conditions in if statement

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.

Shopify - How to show/hide Cart {{ note }} depending on if it is left empty or not in Email Notification

I have a {{ cart.note }} section added on my Cart pages, which I'm using for in-house Purchase Orders. For customer email notifications I'd like this field to display instead of the default Invoice number, but only if that field is filled out (if left empty then the default invoice # will display).
I've tried writing this in the email notification section:
{% if note != '' %}Purchase Order #: {{ note }}
{% else %}Order {{ order_name }}
{% endif %}
I have also try replacing note in the if statement with cart.note and it did not change anything.
Though it ends up showing the first line even when it is left empty (never displaying the 2nd line as well).
In case it helps, this is from the cart-template.liquid
<textarea name="note" class="input-full" id="CartSpecialInstructions">{{ cart.note }}</textarea>
A: In Liquid, '' (empty string) is not the same as null - use the blank keyword to cover all cases
In Liquid, some comparisons and truthy/falsey values do not behave as one might expect when coming from a different language. Fortunately, the blank keyword exists, which will match all of the following:
null/nil
false
'' (empty string)
[]/{} (empty array/object)
Changing your comparison to {% if note != blank %} should get your code behaving exactly as you expect.
Cheers!
PS: Alternatively, if you prefer reading positive comparisons (or just want to be fancy), you could use the equivalent 'unless' statement: {% unless note == blank %} ... {% else %} ... {% endunless %}

How to combine for and where clause in Liquid

I'm currently working on a shopify site and need to create a filter on a products page. My goal is to have primer paints show up in a different section of the product collection page. What I'm having trouble with is how to filter for primers. We have a tag set up in the product page named "Primer". What I want is for the loop to check whether a product has the primer tag, and if so, display that as one of the products in the loop. I'm relatively new to Liquid, so I don't know how to combine clauses if that's possible. I've looked up the "Where" clause, but don't entirely understand how it works.
Here is the code as it stands:
<div class="{% if settings.show_collection_sidebar %}desktop-10{% else
%}desktop-12{% endif %} tablet-6 mobile-3" id="bside">
<div id="product-loop">
{% for product in collection.products %}
<div class="product {% if settings.products_per_row == '3' %}
desktop-4{% cycle ' first', '', ' last' %}
{% elsif settings.products_per_row == '4' %}
desktop-3{% cycle ' first', '', '', ' last' %}
{% endif %} tablet-half mobile-half"
id="prod-{{ product.id }}"
data-alpha="{{ product.title }}"
data-price="{{ product.price }}">
{% include 'product-listing' %}
</div>
{% endfor %}
</div>
</div>
How could I filter for the desired results? I've tried {% if product.tags contains 'Primer' %} in quite a few places, but to no avail.
Thanks for your help.
Have you considered using two collections to accomplish what you're after?
If you made a dynamic ("Smart") collection for all your primers (Let's assume the handle for the collection is 'primer'), you can access that collection at any time through Liquid:
{% assign primer_collection = collections['primer'] %}
{% for product in primer_collection.products %}
<h2>HAVE SOME {{ product.title }}!</h2>
{% endfor %}
Then, if you wanted to exclude all of your primer products from the main collection, create a collection named 'all' (or at least with the handle 'all' - the actual title doesn't strictly matter - see footnote). By default, the 'All' collection is, true to its name, every product in your store. However, if you create your own 'all' collection, you can define it to mean "Everything except certain items" - in your case, 'everything but the primer'
Having Shopify pre-filter everything for you through the collections themselves greatly reduces the headaches of trying to apply filters after-the-fact and dealing with misleading item counts, uneven pagination, etc.
Handles: At the bottom of each collection and product in your Shopify admin is the SEO settings. Editing these allows you to change what the handle for the collection/product is, and this is what Shopify uses to look up your collection/product internally