Shopify - best way to incrementally build up a string in Liquid - iteration

I have an array of cities. I want to create a formatted string that can be output to the page... something like New York, Miami and Denver.
In JavaScript, I'd do something like this (rough):
const cities = ['nEW-york','MIAMI FL', 'denver'];
let out = '';
cities.forEach((city, i) => {
if(i === cities.length-1 && i !== 0) {
out += ` and ${formatCity(city)}.`;
} else {
if(i !== 0) {
out += ', ';
}
out += city;
}
});
console.log(out);
You can assume my formatCity() function knows what different kinds of input to expect and how to transform it into a nicely formatted city name (ex nEW-york --> New York).
The first way I thought to do this in Liquid was to use a for loop and capture, which just creates a mess of whitespace and newlines and whatnot.
Instead, I went with an approach similar to how i' do it in JavaScript, using the append filter.
{% assign cities = 'nEW-york,MIAMI FL,denver' | split: ',' %}
{% assign out = '' %}
{% for city in cities %}
{% if forloop.last and forloop.first == false %}
{% assign out = out | append: ' and ' | append: city | append: '.' %}
{% elsif forloop.first == false %}
{% assign out = out | append: ', ' | append: city %}
{% else %}
{% assign out = out | append: city %}
{% endif %}
{% endfor %}
{%- capture prettyCityStr -%}
{%- render 'format-cities', str: out -%}
{% endcapture %}
{{- prettyCityStr -}}
And you can assume that my format-cities snippet does the same as the formatCity() function above. You should also assume that it's not just as simple as throwing the word and in before the last city.
I have no idea if this method (used extensively on any given page... with WAY larger arrays of cities) would cause any performance issues (it seems to me that re-initializing the out variable over and over again may be problematic) or if there's just a more standard way.
I don't work in Liquid often - any input from anyone who does would be greatly appreciated.

Related

How to append tags in a forlop to an array in shopify liquid

I am trying to make an array which already has some tags.. I want to loop through each product in cart and add the tags to the array. The {{tag }} part is working but its not getting assigned to the array..
{% assign finalTaglist = "apples, oranges, peaches" | split: ", " %}
{% for item in cart.items %}
<p>
{% for tag in item.product.tags %}
{{tag}}
{% assign finalTaglist = finalTaglist | concat: tag %}
{% endfor%}
</p>
{% endfor%}
<p>Final Tag List : {{finalTaglist}}</p>`
`
concat is used for joining arrays, but your code is trying to add a string to an array, hence why it doesn't work.
Start with an empty string, and use append to add on to the string, not forgetting your separator. Once built, use split to create the array, then you can append to another array if required.
Something along these lines (not tested, just winging it, but you get the idea..)
{% assign finalTaglist = 'apples,oranges,peaches' | split: ',' %}
{% assign newTagList = '' %}
{% for item in cart.items %}
{% for tag in item.product.tags %}
{% assign newTagList = newTagList | append: ',' | append: tag %}
{% endfor%}
{% endfor%}
{% assign newTagList = newTagList | remove_first: ',' | split: ',' %}
{% assign joinedTagLists = finalTaglist | concat: newTagList %}

How to turn a string in to an array of objects with keys and values in Liquid

I'm looking to turn a string that you can be edited in Shopify theme settings into an array of object key values in my Liquid file.
For example:
var text = 'key1:value1,key2:value2,anotherKey:anotherValue'
in to:
var array = [{key1: value1}, {key2: value2}, {anotherKey: anotherValue}]
Each object will be separated by a ',' in the string and the key will be to the left of a ':' with the value to the right.
I need to write this inside a theme.liquid file but unsure how to achieve this. Any help would be most appreciated.
I've so far only got as far as:
{% assign text = 'key1:value1,key2:value2,anotherKey:anotherValue' %}
{% assign splitText = text | split: ',' %}
{% assign array = '' | split: '' %}
{% for data in splitText %}
{% assign key = data | split: ':' | first %}
{% assign value = data | split: ':' | last %}
{% assign array = array | concat: key | append: ':' | concat: value %}
{% endfor %}
{{ array }}
Creating associative arrays or arrays with named indexes is not possible in Liquid. However, as a work around, you can create 2 arrays. Treat the same indexes of one array as key while other as value.
A sample implementation would look like
{% assign text = 'key1:value1,key2:value2,anotherKey:anotherValue' %}
{% assign objArr = text | split: ',' %}
{% assign keyArr = ''%}
{% assign valArr = ''%}
{% for obj in objArr %}
{% assign key = obj | split: ':' | first %}
{% assign value = obj | split: ':' | last %}
{% assign keyArr = keyArr| append: ',' | append: key %}
{% assign valArr = valArr| append: ',' | append: value %}
{% endfor %}
{% assign keyArr = keyArr | remove_first: ',' | split: ',' %}
{% assign valArr = valArr | remove_first: ',' | split: ',' %}
{% for obj in objArr %}
{{keyArr[forloop.index0]}} : {{valArr[forloop.index0]}}
<br/>
{% endfor %}
The above code does a basic job. Do not forget to compare both array lengths to identify if all key value pairs were created perfectly as per logic.

Is there any alternative of PHP In_array() in Shopify?

I tried lots of things but didn't get what exactly I am looking for.
Below is the example of what type of array I am looking for in Shopify,
$array['bag'] = 2; $array['shoes'] = 3; $array['xyz'] = 6;
Here is the sample of what and how I am looking for my array variable in shopify.
Where
bag, shoes, xyz
are product type
and 2,3,6
are number of products added for specific product type.
I know it's easy in PHP, but don't know how to do in Shopify liquid code.
As per Shopify documentation, you cannot initialize arrays. However, you can use split filter to create one dimensional array. You cannot create associative arrays using this. However, as a workaround, use 2 arrays of same length where same index in both arrays point to related key and value as of associated array. Example code
{% assign product_type = "type-1|type-2|type-3" | split: '|' %}
{% assign product_count = "1|2|3" | split: '|' %}
{% for p_type in product_type %}
{{ p_type }}
{{ product_count[forloop.index0] }}
{% endfor %}
Expected output
Product Type Count
type-1 1
type-2 2
type-3 3
For your particular scenario explained in comments, have a look at below code and code comments. I have used checkout object for sample code. You may adjust according to your need.
// declare 2 vars to create strings - that will be converted to arrays later
{% assign product_type = "" %}
{% assign product_count = "" %}
// iterate over line_items in checkout to build product_type string
{% for line_tem in checkout.line_items %}
// if product_type exists , then skip -- unique product types
{% if product_type contains line_tem.product.type%}
{% else %}
{% assign product_type = product_type | append: '#' | append: line_tem.product.type %}
{% endif %}
{% endfor %}
// remove first extra hash and convert to array
{% assign product_type = product_type | remove_first: "#" | split: '#' %}
// iterate over unique product type array generated earlier
{% for product_type_item in product_type %}
// set product count for this product type to zero initially
{% assign total_count = 0 %}
// iterate over all lin items and +1 if same product type
{% for line_tem in checkout.line_items %}
{% if product_type_item == line_tem.product.type%}
{% assign total_count = total_count | plus: 1 %}
{% endif %}
{% endfor %}
// append count to product count string
{% assign product_count = product_count | append: '#' | append: total_count %}
{% endfor %}
// remove first extra hash and convert to array
{% assign product_count = product_count | remove_first: "#" | split: '#'%}
{{-product_type-}}
{{-product_count-}}

Assign a value to array by index in Liquid

I am inside of a bit complex loops and I need to assign a value to an array by index, so that if the value is already there it will replace it, if not it will create it.
So I need to do something like this:
{% assign arr = '' | split: '' %}
{% assign arr[index] = value %}
which is not working, the array is still empty.
Is there any workaround to do this?
There is no direct workaround.
You can always re-create the array with a default value though that would only give you a single value.
One potential work around would be to re-create the source and fill in any missing defaults then re-split into an array
{% assign arr = someValue | split: '' %} <!-- splitting to single chars ? -->
{% assign withDefaults = '' %}
{% for ...%}
{% unless arr[loop.index0] == true %}
{% withDefaults = withDefaults | append : 'defaultValue,' %}
{% else %}
{% withDefaults = withDefaults | append : arr[loop.index0] | append : ',' %}
{% endfor %}
{% assign arr = withDefaults | split: ',' %} <!-- you'll have an extra blank element but that may not matter -->

How to use variables in Twig filter 'replace'

Handing over an array from php of form
$repl_arr = array('serach-string1' => 'replace1', ...)
to a Twig template I would like to replace strings in a Twig variable per replace filter similar to this:
{{ block | replace({ repl_arr }) }}
That does not function and neither a variable loop like
{% for key,item in repla_arr %}
{% set var = block | replace({ key : item }) %}
{% endfor %}
does. What is wrong with it? How could it work?
Either you pass the whole array, or you loop the replaces.
But when looping the replaces you need to wrap key and value in parentheses to force interpolation of those
{% set replaces = {
'{site}' : '{stackoverflow}',
'{date}' : "NOW"|date('d-m-Y'),
} %}
{% set haystack = '{site} foobar {site} {date} bar' %}
{{ haystack | replace(replaces) }}
{% set output = haystack %}
{% for key, value in replaces %}
{% set output = output|replace({(key) : (value),}) %}
{% endfor %}
{{ output }}
fiddle