An elegant way to get Jekyll collection item by name? - collections

In Jekyll 2.5.3, I use albums collection (because I need not only data stored, but also pages generated).
When Jekyll Data Files are used, you can get a data for particular item as simple as: site.data.albums[foo]
But with collections, things are much worse. All those ways I've tried just do nothing:
site.albums[foo]
site.collections.albums[foo]
site.collections.albums.docs[foo]
site.collections.albums.files[foo]
So I need to:
Loop through all collection items
For each of them get a bare name
Compare this name with some target name
If the name matches, finally assign collection item data to some variable to use
Any better suggestions?

I have just done this today, you are correct with your assertions. Here's how I did it:
<!-- this is in a partial with a 'name' parameter -->
{% for item in site.albums %}
{% assign name = item.path | split:"/" | last | split:"." | first %}
{% if name == include.name %}
{% assign collectionItem = item %}
{% endif %}
{% endfor %}
Usage
{{ collectionItem.title }}
{{ collectionItem.url }}
{{ collectionItem.path }}
It can even be used to populate values in other partials, like so:
{% include card.html title=workItem.title bg=workItem.card.bg href=workItem.url %}

As of Jekyll 3.2, you can use the filter where_exp to filter a collection by any of its properties. So if I have the following item in an albums collection:
---
title: My Amazing Album
---
...
I can grab the first match for an item by that name:
{% assign album = site.albums
| where_exp:"album", "album.title == 'My Amazing Album'"
| first %}
Note that I use the first filter because where_exp returns an array of matched items.
And then use it any way I like:
<h1>{{ album.title }}</h1>
I can't vouch for the build performance of this method, but I have to imagine it's better than a Liquid loop.

Related

How to "shuffle" includes from twig

I have a base template that includes multiple sub-templates and the code runs something like this:
<ul class="row portfolio list-unstyled mt-3 lightbox" id="grid">
<!-- summary section -->
{{ render(controller('App\\Controller\\ReadController::summary')) }}
{{ render(controller('App\\Controller\\BookController::random', {num: 3})) }}
{{ render(controller('App\\Controller\\WikiController::random')) }}
</ul><!-- / portfolio row -->
As can be seen, these "items" appear in a fixed order:summary goes first, then 3 random and then one random.
What I intend to do is to "shuffle" these items (in the above code snippet, there will be 5 items) so that the order is different in each refresh to give the end user some variation.
Is this possible to do in Twig?
UPDATE
I used #hcoat method and it is working. Will try the shuffle filter later.
As suggested to you in the comments above you can use the Array Extension or send it through a controller. I think having a controller that sends in the random list is the way to go.
However, sometimes it is useful to randomize a list with standard twig and in such cases you can do something like the following:
// Path and pram Array, pass empty hash if no params
{% set arr = [
["App\\Controller\\ReadController::summary", {}],
["App\\Controller\\BookController::random", {'num': 3}],
["App\\Controller\\WikiController::random", {}]
] %}
// create a list and merge arr array with random key
{% set list = {} %}
{% for item in arr %}
// There is a bug in some twig verions
// so concat a letter to ensure random key works
{% set list = list|merge({ (random()~'a'):(item) }) %}
{% endfor %}
// sort the list by the random key and render the output
{% for key in list|keys|sort %}
{{ render(controller(list[key][0], list[key][1])) }}
{% endfor %}
Now the sub-templates will be rendered in a random order.

Variant filter on Collection pages with Shopify

I'm trying to setup a filter in my collection pages.
So far I manage to setup a great custom tag filter like below:
<div class="collection-sort">
{% assign tags = 'Black, Slate, Military Green' | replace: ' ,', ',' | replace: ', ', ',' | split: ',' %}
<select id="FilterBy" class="collection-sort__input">
<option value="/collections/{{ collection.handle }}">Choose Color</option>
{% for tag in tags %}
{% if current_tags contains tag %}
<option value="/collections/{{ collection.handle }}/{{tag}} " selected>{{ tag }}</option>
{% elsif collection.all_tags contains tag %}
<option value="/collections/{{ collection.handle }}/{{tag}}">{{ tag }}</option>
{% endif %}
{% endfor %}
</select>
</div>
However, I'm looking to have a dynamic Size filter ( using Variant)
For this I tried the following:
<div class="collection-sort">
<span value="">Choose Size</span>
{% for variant in collection.variants %}
{% if variant.available %}
<span value="{{ variant.id }}" >{{ variant.size}}</span>
{% else %}
<span value="{{ variant.id }}" >{{ variant.size }}</span>
{% endif %}
{% endfor %}
</div>
But nothing appear i my dropdown . . . all my product have size entered as product option / variant . . .
Anyone managed to make this work ? It will be very helpful !
Thanks a lot
There are too many issue with your code in order for it to output anything.
Lets disect them one by one.
Collections does not have variants
With this line of code:
{% for variant in collection.variants %}
You are targeting the variants inside a collection but collections doesn't have variants. Products do have variants.
So the logic here is not correct.
Variants options are stored in different way
With the following code: {{ variant.size }} you are trying to get the variant option called size but it doesn't work that way.
You will have to get the option using the objects option1 , option2 or option3. If your variant option size is the first one you will get it this way: variant.option1.
The bad part
What you are trying to achive is not possible with liquid because of Shopify limitation.
In order to achieve this you will need to loop all of the products and take their variant options and filter only the unique ones and because of the hard limit ot 50 products per request this is a lost fight.
Workarounds ( there are a few )
All of the workarounds will require you to create a link_list that will hold all the available sizes that you will have to enter manually.
1) The most common one is to use a tag for the size and filter by tag, since the collections can filter products directly by tag.
2) Use collections that will store products based on the size and redirect to them when you filter a specific size.
3) Create a infinite AJAX requests that pull products based on your filter by checking if each product have the selected value and using the pagination as a way to load the next page.
These are the main options without using an App.
Good luck!

Rendering a choice field type (symfony2) as buttons

I have a problem which I cannot solve. How shall I approach the following: I have a choice field with 4 different entries, which I am rendering perfectly in a select form (twig). However, I would like to show the 4 entries as 4 separate buttons. How do I change the input type of those 4? Or do I have to do anything totally different?
Thanks for your help
-AS
edit (new following question):
Alexandru, thanks for your help. That was very helpful.
I added in the fields.html.twig the following:
{% block _appbundle_action_Style_widget -%}
{% for child in form %}
<input type="button" {{ block('widget_attributes') }}{% if value is defined %} value="{{ value }}"{% endif %}{% if checked %} checked="checked"{% endif %} />
{% endfor %}
{%- endblock _appbundle_action_Style_widget %}
However, when I render it it tells me that there is an array to string conversion, which somehow does not work. When I delete the widget attributes, value, and checked parts, the buttons are getting rendered without those then.
You have a few options to choose from:
You can create a new field type, let's call it "button_group", that will extend the "choice" type and write the custom Twig for it, something like this:
https://github.com/symfony/symfony/blob/2.7/src/Symfony/Bridge/Twig/Resources/views/Form/bootstrap_3_layout.html.twig#L95
You can use the "choice" field type with the option: expanded => true. And create a custom form template for it only for the form you need it in:
http://symfony.com/doc/current/cookbook/form/form_customization.html#what-are-form-themes
Ok I solved my problem. In order to access the value variable I have to use the following {{ child.vars.value }}. With this I can now take the variable and put it into the value tag.
Thanks for your help.

Symfony2 - get value displayed to user for choice field in twig

I am trying to display the "label" (ie, value displayed to user) of a choice field in my twig template. I'm using Symfony 2.3. I just need the label associated with the actual current value of the field.
I finally figured out a way to do it that requires looping through every choice option, but am now wondering if there is a more efficient way? Here is my working code:
{% for choice in form.myfield.vars.choices %}
{% if choice.value == form.myfield.vars.value %}
{{ choice.label }}
{% endif %}
{% endfor %}
I'm looking for something like this (non-working code):
{{ form.myfield.vars.choices[form.myfield.vars.value].label }}
Is there a more efficient way, or is my solution the best possible?

Use placeholders in translation using tags

In Symfony / Twig, I could use tags by using percentages in my translated block. For example:
Hello {{nickname}}
would become
{% trans %}Hello %nickname%{% endtrans %}
This works as expected. The array with placeholders that I pass to Twig, are automatically mapped to %placeHolder%. No extra work involved. So this works with my PHP array from the controller being:
Array('nickname' => 'rolandow')
When I want to use the nickname inside the translation block, all I have to do is surround it with percentages %. Unfortunately, this doesn't seem to work when I pass it to trans.
Now I would like to translate a whole block of text, using tags. I can't figure out how I can use the tags in my translation. So, my twig would look something like this:
{{ say.hello|trans }}
And my translation snippet
<trans-unit id="1">
<source>say.hello</source>
<target>Hello %nickName%, how are you doing today? lots-of-text-here</target>
</trans-unit>
I got it working by using this in my template, but it feels like doing things twice. I now need to put the array of placeholder into the trans function again. For example:
{{ say.hello|trans('%nickName%' : nickName) }}
If I want to use other tags that are given to twig in my controller, I need to pass them to the translator as well. Can't I just pass the complete array somehow?
{{ say.hello|trans('%nickname%': 'rolandow') }}
There are several questions here so let's cover them.
1) Twig's behaviour is not like a Doctrine query, where each parameter must be bounded. You can pass an array that contains unused parameters to trans, so if you don't want to specify {'key': 'value', 'key2': 'value2'...} to the filter, just pass the entire array (example: | trans(array)). That's #Luke point.
2) You can translate block of texts using several ways, the most simple is {% set %}. The {% set %} tag can be used two ways :
{% set var = expression %} or {% set var1, var2 = expression1, expression2 %} is the most known and used way: you just put some value inside one or several variables.
{% set var %} block of text {% endset %} allow you to set an entire block of text inside that variable. This is useful if you want to put that block into a filter (such as, escape, or in your case, trans).
So to translate a block of text, you'll do something like:
{% set variable %}
block to translate %placeholder%
{% endset %}
{{ variable | trans(array) }}
Anyway, I don't see any interest of translating a whole block in one time : we use | trans generally after a property (such as say.hello), and I can't imagine your xlf/yml translation file with such a design. If you want to use the translator just to fulfill placeholders, just use Twig as it is written for that job :-)
3) About replacing placeholder by %placeholder% in your parameters array's keys : the point of Twig is: put what you want as placeholder. In such a way, if your translated sentence contains several %, you can use $something$, #something# or even something as placeholder.
If your array keys does not contain those %, you need to add them, you don't have any choice. If you really want to do it on a Twig file, you can create a macro that will do the job for you, and put it in a file you import in your base layout.
Something like :
{% macro trans_pct(property, params) %}
{% set newParams = [] }
{% if params %}
{% for key, value in params %}
{% set newParams['%' ~ key ~ '%'] = value %}
{% endfor %}
{% endif %}
{{ property | trans(newParams) }}
{% endmacro %}
And then use it with {{ _self.trans_pct('hello.say', array) | trim }}.
Notes :
_self is the template where is stored the macro (see the documentation for more details).
trim is used because I wrote the macro with indentation and line breaks (that's cleaner to read). Those spaces are, by default, printed.

Resources