Shopify - Capturing a Metafield Value of the Last Added Collection with a Custom Template - shopify

I'm working on a theme where I've got several collections of clothing and I have created a custom collection template called 'seasonal', with the intent of using a custom background color for that specific type of collection.
I've achieved this by using metafields. It's working fine and I now have two seasonal collection pages - FW15 and SS16, with two different background colors,
I would now like to fetch the background color of the most recent 'seasonal' collection in another page of the theme.
This is where I'm stuck in. See the code below:
{% for collection in collections reversed %}
{% if collection.template_suffix contains 'seasonal' %}
{% assign seasonalCollectionColor = collection.metafields.c_f.Collection_Color | split: "|" %}
{{ seasonalCollectionColor[0] }}
{% endif %}
{% endear %}
This is outputting both seasonal collection colors:
#8DE5EB #FF7C1A
Instead of just the most recent one, which I'm trying to get by appending [0] to seasonalCollectionColor.
Any help on what am I doing wrong?
Thanks in advance!

You are in a loop. The loop has one condition you are triggering off of (contains seasonal). Hence you capture the result twice, and output it twice. You are not limiting your test to the condition of most recent.
If you were to try and test for the condition of most recent, what would that be? In your case, perhaps since you are traversing collections in a reverse order, the first occurrence is your most recent one.
So add a condition that if the condition is found, and you have set the seasonalCollectionColor to something, DO NOT set it again... just ignore any other values that match, and then you'll have only one value, the most recent one.

Related

Empty spaces in filtered list

I am trying to return a list of all product IDs which contain 'sometag' in Shopify to power a theme section I'm building.
At first I was stuck by the limit of 50 products in a query (my catalogue is 600+ products), but by adding pagination to my for loop I have been able to return a filtered list from a collection containing all products to show only the tagged products I'm looking for.
However, despite the filtered list usually only containing around 1-5 items depending on tag, Shopify is still displaying the full pagination list for the entire product library - only every page is empty apart from the filtered items (which are usually on page 3 or 4 or something) - so it's not really removing the filtered out items, rather not displaying them and leaving their index spaces there empty.
Does anyone have any advice on how to trim out all the blank entries from the returned list and only display the filtered results?
{% paginate collections['somecollection'].products by 50 %}
{% for product in collections['somecollection'].products %}
{%- if product.tags contains 'sometag' -%}
{{ product.id }}
{% endif %}
{% endfor %}
{{ paginate | default_pagination }}
{% endpaginate %}
EDIT: Shopify tell me this is 'expected behavior' so I guess I need to create some mini app or use JS or something.
Because Liquid pagination happens server-side, it will always be based on the actual size of the collection, not on a sorted subset created for front end display.

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.

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.

Targeting a Collection - Liquid

Greetings Shopify Pals!
I have a website at: https://www.blakesseedbased.com/ (password: TopSecretPass)
The Goal:
We will have products that vary in quantity and would like to display this quantity information between the product title and price from the featured home page products (raspberry, chocolate chip, pineapple).
The Issue:
Currently we're able to add content to this area, but we cannot seem to get the if else statement to work correctly. We have a collection called "Snack Bars" however our logic isn't recognising this and injecting the proper code. We tried changing the collection Snack Bars to lower and uppercease snack-bars/Snack-Bars but no luck.
The Logic:
IF the product item is from the Snack Bar collection, add the box quantity (box of 12).
Else, add "no quantity"
The Code:
{% if product.collections contains 'snack-bars' %}
<span>Box of 12</span>
{% else %}
No Box Amount
{% endif %}
Thanks in advance!!
Greetings Birdie Golden!
The issue you're hitting is that product.collections is an array of Collection objects when you're accessing it in Liquid, and no collection object will ever equal a string value.
Luckily, we can easily create an array of handles using the map filter and assign that to a variable, like this:
{% assign collection_list = product.collections | map: 'handle' %}
{% if collection_list contains 'snack-bars' %}
...
The map filter above will create a new array using just the value that you pass it, so the collection_list variable will be an array of just-the-handles. Since handle is a string, we can use contains as expected to see if the value we want is in the list.
Hope that helps!

Shopify Product Replacement Parts

Looking for some advice on an interesting situation using Shopify. I'm building a site for a client that has Products that have free replacement parts available. Each replacement part has variant color options.
So far I have had the users at the company add all the replacement parts as products in the store. I have filtered the search results and the catalog results so the replacment parts are not show.
The only place they want the replacement parts to show is when a user visits a PRODUCT, they can click a button that says order replacement parts. Then a screen will show with all the replacement parts for that product.
A single replacement part may belong to several products and may have different color variants.
So what I have done thus far was had the client tag all parts with at least two tags. A tag called "part" that identifies the product as a part. And one or more tags like "link:SKU123" that links the part to one or more products. Then on my Product page I was using liquid to loop all parts and display the ones that matched the products SKU. Then I found out that the for loop has a 50 item limit...
So I looked at the product API, which would be ok, except that it has no way to filter by tags. Tags seem so handy and yet I don't see many ways to use them... So ultimately I'm looking for a way to display all replacement parts for a particular product. It doesn't have to involve tags, although the client has already tagged all the parts and I would hate to tell them it was a waste of time. But really any thoughts on how to accomplish this would be greatly appreciated.
The only way I can think to do it, is to return all of the replacement parts and then filter through them on the page. I see the API is limited to 250 products, but I suppose I could make multiple calls until I get them all. I'm not sure how many replacment parts there are total, but I suspect there could be upwards of 1000. Seems like a waste of network resources to have to pull them all down and then filter through them...
P.S. - the replacement parts are free, can they be run through the checkout with a zero dollar amount?
Ok so I have a couple different answers to this question. Not sure which one I will use yet.
Method 1: Filter Collection by Tag using URL
The first one was provided by Shawn Rudolph on the Shopify forums. It involves filtering a collection by tag using the URL. Shawn's post here explains it well: https://ecommerce.shopify.com/c/shopify-discussion/t/product-replacement-parts-270174
Method 2: Use paginate to get all products from collection using the AJAX API
This method is pretty cool. Yes it is more work than method one, but this maybe useful in a lot of scenarios. Out of the box Shopify does not allow you to retrieve all products from a given collection using the AJAX API. It can be done with the admin API but not the AJAX one to my knowledge. However, you can access all products from a collection with a for loop, but the for loop only allows up to 50 items to be looped at a time. That's where the paginate trick comes in. Basically I adapted the technique outlined by davecap here: http://www.davecap.com/post/9675189741/infinite-scroll-for-shopify-collections
So first you need your HTML/Liquid layout:
{% paginate collections.mycollectionname.products by 50 %}
{% for product in collections.mycollectionname.products %}
<div class="clone-node" id="product-{{ forloop.index | plus:paginate.current_offset }}">
{{ product.title }}
</div>
{% endfor %}
{% if paginate.next %}
<div class="clone-node next" title="{{ paginate.next.url }}"></div>
{% endif %}
<div id="insertion-point"></div>
{% endpaginate %}
So let's break it down a bit. First we are paginating are products by 50. This is the max amount a for loop will allow, so that's what we are going to use:
{% paginate collections.mycollectionname.products by 50 %}
Next we begin to loop our products. Every product is given a wrapper div with a class of "clone-node" this is very important. I also assign the div a unique ID, which isn't necessary for this to work, but may come in handy when trying to identify the product for later operations.
{% for product in collections.mycollectionname.products %}
<div class="clone-node" id="product-{{ forloop.index | plus:paginate.current_offset }}">
{{ product.title }}
</div>
{% endfor %}
We have to make sure to include the paginate.next URL. We also give this a "clone-node" class and we add a "next" class. I assign the paginate.next.url to the title attribute, but you could assign it to any number of attributes. You just need to be able to fetch it with jQuery.
{% if paginate.next %}
<div class="clone-node next" title="{{ paginate.next.url }}"></div>
{% endif %}
Then lastly we assign an insertion point. This is where we want our next set of 50 products to be inserted once we fetch them:
<div id="insertion-point"></div>
OK so now let's look at the JS code:
<script>
var prevUrl = ""; //this helps us know when we are done receiving products
function getParts() {
//get the last instance of the .next node. This will give us the next URL to query
var nextNode= $(".next").last(),
url = nextNode.attr("title"); //nab the URL
//send a get request to our next URL
$.ajax({
type: 'GET',
url: url,
dataType:'text',
success: function (data) {
//use a dummy div to convert the text to HTML, then find all of our clone-nodes, including our new "next" div which contains our next URL
var cloneNodes = $("<div>").html(data).find(".clone-node");
//insert our new clone-nodes on the page
cloneNodes.insertBefore("#insertion-point");
//if the URL's don't match let's grab the next 50!
if (prevUrl != url) {
prevUrl = url;
getParts();
}
}
});
}
//Call getParts for the first time to get the party started.
getParts();
</script>
What this will basically do, is get the URL for the next page of products from the title attribute of the div that's holding the paginate.next.url. Then using the jQuery ajax function we call that URL and it returns a page of HTML to use formatted just like our existing page, with the same "clone-node" classes we assigned, only it has the next 50 products embedded in it.
In davecap's example he used a dataType of HTML on his Ajax call, but that gave me some troubles. So instead, I used dataType text and used a dummy div created by jQuery to convert the text into HTML. Then jQuery grabs all of the divs with the "clone-node" class on them and inserts them on the page before our insertion-point. Remember the clone-nodes now hold the next 50 products so we just added the next 50 products to our page.
Lastly, we check if the previous URL is not equal to the current one. If it's not equal, that means it's a new URL and thus there must be more products to fetch, so we recursively call our getParts() function, which starts the process over and grabs the next 50. This continues until finally the URLs match, which means no more products to fetch, and the process stops.
There you have it! Of course if you have to fetch thousands and thousands of products this may be less then ideal because you are calling them 50 at a time. But for smaller numbers (maybe hundreds and hundreds...) it should work just fine.
I would create the parts as standalone products. Then each part will have variants (colours). Then you create metafields for this parts (product) which have a field with a list of all product ids which are 'mother' for this parts. This way you don't need to have strange tags and you keep concepts more separated, & everything cleaner. As per the zero amount, yes, you can go to checkout with zero amount (if your shipping settings allow you to do so).