Macro causing white space in Jinja2? - python-3.x

I was expecting that this macro
{% macro join_them(first) -%}
{% set data = [] %}
{% if first not in ["", None, "None"] %}
{{ data.append(first) }}
{% endif %}
{% for arg in varargs %}
{% if arg not in ["", None, "None"] %}
{{ data.append(arg) }}
{% endif %}
{% endfor %}
{{' '.join(data)}}
{%- endmacro %}
Would render this
<td> * {{ join_them(docs["Author"]["Name"].title, docs["Author"]["Name"].first, docs["Author"]["Name"].middle, docs["Author"]["Name"].last, docs["Author"]["Name"].suffix) }}</td>
As this line, without the use of the macro, does
<td> * {{ ' '.join([docs["Author"]["Name"].title, docs["Author"]["Name"].first, docs["Author"]["Name"].middle, docs["Author"]["Name"].last, docs["Author"]["Name"].suffix])}}</td>
The non macro version is working fine, and outputing the name information correctly, but the Macro version is putting out white space and some "None" strings.
e.g.
<td> *
None
None
Computer Community
*</td>
<td>Computer Community </td>
Anyone have any suggestions on what I'm missing here? Or a better way to handle this? I don't typically use macros in Jinja2, but this seemed a perfect opportunity to do so.

The macro is indeed causing those blank lines, and you had the right approach already, you need to use whitespace control to remove them. You only placed them at the beginning and the end of your macro but note that:
You can also strip whitespace in templates by hand. If you add a minus sign (-) to the start or end of a block, a comment, or a variable expression, the whitespaces before or after that block will be removed
Source: https://jinja.palletsprojects.com/en/3.1.x/templates/#whitespace-control, emphasis, mine
So, for each and every single block in your macro, you have to repeat this operation. To be extra sure, what you could do here is to frame any block in the macro with the minus sign.
Then for the None appearing, this is because you are using an expression delimiter {{ ... }} to append to your array, when you should use a statement delimiter {% ... %} and a do, as pointed in the documentation
If the expression-statement extension is loaded, a tag called do is available that works exactly like the regular variable expression ({{ ... }}); except it doesn’t print anything. This can be used to modify lists
{% do navigation.append('a string') %}
Source: https://jinja.palletsprojects.com/en/3.1.x/templates/#expression-statement
So, your macro should end up looking like:
{%- macro join_them() -%}
{%- set data = [] -%}
{%- for arg in varargs if arg not in ["", None, "None"] -%}
{%- do data.append(arg) -%}
{%- endfor -%}
{{- ' '.join(data) -}}
{%- endmacro -%}
Note that, if you don't have the expression-statement extension loaded, you could also replace the {% do ... %} blocks by this inline-if 'hack':
{{- '' if data.append(arg) -}}

Related

Laravel: Return a button if two columns in a table are true, if not, don' return anything

I need to loop through a table in .twig and if two columns are equal, it should return a button to delete., for example:
loop through boxes and if the quantity of boxes is equal to the available_quantity then I can show a delete button.
{% for toy in boxes.toys %}
{% if toy.quantity == toy.available_quantity %}
Delete
{% endif %}
{% endfor %}
Right now, this returns a button for each true case. I just need one button if all are true, if on or all are false I dont want a button
What you could do is something like this, as soon as you find a row where the quantities are different, set the value to false, so it'll never display the button:
{% set delete = true %}
{% for toy in boxes.toys %}
{% if toy.quantity != toy.available_quantity %}
{% set delete = false %}
{% endif %}
{% endfor %}
{% if delete %}
Delete
{% endif %}
Unfortunately Twig hasn't got a way to then break out of the loop, so it'll continue to loop over all the entries, but that won't be a problem.
Alternatively, you can use this method which will only loop over entries that match a certain condition, to do basically the same thing:
{% set delete = true %}
{% for toy in boxes.toys if toy.quantity != toy.available_quantity %}
{% set delete = false %}
{% endfor %}
{% if delete %}
Delete
{% endif %}
In this case, it'll only loop over the entries which have the values not matching, and in that case, set delete to false.
I came across a fix, not sure how ideal or correct this is. Im simply running the check inside the anchor tag and adding a hide class if quantities don't match up
<a href="" type="button" class="btn btn-xs btn-danger
{% for toy in boxes.toys %}
{% if toy.quantity != toy.available_quantity %}
hide
{% endif %}
{% endfor %}
">Delete</a>

What is the hyphen for, apart from trimming whitespace?

I understand using
{{- remove leading spaces -}}
for trimming whitespace, but in the example below, and that I've seen loads, what is the point?
{%- if true -%}
or
{%- endfor %}
What purpose does it serve in a control statement? I can find nothing in the documentation about this specifically, but if there is something, please, point me in the right direction!
It applies to the full block,
{% for i in 1..10 %}
{{ i }}
{% endfor %}
{%- for i in 1..10 -%}
{{ i }}
{%- endfor -%}
output
1
2
3
4
5
6
7
8
9
10
12345678910
demo

Prevent quotes from being converted to HTML entities in twig ternary operator

The following does not convert the quotes to HTML entities
{% for row in files %}
<tr data-id="{{ row.id }}"><td>{{ row.name }}</td></tr>
{% endfor %}
The following does convert the quotes to HTML entities
{% for row in files %}
<tr{{ row.id?' data-id="'~row.id~'"' }}><td>{{ row.name }}</td></tr>
{% endfor %}
How can I prevent quotes from being converted to HTML entities in a twig ternary operator?
You should try the |raw filter (check out the documentation).
This is because in general, everything that twig prints out will be escaped to avoid things like cross-site-scripting. An exception is made for entirely static values like {{ '<b>static value</b>' }} which will not be escaped.
In your case, the following should work:
{% for row in files %}
<tr{{ (row.id?' data-id="'~row.id~'"')|raw }}><td>{{ row.name }}</td></tr>
{% endfor %}

Breaking Nunjucks Loop

I've racked my brain in all directions but still no solution. Maybe someone has some advice?
I have the following block in an ExpressJS app using Nunjucks as the templating engine.
{%- for course in courses -%}
{%- for rcourse in report.courses -%}
{%- if rcourse.id == course.id -%}
<li {{ 'class="column-pre"' if loop.last else ''}}><span>Cool!</span></li>
{%- else -%}
<li></li>
{%- endif -%}
{%- endfor -%}
{%- endfor -%}
My problem: I need to break the inner report.courses loop when the conditional proves truth-y. Basically, the moment that I print the non-empty <li> line, I need to jump to the next iteration of the courses loop.
I know that Nunjucks does't have a break for loops like Jinja2, Nunjucks' variables are scoped (so I can't set a sentinel-like variable that gets modified in the if/else statement), nor can I append to an array so that I can use array.length as a way to determine if I should print the <li></li> line.
Maybe someone has a clever solution?
I think the sentinel-like variable would not be affected by scoping:
{% for course in courses %}
{% set searchrcourse = true %}
{% for rcourse in report.courses %}
{% if searchrcourse %}
{% if rcourse.id == course.id %}
<li {{ 'class="column-pre"' if loop.last else ''}}><span>Cool!</span></li>
{% set searchrcourse = false %}
{% else %}
<li></li>
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}

Minus In twig block definition

What's the difference between this:
{%block body %}
and that
{%block body -%}
Just read something about it in the documentation, not sure if this will also apply on {% block ... %} tags.
Twig whitespace control
{% set value = 'no spaces' %}
{#- No leading/trailing whitespace -#}
{%- if true -%}
{{- value -}}
{%- endif -%}
{# output 'no spaces' #}
There's also another example given which trims the whitespace in front of the variable but doesnt't do it at the end - so the effect is only on one side.
{% set value = 'no spaces' %}
<li> {{- value }} </li>
{# outputs '<li>no spaces </li>' #}
The above sample shows the default whitespace control modifier, and how you can use it to remove whitespace around tags. Trimming space will consume all whitespace for that side of the tag. It is possible to use whitespace trimming on one side of a tag
So I think the difference in your given exmaples is that in the first block body there will be a whitespace after the block started. In your second example body - there's none after the block started.
Just read the documentation entry to see how it works.
EDIT
A simple example to demonstrate the example in the docu:
{% set value = 'NO space in source code after/before "value"' %}
<li> {{- value -}} </li>
...
outputs in Firebug in the HTML markup:
Whereas this
{% set value = 'space in source code after "value"' %}
<li> {{- value }} </li>
...
ouputs:
Notice the space between "value" and the closing </li> in the second example. So the minus - erases/trims a whitespace either before, after or on both sides of e.g. a variable.

Resources