Append string to array and join back into string - jinja2

I'm using Home Assistant templates, which run on Jinja2 script.
I have a group of entities (states.group.doors) that have attribute battery_level. I want to build an array of entities with battery_level < min_battery level and display as a string separated by commas.
I can't figure out what's wrong with my syntax. Two questions:
Is there just a better way overall to create a list that is filtered for battery_level < min_battery_level rather than building an array like I am?
If not, then there must be something wrong with the way I am building this array. Can someone spot it?
Thanks for the help.
The following code does successfully detect battery_level < 98 and display true if anything meets that criteria, so I'm almost there.
{% set min_battery_level = 98 -%}
{% set ns = namespace(found=false, entities=[]) -%}
{% set entities = [] -%}
{% for entity_id in states.group.doors.attributes.entity_id -%}
{% set parts = entity_id.split('.') -%}
{% if (state_attr(entity_id, 'battery_level') | replace("%","") | int) < min_battery_level -%}
{% set ns.found = true -%}
{% set entities = entities + [entity_id] -%}
{% endif -%}
{% endfor -%}
{{ ns.found }}
{{ entities | join(' ') }}

Welp... kept playing with it and got it working as follows:
{% set min_battery_level = 98 -%}
{% set ns = namespace(found=false, entities = []) -%}
{% for entity_id in states.group.doors.attributes.entity_id -%}
{% set name = state_attr(entity_id, 'friendly_name') | string -%}
{% set battery = state_attr(entity_id, 'battery_level') | replace("%","") | int -%}
{% if (battery) < min_battery_level -%}
{% set ns.found = true -%}
{% set ns.entities = ns.entities + [name+' ('+battery|string+'%)'] -%}
{% endif -%}
{% endfor -%}
{{ ns.found }}
{{ ns.entities | join(', ') }}

Related

Cleaner way to turn string separated by commas into JSON using Shopify Liquid?

I've got this working, but I was wondering if there is a cleaner way to write this using liquid. I'm pulling a string from theme settings which would like this:
"keyOne: valueOne, keyTwo:ValueTwo, keyThree:ValueThree"
Then in my liquid file I have this:
this is getting the string
{% assign messageList = settings.message | split: ',' %}
Loops over messageList and as long as there is a length it splits the text again by the colon and returns the key and value wrapped in quotes. Then checking the last iteration so that it doesn't add the comma
<script class="js-product-messages" type="application/json">
{
{%- for messages in messageList -%}
{%- if forloop.length > 0 -%}
{%- assign singalMessage = messages | split:':' -%}
"{{ singalMessage[0] | strip}}":"{{ singalMessage[1] | strip}}"{% unless forloop.last %},{% endunless -%}
{%- endif -%}
{%- endfor -%}
}
</script>

Grouping if statements

I'm trying to find a way to have the "if contains" conditions all appear in one line rather than having to repeat the code every single time.
Here is what the code looks like:
var selector = document.querySelector('tr[data-variant-id="{{ item.variant.id }}"] .product__description__variant');
{% assign pre_order_message = '' %}
{% for tag in item.product.tags %}
{% if tag contains 'weeks' %}
{% assign count = count | plus: 1 %}
{% assign pre_order_message = tag | split:'**' | last %}
var para = document.createElement("p");
para.classList.add('hc-shipping');
var node = document.createTextNode("Expected to begin shipping {{ pre_order_message }} from order date.");
para.appendChild(node);
selector.after(para);
{% endif %}
{% endfor %}
var selector = document.querySelector('tr[data-variant-id="{{ item.variant.id }}"] .product__description__variant');
{% assign pre_order_message = '' %}
{% for tag in item.product.tags %}
{% if tag contains 'January' %}
{% assign count = count | plus: 1 %}
{% assign pre_order_message = tag | split:'**' | last %}
var para = document.createElement("p");
para.classList.add('hc-shipping');
var node = document.createTextNode("Expected to begin shipping {{ pre_order_message }}.");
para.appendChild(node);
selector.after(para);
{% endif %}
{% endfor %}
I just want to make sure I can group together what lies between the ' ... ' on the line, that states {% if tag contains ' ' %}. Any way to fix? Thanks!
You can use and/or to combine conditions, more info about this here.
By the way if you just want to make sure a tag exists in product tags, you can do this:
{% if item.product.tags contains 'weeks' %}
Do something...
{% endif %}
Instead of looping through all the tags, you'll save a couple of lines that way ;)
One last thing, you can DRY the code by using code snippets, here's a quick read about this.

(Shopify) How to update my inventory message on variant change using current theme JSON + AJAX setup

Background:
I am currently helping a company move their current website to Shopify and am trying to add a custom feature to the product page. I am a web designer, not a developer so I am having trouble implementing this.
Here is my situation:
At this point, our theme allows us to display an "In stock & ready to ship" or an "Out of stock" message on our product page if the variant.inventory_quantity is great than 0 or equal to 0, respectively. In my case, I have added another option that will display a message if the following is true:
section.settings.show_special_order_option and selected_variant.inventory_management and selected_variant.inventory_policy == 'continue' and selected_variant.inventory_quantity == 0
In this case, section.settings.show_special_order_option is a checkbox option I created that when checked will [ideally] display the message. I would like this message to only be displayed if the above conditions are met, and update as each new variation is selected in the event that the conditions are not met.
As of now, this is my code in the product-info.liquid section:
{%- if selected_variant.available -%}
{%- if selected_variant.inventory_management and selected_variant.inventory_policy == 'deny' and section.settings.low_inventory_threshold > 0 -%}
{%- if selected_variant.inventory_quantity <= section.settings.low_inventory_threshold -%}
<span class="product-form__inventory inventory inventory--low">{{ 'product.form.low_stock_with_quantity_count' | t: count: selected_variant.inventory_quantity }}</span>
{%- else -%}
<span class="product-form__inventory inventory inventory--high">{{ 'product.form.in_stock_with_quantity_count' | t: count: selected_variant.inventory_quantity }}</span>
{%- endif -%}
{%- elsif section.settings.show_special_order_option and selected_variant.inventory_management and selected_variant.inventory_policy == 'continue' -%}
{%- if selected_variant.inventory_quantity == 0 -%}
<span class="product-form__inventory inventory inventory--special-order">{{ 'product.form.no_stock_special_order' | t }}</span>
{%- else -%}
<span class="product-form__inventory inventory inventory--high">{{ 'product.form.in_stock' | t }}</span>
{%- endif -%}
{%- else -%}
<span class="product-form__inventory inventory inventory--high">{{ 'product.form.in_stock' | t }}</span>
{%- endif -%}
{%- else -%}
<span class="product-form__inventory inventory">{{ 'product.form.sold_out' | t }}</span>
{%- endif -%}
And this is my JSON Script:
<script type="application/json" data-product-json>
{
"product": {{ product | json }},
"options_with_values": {{ product.options_with_values | json }},
"selected_variant_id": {{ selected_variant.id }}
{%- if section.settings.show_inventory_quantity -%}
,"inventories": {
{%- for variant in product.variants -%}
{%- if variant.available -%}
{%- if variant.inventory_management and variant.inventory_policy == 'deny' and section.settings.low_inventory_threshold > 0 -%}
{%- if variant.inventory_quantity <= section.settings.low_inventory_threshold -%}
{%- capture inventory_message -%}{{ 'product.form.low_stock_with_quantity_count' | t: count: variant.inventory_quantity }}{%- endcapture -%}
{%- else -%}
{%- capture inventory_message -%}{{ 'product.form.in_stock_with_quantity_count' | t: count: variant.inventory_quantity }}{%- endcapture -%}
{%- endif -%}
{%- elsif section.settings.show_special_order_option and selected_variant.inventory_management and selected_variant.inventory_policy == 'continue' -%}
{%- if selected_variant.inventory_quantity == 0 -%}
{%- capture inventory_message -%}{{ 'product.form.no_stock_special_order' | t }}{%- endcapture -%}
{%- else -%}
{%- capture inventory_message -%}{{ 'product.form.in_stock' | t }}{%- endcapture -%}
{%- endif -%}
{%- else -%}
{%- capture inventory_message -%}{{ 'product.form.in_stock' | t }}{%- endcapture -%}
{%- endif -%}
{%- else -%}
{%- capture inventory_message -%}{{ 'product.form.sold_out' | t }}{%- endcapture -%}
{%- endif -%}
"{{ variant.id }}": {
"inventory_management": {{ variant.inventory_management | json }},
"inventory_policy": {{ variant.inventory_policy | json }},
"inventory_quantity": {{ variant.inventory_quantity | json }},
"inventory_message": {{ inventory_message | json }}
}{% unless forloop.last %},{% endunless %}
{%- endfor -%}
}
{%- endif -%}
}
</script>
And finally this is the JS snippet that is executed when there is a variation change:
function _updateinven(newVariant) {
if (!this.options['showInventoryQuantity'] || !newVariant) {
return;
}
var productFormInventoryElement = this.element.querySelector('.product-form__inventory'),
variantInventoryManagement = this.variantsInventories[newVariant['id']]['inventory_management'],
variantInventoryPolicy = this.variantsInventories[newVariant['id']]['inventory_policy'],
variantInventoryQuantity = this.variantsInventories[newVariant['id']]['inventory_quantity'],
variantInventoryMessage = this.variantsInventories[newVariant['id']]['inventory_message'];
productFormInventoryElement.classList.remove('inventory--high');
productFormInventoryElement.classList.remove('inventory--low');
productFormInventoryElement.classList.remove('inventory--special-order');
if (newVariant['available']) {
if (null !== variantInventoryManagement && variantInventoryPolicy === 'deny' && this.options['lowInventoryThreshold'] > 0) {
if (variantInventoryQuantity <= this.options['lowInventoryThreshold']) {
productFormInventoryElement.classList.add('inventory--low');
} else {
productFormInventoryElement.classList.add('inventory--high');
}
} else if (variantInventoryQuantity === 0) {
productFormInventoryElement.classList.add('inventory--special-order');
} else {
productFormInventoryElement.classList.add('inventory--high');
}
}
// We also need to update the stock countdown if setup
var stockCountdown = this.element.querySelector('.inventory-bar');
if (stockCountdown) {
var stockCountdownProgress = Math.min(Math.max(variantInventoryQuantity / parseInt(stockCountdown.getAttribute('data-stock-countdown-max')) * 100.0, 0), 100);
stockCountdown.classList.toggle('inventory-bar--hidden', stockCountdownProgress === 0);
stockCountdown.firstElementChild.style.width = stockCountdownProgress + '%';
}
productFormInventoryElement.innerHTML = variantInventoryMessage;
}
How the function currently works:
As of now, when clicking on a product that is available and has inventory greater than 0, the "In stock & ready to ship" message appears in green (the class 'inventory--high' is what makes it green). When clicking on another variation that is out of stock but set to 'Continue selling when out of stock' the message stays the same, but the color changes to orange (the class 'inventory--special-order' is what makes it orange).
What appears to be the problem:
I have narrowed this problem down to the liquid function and/or JSON script. When viewing the page source, I look at the computed JSON script and it is displaying the same message for all variations when it should be displaying unique messages depending on the conditions set forth. (In other words, every variation in the JSON script has the same inventory message of "In stock & ready to ship" or "Special order: this product will take 7-10 days to arrive" even if there multiple variations in stock and multiple not in stock but able to continue selling).
My question:
Assuming I have provided all the necessary information, I am in need of some guidance as to correct my function to display the proper inventory message for each variation as the user selects different variations. Could someone please help me solve this problem? Please let me know if you need any additional information from me.

How can I concatenate a string using Jinja for loop?

I am trying to iteratively concatenate a string to build url params with a 'for' loop, but I believe I am having scoping issues.
The output should be: url_param = "&query_param=hello&query_param=world"
array_of_objects = [{'id':'hello'},{'id':'world'}]
{% set url_param = "" %}
{% set array_of_ids = array_of_objects|map(attribute='id')|list%} // correctly returns [1,2]
{% for id in array_of_ids %}
{% set param = '&query_param='~id %}
{% set url_param = url_param~param %}
{% endfor %}
//url_param is still an empty string
I also tried namespace(), but to no avail:
{% set ns = namespace() %}
{% set ns.output = '' %}
{% set array_of_ids = array_of_objects|map(attribute='id')|list%} // correctly returns [1,2]
{% for id in array_of_ids %}
{% set param = '&industries='~id%}
{% set ns.output = ns.output~param %}
{% endfor %}
//ns.output returns namespace
That is indeed a scope issue. One "hacky" way of dealing with this is using a list that you append to like so:
{% set array_of_objects = [{'id':'hello'},{'id':'world'}] %}
{% set array_of_ids = array_of_objects|map(attribute='id')|list%}
{{ array_of_ids|pprint }} {# output: ['hello', 'world'] #}
{% set ids = [] %} {# Temporary list #}
{% for id in array_of_ids %}
{% set param = '&query_param='~id %}
{% set url_param = url_param~param %}
{{ ids.append(url_param) }}
{% endfor %}
{{ ids|pprint }} {# output: [u'&query_param=hello', u'&query_param=world'] #}
{{ ids|join|pprint }} {# output: "&query_param=hello&query_param=world" #}
The above gets you what you need, but for this specific example I would take a look at using jinja's join filter. It's more declarative and feels a little less hacky.
{% set array_of_objects = [{'id':'hello'},{'id':'world'}] %}
{# set to a variable #}
{% set query_string = "&query_param=" ~ array_of_objects|join("&query_param=", attribute="id") %}
{{ query_string|pprint }}
{# output: u'&query_param=hello&query_param=world' #}
{# or just use it inline #}
{{ "&query_param=" ~ array_of_objects|join("&query_param=", attribute="id") }}
You should change the initialization of your namespace.
Here is an example from the docs that will help you out:
{% set ns = namespace(found=false) %}
{% for item in items %}
{% if item.check_something() %}
{% set ns.found = true %}
{% endif %}
* {{ item.title }}
{% endfor %}
Found item having something: {{ ns.found }}

Why does this where filter not work in Jekyll?

I have a github page, whose _include directory has a file courses.html:
{% assign id = include.lessonID | split: '.' %}
{% assign courseID = id | first %}
{% assign node = site.data.courses | where: "id","1" %}
{% assign node = node[1] %}
{%- if node.id == empty -%}
<h1> EMPTY NODE Warning</h1>
{%- else -%}
<h2> DATA Found! </h2>
ID: {{ node.id }}
{%- endif -%}
<p>CourseID: {{node.id}}</p>
<p>Name: {{ node.name }}</p>
<p>Link: {{ node.permalink }}</p>
{%- for node in site.data.courses -%}
{%- if node.id == 1 -%}
<p>{{ node.name }}</p>
<p>{{ node.permalink }}</p>
{%- endif -%}
{%- endfor -%}
It is being used by a file in _layout called courses.html:
{% include courses.html post=page.lessonInfo.lessonID post=page %}
Finally, there's file lister.md that has the following contents:
---
layout: courses
title: 'Test'
lessonInfo:
lessonID : 1.1
modName: 'Installing RHEL Server'
chapterName: 'Using Essential Tools'
---
# There should be some course list around here!
The output is as follows:
DATA Found!
ID:
CourseID:
Name:
Link:
RHCSA
/rhcsa
So, apparently the node variable isn't empty, but I can't access any of the properties when I'm selecting the right element of the array using where clause.
However, this works when using the second part using if statement in for loop. How do I fix the where clause?!
Edit
The suggestions by #JJJ did solve my problem, but I have a related problem now. I can't replace the constant 1 in the expression where: "id","1" with a variable! I tried the normal where clause (both with and without quotes) which didn't work. So, I tried a where expression, which also doesn't work:
{% assign node = site.data.courses | where: "id",courseID %}
Doesn't work!
{% assign node = site.data.courses | where_exp: "selNode","selNode.id == courseID" %}
Neither does this.
What am I doing wrong and how do I fix it?
Firstly, like in most programming languages, arrays are zero-indexed. node[1] contains the second node, not the first one. You probably meant {% assign node = node[0] %} instead.
Secondly, if node.id == empty isn't how you check if a value exists. Just do unless node or node.size == 0.