Access image metafield inside product.liquid in Shopify - shopify

I am trying to access image metafield inside the product.liquid template in Shopify.
This is the metafield on a image:
{
metafields: [
{
id: 11419631342244,
namespace: "tags",
key: "821753034",
value: "test",
value_type: "string",
description: this is a test,
owner_id: 124682934492,
created_at: "2019-12-23T18:20:58-05:00",
updated_at: "2019-12-23T18:20:58-05:00",
owner_resource: "product_image",
admin_graphql_api_id: "gid://shopify/Metafield/12312423535"
}
]
}
I tried accessing it under the {% for image in product.images %}
When I access image.alt it returns the result, but when I access image.metafields.namespace['tags'] it returns nothing. I have tried different syntax. If anyone knows how I can access the metafields that will be great.

I Don't think you can access image meta fields to liquid.
But you can make use of product meta fields to achieve your goal.create product metafields with namespace like "image_metafields" pass image ID as metafield key and access it using liquid.
{% assign image_metafields = product.metafields.image_metafields %}
{% assign image_meta_id = "ik_" | append: image.id %}
<p> {{ image_metafields[image_meta_id] }} </p>

Related

Eleventy linking post author to author bio

I'm working on a site that essentially has posts, those posts have tags and also authors. there will only ever be one author per post.
I have the blogs working as intended as well as the tags, what I've hit a stumbling block with is relating an author key in my Frontmatter to their bio in contributors.json. Eventually this will be using Netlify CMS, so there will an "Add new author" form, then when an admin creates a new post, they can choose the author (some authors won't have access, they'll be guest contributors and just email the post over).
I can access both pages for my test authors and the correct posts are assigned, I just can't access the related data, when in the authors loop.
Anyway, my Frontmatter for authors.njk:
layout: base.njk
permalink: /authors/{{ author | slugify }}/
pagination:
data: collections.authorList
size: 1
alias: author
filter:
- all
- nav
- post
- posts
- tagList
eleventyComputed:
title: Posts written by {{ author }}
summary: "All posts written by “{{ author }}”"
Ideally, I'd need to access the data in the front matter, so I could use their actual name, as opposed to the key, in the page title etc.
A test post's Frontmatter:
title: "The fourteenth post"
summary: "A super useful post about something or other"
date: 2022-09-15
authors:
- jbloggs
tags:
- Android
- iOS
- MacOS
- Windows
In the above, my key is "jbloggs", I then have a global JSON file, like this:
[
{
"key": "jbloggs",
"name": "Joe Bloggs",
...
"img": {
"url": "/test.jpeg",
"alt": "Profile picture of Joe Bloggs"
}
},
...
]
In authors.njk, I have a card component which has a title, tags author etc and I'm correctly displaying all posts by jbloggs (I found I had to use set on a postAuthor variable and convert to JSON, as for some reason it was a JS object:
{% set postAuthor = null %}
{% for contributor in contributors %}
{% set postAuthor = contributor | toJson %}
{% if post.data.author == postAuthor.key %}
{%- set postAuthor = contributor[0] %}
{%- endif %}
{%- endfor %}
<h2>Showing {{ collections.authorList[author].length }} posts</h2>
<ul class="cards">
{%- for post in collections.authorList[author] | reverse -%}
<li class="card__item">
<article>
<h3 class="card__title">{{ post.data.title }}</h3>
...
<span class="card__author-name">
{{ postAuthor.name }}
...
</article>
</li>
{%- endfor %}
</ul>
Then finally, the .eleventy.js file creates a collection of authors, I tried using the same functionality as the tags, but it wouldn't display the posts and some Googling got me creating a collection in this way, which would be fine, if I didn't have related data.
function filterTagList(tags) {
return (tags || []).filter(tag => ["posts", "posts"].indexOf(tag) === -1);
}
eleventyConfig.addFilter("filterTagList", filterTagList)
eleventyConfig.addCollection("tagList", function(collection) {
let tagSet = new Set();
collection.getAll().forEach(item => {
(item.data.tags || []).forEach(tag => tagSet.add(tag));
});
return filterTagList([...tagSet].sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'})));
});
eleventyConfig.addCollection('authorList', collection => {
let authorSet = {};
collection.getAll().forEach(item => {
if (!item.data.authors) return;
item.data.authors.filter(
author => !['posts', 'all'].includes(author)
).forEach(
author => {
if (!authorSet[author]) { authorSet[author] = []; }
authorSet[author].push(item)
}
);
});
return authorSet;
});
What I think is happening, is the loop in the authors.njk file is converting my JSON to a js object, when it grabs the collection from config. As if I use {{ author.name }}, I get [object Object] hence why I ended up attempting to set to a new variable. I'm at that stage where I'm trying all sorts, to no avail, hence my ridiculous attempt with the new variable, so I know my I'm going about this in a complete noob type way.
What I need to do is:
Have access to the related JSON in the Frontmatter, so I can have a page title that is the persons full name, not the "key" I have set in Frontmatter
Query whether the Frontmatter key matches the JSON key, so I can access that data, to create an author bio at the top of the page and the authors/author urls are correctly generated on the post cards
But there is probably a much better way of doing this, that I am not seeing, so any help would be greatly appreciated.
Thank you
I managed to solve it, with a bit more Googling. I was over-complicating things with in my .eleventy.js file, but thanks to the chap who wrote this blog, I was able to refactor everything and got it working as I wanted.

Shopify text translation

My website is made by shopify and I use the dawn theme.
I've been trying for a few days to translate the word "Color" from inside the product pages into my language "Culoare". I looked for a solution on the internet, but I didn't find it. I don't want to translate the whole website because it is translated, I'm interested in translating only the variant picker metafield, the "Color" and "Size" options.
Thank you in advance.
You can put this code inside your main-product.liquid so that you can use the t filter for translation:
{% if option.name == 'Colors' %}
{% assign color_label = 'products.product.colors' %}
<legend class="form__label">{{ color_label | t }}</legend>
{% else %}}
<legend class="form__label">{{ option.name }}</legend>
{% endif %}
Then in your ro-RO.json add the reference to the translation:
"products": {
"product": {
"add_to_cart": "Adăugați în coș",
"colors": "Culoare"
}
}
It's the solution I came up with. Maybe there is a better solution.

Disable checkout if all products do not share same tag Shopify

I have a custom checkout experience in my Shopify store that I only want to allow if all products in the cart contain the tag "test"
This is the function I currently have, which seems to only work with a single item in the cart.
function productTags() {
{%- assign tagEnabled = false -%}
return {
{%- for item in cart.items -%}
{%- if item.product.tags contains 'test' -%}
"{{ item.product.tags }}": {{ item.quantity | json }}
{%- assign tagEnabled = true -%}
{%- endif -%}
{%- endfor -%}
};
}
this line ( "{{ item.product.tags }}": {{ item.quantity | json }}) is only there for display in the console when testing this. I can remove that if necessary.
How can I expand this to look for all item tags in the cart, and only assign the tagEnabled variable to true if all of them have the same tag?
Thanks in advance!
It looks like you are trying to mix Liquid with Javascript, which can make coding confusing. I would recommend splitting your code into two parts: one where you collect your Liquid variables and assign them to Javascript variables (and I see you are using the json filter to do that already, which is awesome - that filter guarantees that your variable output will be in a Javascript-legal format), and the second part where you write your Javascript code without any Liquid brackets getting in the way. (This is especially helpful if you are using a code editor with any syntax or error highlighting)
Let me know if the following helps you get the information you need:
// The Liquid filter 'map' lets you drill into nested object properties. We can use this to get an array of tag arrays.
// The JSON Liquid filter will turn any Liquid variable into a legal Javascript variable.
const itemProductTags = {{ cart.items | map: 'product'| map: 'tags' | json }}
// Check on our variables
console.log('All product tag arrays', itemProductTags)
console.log('Results of tagCheck()', tagCheck(itemProductTags, 'test') )
function tagCheck(tagsList, targetTag){
// Checks to see if the targetTag appears in all, some or none of the tag arrays.
// tagsList will be an array of arrays
var targetInAny = false;
var targetInAll = true;
for(var i=0; i<tagsList.length; i++){
var tags = tagsList[i];
if(tags.indexOf(targetTag) != -1){
// Found the tag - this targetTag appears in at least one product-tag list
targetInAny = true;
}
else{
// Did not find the tag - this targetTag is NOT in every product-tag list
targetInAll = false;
}
}
// Returns an object specifying if we found the target in any, all or none of the lists
return { targetInAny: targetInAny, targetInAll: targetInAll, targetInNone: !targetInAny }
}

Shopify - Loop over data from schema

Playing around with Shopify theme creation, I am facing an issue while creating a loop.
I am allowing the theme administrator to choose from 1 to 4 products to display in the store. He can choose them from the customize UI of his theme.
The schema is:
{
"type": "product",
"id": "popular_product_1",
"label": "Product N°1"
},
{
"type": "product",
"id": "popular_product_2",
"label": "Product N°2"
},
{
"type": "product",
"id": "popular_product_3",
"label": "Product N°3"
},
{
"type": "product",
"id": "popular_product_4",
"label": "Product N°4"
}
Back to my liquid file, as a test, if I want the URL to the product I can do:
{{ all_products[section.settings.popular_product_1].url }}
And it's going to work. But of course, I have to repeat the same code 4 times. So I wanted to create a loop that would go over each.
But how to get the incremental number to be inserted in the above?
Of course
{{ all_products[section.settings.popular_product_i].url }}
{{ all_products[section.settings.popular_product_{{i}}].url }}
don't work.
I also tried
{% assign i = 1 %}
{% capture popular_product %}section.settings.popular_product_{{i}}{% endcapture %}
{{ all_products[popular_product].url }}
but it does not work either... as it seems that the variable popular_product is a string while it's not what it should be.
Alternate approach: Use section blocks
Have you considered using a section with blocks rather than just numbered product fields in the base settings? If you create a 'Popular Products' section and define popular-product blocks, you can add an arbitrary number of products (or can specify a maximum), then loop over them all using
{% for block in section.blocks %}
{% assign popular_product = all_products[block.settings.product] %}
<!-- Cool stuff with your popular product -->
{% endfor %}
You can read more about setting up sections & blocks in Shopify here: https://help.shopify.com/en/themes/development/sections
Now, the approach you used isn't wrong, but the code you have above has some errors in it. These can be corrected to get the right product handle to use for the all_products lookup. First:
{{ all_products[section.settings.popular_product_{{i}}].url }}
is incorrect: we never nest Liquid curly-braces inside of Liquid curly-braces. Instead, this should look something like:
{% for i in (1..4) %}
{% assign setting_name = 'popular_product_' | append: i %}
{% assign handle = section.settings[setting_name] %}
{% assign popular_product = all_products[handle] %}
<!-- Cool stuff with our popular_product object -->
{% endfor %}
Next, the capture variable will evaluate everything between the tags and store it in a string. When you use:
{% capture popular_product %}section.settings.popular_product_{{i}}{% endcapture %}
The contents of the capture is going to first substitute the value of i and then capture the resulting string, which is not a product handle! What you really want is a specific value in the section.settings object.
You should use capture to just get the popular_product_x variable you need and accessing that inside of the section.settings. For example:
{% capture field_name %}popular_product_{{i}}{% endcapture %}
{% assign popular_product = all_products[section.settings[field_name]] %}
<!-- Cool stuff with your popular product -->
Hope this helps!
Note: I personally prefer assign for simple variables like the above and use capture only for grabbing multiple lines (like a block of HTML), but either one works in this instance. A warning with capture though: remember that all whitespace is captured as well, which is often unintended with simple variables like product handles or setting names.
Hope this helps!

How to define global variables in Liquid?

Right now it seems variables I create can't communicate across files.
For Liquid you can pass a variable in the include
{%- assign global_var = "VALUE" -%}
{%- include 'YOUR_FILE' global_var: global_var -%}
For Shopify liquid you can do the following:
There is a work around this, you can set the global variable in the theme settings as an option config/settings_schema.json
{
"type": "text",
"id": "global_variable",
"label": "global variable",
"default": "Variable value"
},
and you can access it in the liquid files through
settings.global_variable
But the value is depending on what you enter in the theme settings.
If you need more dynamic way, you can set cart attributes through ajax like:
$.ajax({
type: 'POST',
url: '/cart/update.js',
data: { attributes: {'global_variable': "MY_VALUE"} },
dataType: 'json',
success: function(cart) {
location.reload();
}
});
And then access it any where in the theme through
cart.attributes.global_variable
But you have to update it each time the cart is empty
It seems the templates are loaded before the theme, so variables set in your layout/theme file wont be present in templates. Frustrating. However you can set them via a snippet, and include this snippet in your templates, layout, etc
In Liquid, as you want for example to determine the language and reuse this code in multiple occasions, you can create an file in the render folder.
instead of calling this using render, use include. Then it returns the assigned value. For example, create "render/current_language.liquid" :
{%- liquid
assign current_language = 'en'
if request.path contains '/nl/'
assign current_language = 'nl'
endif
%}
In a other file you can use as follows:
{%- liquid
include 'get-language'
if current_language == 'en'
else
endif
%}
As long as you use the
{% assign variable = value %}
you should be able to get the value anywhere in the file, and any file included after it has been assigned.
I believe this is the closest you can get to global variables in it.