How can I sort nested Data with Jekyll/Liquid - jekyll

Is it possible to sort nested data?
I tried it with assign, but an error occurred:
Cannot sort a null object
My Liquid-Code
<ol>
{% for fuehrung2 in site.data.programmfk %}
{% assign fuehrung1 = fuehrung2['fuehrungen'] | sort: "start" %}
{% for fuehrung in fuehrung1 %}
<li>{{ fuehrung.titel }}<br>{{ fuehrung.start | date: "%H:%M" }}{% if fuehrung.ende != nil %}–{{ fuehrung.ende | date: "%H:%M" }}{% endif %} Uhr <span style="color:#cc0000">⟩</span> Treffpunkt: {{ fuehrung.treffpunkt | join: " & " }}</li>
{% endfor %}
{% endfor %}
</ol>
My programmfk.yml (just a little part of …)
- nummer: "04"
titel: Elektrotechnik und Informationstechnik
infostand: Lothstraße 64
gespraech:
start: 2020-03-28 10:00
ende: 2020-03-28 15:00
punkte:
- Vertiefungsrichtung Kommunikationstechnik
- Demos Labor Schaltungstechnik
- Robotikwerkstatt
- Smart Car
- Computer Kicker
praesentation:
start:
ende:
punkte:
vortraege:
- titel: Vorstellung der Studiengänge
start: 2020-03-28 11:00
ende: 2020-03-28 12:00
raum: E 0.103
fuehrungen:
- titel: Nachrichtensatellitentechnik
treffpunkt: Infostand der Fakultät
start: 2020-03-28 12:00
ende: 2020-03-28 12:30
- titel: Regelungstechnik
treffpunkt: Infostand der Fakultät
start: 2020-03-28 12:30
ende: 2020-03-28 13:00
- titel: Aufbau- und Verbindungstechnik
treffpunkt: Infostand der Fakultät
start: 2020-03-28 13:00
ende: 2020-03-28 13:30
- titel: Regelungstechnik
treffpunkt: Infostand der Fakultät
start: 2020-03-28 13:30
ende: 2020-03-28 14:00
I'm trying to sort fuehrungen by start-time start.
#david-jacquel
If I use a yml-file online for fuehrungen than it's working.
My fuehrungen.yml
fuehrungen:
- titel: Motorenlabor
treffpunkt: Infostand der Fakultät
start: 2020-03-28 11:00
ende: 2020-03-28 11:45
- titel: Motorenlabor
treffpunkt: Infostand der Fakultät
start: 2020-03-28 12:00
ende: 2020-03-28 12:45
- titel: Nachrichtensatellitentechnik
treffpunkt: Infostand der Fakultät
start: 2020-03-28 12:00
ende: 2020-03-28 12:30
- titel: Regelungstechnik
treffpunkt: Infostand der Fakultät
start: 2020-03-28 12:30
ende: 2020-03-28 13:00
- titel: Aufbau- und Verbindungstechnik
treffpunkt: Infostand der Fakultät
start: 2020-03-28 13:00
ende: 2020-03-28 13:30
- titel: Regelungstechnik
treffpunkt: Infostand der Fakultät
start: 2020-03-28 13:30
ende: 2020-03-28 14:00
- titel: Einführung und experimentelle Laborführung Technische Redaktion und Kommunikation
treffpunkt: Infostand der Fakultät
start: 2020-03-28 11:45
ende: 2020-03-28 12:30
- titel: Führung Bereich Papier und Verpackung (Labore, Maschinenhalle etc.)
treffpunkt: Infostand der Fakultät
start: 2020-03-28 12:15
ende: 2020-03-28 13:00
- titel: "Laborführung: Studiengang Druck- und Medientechnik „Produktion von Fotobüchern“"
treffpunkt: Infostand der Fakultät
start: 2020-03-28 13:50
ende: 2020-03-28 14:15
The Liquid/HTML
{% assign fuehrungSort = site.data.fuehrungen['fuehrungen'] | sort: "start" %}
{% for fuehrung in fuehrungSort %}
<li><b>{{ fuehrung.start | date: "%H:%M" }} Uhr</b> {{ fuehrung.titel }}</li>
{% endfor %}
But in my original programmfk.yml I have one more level before and I'm not able to get the result with that level.
- nummer:
…
fuehrungen:
- title:
…

As seen in your repository, you somewhere have an empty vortraege hidden in your code :
- nummer: "13"
titel: Studium Generale und Interdisziplinäre Studien
infostand: Lothstraße 64
gespraech:
start: 2020-03-28 10:00
ende: 2020-03-28 15:00
punkte:
praesentation:
start:
ende:
punkte:
vortraege:
fuehrungen:
In your code vortraege['vortraege'] can be null or empty and contains no object containing a start field to sort on. This explains your error.
You can test if you have anything to sort before sorting {% if vortraege['vortraege'].size %}
That gives :
<ol>
{% for vortraege in site.data.programmfk %}
{% if vortraege['vortraege'].size %}
{% assign vortraegeSort = vortraege['vortraege'] | sort: 'start' %}
{% for vortrag in vortraegeSort %}
<li><b>{{ vortrag.start | date: "%H:%M" }} Uhr</b> {{ vortrag.titel }}</li>
{% endfor %}
{% endif %}
{% endfor %}
</ol>
Edit: Sorting all vortraege from programmfk data file in one time
{% comment %} --- Create an empty array --- {% endcomment %}
{% assign vortraege_all = '' | split: '' %}
{% for vortraege in site.data.programmfk %}
{% comment %} --- Only if vortraege['vortraege'] contains something
-- {% endcomment %}
{% if vortraege['vortraege'].size %}
{% comment %} --- Concatenate vortraege_all and current non-empty vortraege['vortraege'] --- {% endcomment %}
{% assign vortraege_all = vortraege_all | concat: vortraege['vortraege'] %}
{% endif %}
{% endfor %}
{% comment %} ----------- Sorting -------------- {% endcomment %}
{% assign vortraege_all = vortraege_all | sort: 'start' %}
<ol>
{% for v in vortraege_all %}
<li><b>{{ v.start | date: "%H:%M" }} Uhr</b> {{ v.titel }}</li>
{% endfor %}
</ol>

Related

Output the swatch image of a product Shopify

On my Shopify site I am editing the account/order page. On the individual order summary page I am displaying the product swatch image along with the product color name. I have gotten the product color name to work, however I don't know how I could get the image of the product swatch to show. Does anyone have any ideas?
<div class="account-single-order-product-card-info-variant">
<span class="account-single-order-product-card-info-variant-image-padding">
<span class="account-single-order-product-card-info-variant-image"> </span>
</span>
<span class="account-single-order-product-card-info-variant-text">
{%- for option in line_item.product.options -%}
{% if option == 'Color' %}
{{ line_item.product.variants.first.options[forloop.index0] }}
{% endif %}
{% endfor %}
</span>
</div>
Here is my swatch.liquid to show how they display on my product page (if this helps) -
{%- include 'check_not_feature_img' -%}
<table class="variations{%- if settings.swatch_design == '2' %} variant_square{%- elsif settings.swatch_design == '3' %} variant_simple{%- endif -%}" cellspacing="0">
{%- assign option_index = 0 -%}
{%- if settings.color_name_check != blank -%}{%- assign _gl_color_name = settings.color_name_check | replace: ' ,', ',' | replace: ', ', ',' | split: ',' -%}{%- assign gl_color_name = _gl_color_name | downcase -%}{%-else-%}{%- assign gl_color_name = '\nathan_dt\' -%}{%-endif-%}
<tbody>
{%- for option in product.options_with_values -%}
{%- assign option_index = forloop.index0 -%}
{%- assign downcased_option = option.name | downcase -%}
{%- assign downcased_option_check = option.name | downcase | prepend: '"' | append: '"' | strip -%}
{%- if downcased_option == 'color' or downcased_option == 'colour' or gl_color_name contains downcased_option_check -%}
<tr data-option-index="{{ option_index }}" id="gl_select_{{ option_index }}">
<td class="label"><label for="{{ option_index }}">{{ option.name }}</label></td>
<td class="value with-swatches">
<div class="swatches-select" data-id="{{ option_index }}" data-option-index="{{ option_index }}">
{%-assign index = 0 %}
{%- for value in option.values -%}
<div class="basel-swatch bg_color basel-tooltip swatch-size-{{settings.swatch_size}}{%- if settings.swatch_style == '1' %} colored-swatch{%- else %} image-swatch{%- endif %}{%- if first_available_variant and option.selected_value == value %} active-swatch{%- elsif forloop.first == true and product.selected_variant == blank and first_available_variant == false %} active-swatch{%- elsif option.selected_value == value and product.selected_variant != blank and first_available_variant == false %} active-swatch{%- endif %} bg_color_{{ value | handleize }} bg_{{ value | handleize }} swatch-enabled" data-value='{{ value | handleize }}' data-image-id="{{ featured_image_id[index] }}">
<span class="basel-tooltip-label">{{ value }}</span>{{ value }}
</div>
{%-assign index = index | plus: 1 %}
{%- endfor -%}
</div>
</td>
</tr>
{%- else- %}
<tr data-option-index="{{ option_index }}" id="gl_select_{{ option_index }}">
<td class="label"><label for="{{ option_index }}">{{ option.name }}</label></td>
<td class="value with-swatches">
<div class="swatches-select" data-id="{{ option_index }}" data-option-index="{{ option_index }}">
{%- for value in option.values -%}
<div class="basel-swatch basel-tooltip text-only swatch-size-{{settings.swatch_size}}{%- if first_available_variant and option.selected_value == value %} active-swatch{%- elsif forloop.first == true and product.selected_variant == blank and first_available_variant == false %} active-swatch{%- elsif option.selected_value == value and product.selected_variant != blank and first_available_variant == false %} active-swatch{%- endif %} bg_{{ value | handleize }} swatch-enabled" data-value='{{ value | handleize }}'>
<span class="basel-tooltip-label">{{ value }}</span>{{ value }}
</div>
{%- endfor -%}
</div>
</td>
</tr>
{%- endif -%}
{%- endfor -%}
</tbody>
</table>
By the product swatch I assume that you referring to the variant.image since there is no image for a swatch.
So you can do this:
{{ line_item.image | default: line_item.product.featured_image | img_url: '300x' | img_tag }}
Where we target the variant image and provide a default image in case the variant doesn't have an image which will fallback to the product image.

How can I add a random number method in liquid that can called multiple times?

How can I add a random number in my liquid template files?
Is there something similar to PHP's rand()?
I have seen this used before, but if the variable is called in other places it displays the same number and is not randomized, even if multiple variables are created.
{% assign min = 65 %}
{% assign max = 80 %}
{% assign diff = max | minus: min %}
{% assign randomNumber = "now" | date: "%N" | modulo: diff | plus: min %}
This is how I am trying to accomplish it now:
<div class="contentDoubleWideWrap">
<div class="contentDoubleWideWrap">
{% assign min = 50 %}
{% assign max = 1000000 %}
{% assign diff = max | minus: min %}
{% assign randomNumber = site.time | date: "%s" | modulo: diff | plus: min %}
<div id="div-gpt-ad-1553024073723-{{ randomNumber }}" data-ad="div-gpt-ad-1553024073723-0" class="tmsads adContentDouble"></div>
</div>
<div class="contentDoubleWideWrap">
{% assign min2 = 50 %}
{% assign max2 = 1000000 %}
{% assign diff2 = max2 | minus: min2 %}
{% assign randomNumber2 = site.time | date: "%s" | modulo: diff2 | plus: min2 %}
<div id="div-gpt-ad-1553024121524-{{ randomNumber2 }}" data-ad="div-gpt-ad-1553024121524-0" class="tmsads adContentDouble"></div>
</div>
</div>
What you could do, it to define a prng, for example a linear congruential random number generator.
This works the following way:
you need a random seed (this can be date: "%N"),
you need the parameters of the rng: m, a, c
you need to specify the index into the generated pseudorandom series.
A good set of parameters:
m=2^31
a=1103515245
c=12345
This is used by GCC rand, see here for the link:
https://en.wikipedia.org/wiki/Linear_congruential_generator
the random number is created by (in pseudo code):
let x=seed
loop index times:
x=(a*x+c)%m
In this setting the random number you get from the generator can be further conditioned by the max, min, module parameters like you already did this for the date.
The only difference would be to use x instead of assign randomNumber = site.time | date: "%s"
You have almost everything in your code to do this. If you only need a few random numbers I would most probably simply unroll the loop in the pseudocode. Does that work for you?
Let me show how to implement the example you gave above:
<div class="contentDoubleWideWrap">
<div class="contentDoubleWideWrap">
{% assign min = 50 %}
{% assign max = 1000000 %}
{% assign diff = max | minus: min %}
{% assign seed = site.time | date: "%N" %}
{% assign randomNumber1 = seed | modulo: diff | plus: min %}
<div id="div-gpt-ad-1553024073723-{{ randomNumber1 }}" data-ad="div-gpt-ad-1553024073723-0" class="tmsads adContentDouble"></div>
</div>
<div class="contentDoubleWideWrap">
{% assign m = 2147483648 %}
{% assign a = 1103515245 %}
{% assign c = 12345 %}
{% assign min2 = 50 %}
{% assign max2 = 1000000 %}
{% assign diff2 = max2 | minus: min2 %}
{% assign randomNumber2 = seed | times: a | plus: c | modulo: m | modulo: diff2 | plus: min2 %}
<div id="div-gpt-ad-1553024121524-{{ randomNumber2 }}" data-ad="div-gpt-ad-1553024121524-0" class="tmsads adContentDouble"></div>
</div>
</div>
In this case x0 is seed and x1 the next state of the rng, you get randomNumber2 from that by modulo diff |plus: min2. To generate randomNumber3, you would repeate the a c and m filters one more time, like:
times: a | plus: c | modulo: m |times: a | plus: c | modulo: m
This can work if you only have a few random numbers to generate.
If you need more random numbers, you can use a loop instead to do the same thing.
The problem is that basically a static site is generated. What you could do is to move the random number generation to the client side, by using javascript. Does that work for you?
I bet it is possible via plugins, e.g. https://github.com/codecalm/jekyll-random
random_number(index, min=0, max=100, round=0)
Returns a number between min and max based on index. By default it returns a number between 0 and 100.
{% for i in (1..100) %}
{{ i }} - {{ forloop.index | random_number: 0, 10 }}
{% endfor %}
The code above returns random numbers like:
1 - 6
2 - 1
3 - 6
4 - 8
5 - 4
6 - 7
7 - 1
...

Formatting numbers in Jekyll without using custom plugins

I am using a Jekyll collection to manage elements of a lesson. Each element has a duration in its YAML front matter:
---
duration: 5
---
I loop over the elements to figure out the start time for each element:
{% assign current = 0 %}
<table>
{% for element in site.elements %}
<tr>
<td>{{ forloop.index }}</td>
<td>{{ element.title }}</td>
<td>{{ current | divided_by: 60 }}:{{ current | modulo: 60 }}</td>
</tr>
{% assign current = current | plus: element.duration %}
{% endfor %}
</table>
This gives the right output, but is poorly formatted: in particular, some times are shown like this:
9:5
instead of:
09:05
I can't think of a way to use date formatting to get the output I want, since the "times" are just minutes, not dates. Is there a way to format numbers in Jekyll with a specified number of decimal places and leading zeroes?
You can use conditional formating:
{% assign h = timeInMinutes | divided_by: 60 %}
{% if h <= 10 %}{% assign h = h | prepend: '0' %}{% endif %}
{% assign m = timeInMinutes | modulo: 60 %}
{% if m <= 10 %}{% assign m = m | prepend: '0' %}{% endif %}
{{ h }}:{{ m }}
Or a string trick :
{{ timeInMinutes | divided_by: 60 | plus: 100 | slice: 1, 2 }}
:
{{ timeInMinutes | modulo: 60 | plus: 100 | slice: 1, 2 }}

Jekyll archive link in another language than english

I have this code to generate my archive links with Jekyll
<div class="col-lg-4">
<h3>Archives</h3>
<ul>
{% for post in site.posts %}
{% assign thisyear = post.date | date: "%B %Y" %}
{% assign prevyear = post.previous.date | date: "%B %Y" %}
{% assign counter = counter | plus: 1 %}
{% if thisyear != prevyear %}
<li>{{ thisyear }} ({{ counter }})</li>
{% assign counter = 0 %}
{% endif %}
{% endfor %}
</ul>
</div>
As you can see it will render something like "May 2015 (3)". I'm trying to achieve something like "Mai 2015 (3)".
Here is how it's done for a simpler loop :
<div class="col-lg-4">
<h3>Derniers articles</h3>
<ul class="posts">
{% for post in site.posts limit:10 %}
<li>
<span>
{{ post.date | date: '%d' }}
{% assign m = post.date | date: "%-m" %}
{% case m %}
{% when '1' %}janvier
{% when '2' %}février
{% when '3' %}mars
{% when '4' %}avril
{% when '5' %}mai
{% when '6' %}juin
{% when '7' %}juillet
{% when '8' %}août
{% when '9' %}septembre
{% when '10' %}octobre
{% when '11' %}novembre
{% when '12' %}décembre
{% endcase %}
{{ post.date | date: '%Y' }}
</span> »
<a href="{{ BASE_PATH }}{{ post.url }}">
{{ post.title }}</a>
</li>
{% endfor %}
</ul>
</div>
This code will render "15 janvier 2015" for instance instead of "15 january 2015".
Up to now I did not manage to merge the two loops to create what I want to achieve. Any idea ?
EDIT
Made some progress with that code :
But there is a big problem, it will only render one month instead of all months.
So for now it renders
Mai 2015 (4)
instead of
Mai 2015 (1)
Juin 2015 (1)
Juillet 2015 (2)
<ul>
{% for post in site.posts %}
{% assign thisyear = post.date | date: "%Y" %}
{% assign prevyear = post.previous.date | date: "%Y" %}
{% assign m = post.date | date: "%-m" %}
{% assign counter = counter | plus: 1 %}
{% if thisyear != prevyear %}
<li><a href="/archive/#{{ post.date | date:"%B %Y" | }}">{% case m %}
{% when '1' %}janvier
{% when '2' %}février
{% when '3' %}Mars
{% when '4' %}avril
{% when '5' %}mai
{% when '6' %}juin
{% when '7' %}juillet
{% when '8' %}août
{% when '9' %}septembre
{% when '10' %}octobre
{% when '11' %}novembre
{% when '12' %}décembre
{% endcase %}{{ thisyear }} ({{ counter }})</a></li>
{% assign counter = 0 %}
{% endif %}
{% endfor %}
</ul>
With {% if thisyear != prevyear %}, you're grouping by year only.
You need to group by month and year.
You print IF :
not same month (thismonth != prevmonth)
OR same month but not same year (thisyear != prevyear and thismonth == prevmonth)
This works :
<ul>
{% for post in site.posts %}
{% assign thisyear = post.date | date: "%Y" %}
{% assign thismonth = post.date | date: "%-m" %}
{% assign prevyear = post.previous.date | date: "%Y" %}
{% assign prevmonth = post.previous.date | date: "%-m" %}
{% assign counter = counter | plus: 1 %}
{% if (thismonth != prevmonth) or (thisyear != prevyear and thismonth == prevmonth) %}
<li><a href="/archive/#{{ post.date | date:"%B %Y" | }}">
{% case thismonth %}
[ month in french here ]
{% endcase %}
{{ thisyear }} ({{ counter }})
</a></li>
{% assign counter = 0 %}
{% endif %}
{% endfor %}
</ul>

Get the difference in days between two dates in Jekyll

I would like to get the difference in days between two dates in Jekyll. How can I achieve this?
{% capture currentDate %}{{ site.time | date: '%Y-%m-%d' }}{% endcapture %}
{{currentDate}}
{% capture event_date %}{{ entry.date }}{% endcapture %}
{% if event_date < currentDate %}Yes{% else %}No{% endif %}
In the entry there is my YAML:
---
title: ChartLine C3
type: charts
description: Chart with round for prisma
id: c3-1
date: 2015-07-18
---
The way to do this in Liquid (Jekyll's templating engine) is silly:
{% assign today = site.time | date: '%s' %}
{% assign start = '20-01-2014 04:00:00' | date: '%s' %}
{% assign secondsSince = today | minus: start %}
{% assign hoursSince = secondsSince | divided_by: 60 | divided_by: 60 %}
{% assign daysSince = hoursSince | divided_by: 24 %}
Hours: {{hoursSince}}
Days: {{daysSince}}
Hours: 27780
Days: 1157
Note that Liquid's divide_by operation rounds automatically.
Remainder hours: {{hoursSince | modulo: 24}}
Remainder hours: 12
If this annoys you as much as it annoyed me then you can do this to recover decimal places:
{% assign k = 10 %}
{% assign divisor = 24 %}
{% assign modulus = hoursSince | modulo: 24 | times: k | divided_by: divisor %}
{{daysSince}}.{{modulus}}
1157.5
Add more zeroes to k to add more decimal places.
No-one actually answered the question but it's not impossible.
You can get the difference between years, say how many years have passed since the year 2000 would be:
{{ site.time | date: '%Y' | minus:2000 }}
As for days between two dates, that's harder.. best bet is to look at the plugin:
https://github.com/markets/jekyll-timeago
It's output might be a bit verbose though but you could modify the plugin itself (have a look through the code, it's not too complex)
If all you want to do is know whether a date from your Front Matter is earlier than the system time then you can use the ISO 8601 date format and rely on the lexicographical ordering. It's sort of cheating but it'll work for the example you provided.
It's important to capture both site.time and the date from your Front Matter (page.past_date and page.future_date in the example below) in ISO 8601 format in order for this trick to work.
---
layout: default
past_date: 2015-03-02
future_date: 2016-03-02
---
{% capture currentDate %}{{ site.time | date: '%F' }}{% endcapture %}
{% capture pastDate %}{{ page.past_date | date: '%F' }}{% endcapture %}
{% capture futureDate %}{{ page.future_date | date: '%F' }}{% endcapture %}
<br>currentDate: {{currentDate}}
<br>PastDate earlier than currentDate? {% if pastDate < currentDate %}Yes{% else %}No{% endif %}
<br>FutureDate earlier than currentDate? {% if futureDate < currentDate %}Yes{% else %}No{% endif %}
Gives me the following output:
currentDate: 2015-07-12
PastDate earlier than currentDate? Yes
FutureDate earlier than currentDate? No
I computed the difference in years between current date and the one of the post to render a specific message, and it works pretty nicely:
{% assign currentYear = site.time | date: '%Y' %}
{% assign postYear = post.date | date: '%Y' %}
{% assign postAge = currentYear | minus: postYear | minus: 1 %}
{% if postAge >= 3 %}
<div class="maybe-obsolete">
This post has been published more than {{ postAge }} years ago, and parts
of its contents are very likely to be obsolete by now.
</div>
{% endif %}