multiple arbitrary blocks in Jinja2 macros - jinja2

I use both Jinja2 and Nunjucks (depending on the project), but have yet to figure out how to create reusable elements with multiple blocks containing arbitrary HTML. For example (pseudo-code):
{% macro item(class) %}
<article class="{{ class }}">
<h3>{{ caller(1) }}</h3>
<p>{{ caller(2) }}</p>
</article>
{% endmacro %}
{% call item %}
Hello <abbr title="...">world</abbr>!
{% ---- %}
lorem <em>ipsum</em> dolor <strong>sit</strong> amet
{% endcall %}
Passing the respective blocks' HTML as regular arguments (i.e. strings) to the macro seems unrealistic.
A less contrived example might be Bootstrap-style forms:
<div class="form-group">
<label for="{{ id }}" class="control-label">$label</label>
<input type="{{ type }}" id="{{ id }}">
<p class="help-block">$hint</p>
</div>
Here both $label and $hint might be arbitrary blocks of HTML - perhaps there might even be multiple fields, defined outside the macro.
What's the recommended approach here?

You might find this useful for re-usable HTML components:
https://github.com/mozilla/nunjucks/pull/277
Example:
{% include 'search-box.html.twig' with {placeholder: 'Search users'} %}

You can use embed tag of atpl template engine.
Example:
{% embed "teasers_skeleton.twig" %}
{# These blocks are defined in "teasers_skeleton.twig" #}
{# and we override them right here: #}
{% block label %}
Some content for the label box
{% endblock %}
{% block hint %}
Some content for the hint box
{% endblock %}
{% endembed %}

Related

What is the "base" file that the Bokeh index.html extends?

I am a little confused about what {% extends base %} is extending at the start of index.html in Bokeh server application packages.
Examples of this can be seen in:
Bokeh Docs: Embedding in Templates
{% extends base %}
{% block contents %}
<div>
<p> Hello {{ user_id }}, AKA '{{ last_name }}, {{ first_name }}'! </p>
</div>
{% endblock %}
Bokeh Server Application Examples
Example code from the Gapminder package in templates/index.html
{% extends base %}
{% block title %}Bokeh Gapminder Example{% endblock %}
{% block postamble %}
<style>
{% include 'styles.css' %}
</style>
{% endblock %}
What is this "base" that is being extended?
I see that there is a "contents" block, "title" block, and "postamble" block from the above examples.
How do I know what other jinja blocks I can modify?
Thanks.

How to use a Jinja variable inside of sphinx ref directive or head tag?

I'm using sphinx and auto-summary to create my documentation. However, for each module a I want to generate customized list of references. However, to do that, I need to pass the objname to ref directive. How can I achieve that?
{{ fullname | escape | underline}}
.. automodule:: {{ fullname }}
Items
-----
{% block classestab %}
{% if classes %}
{% for objname in classes %}
- :ref:` {{objname}} <{{objname}}>`_ // doesn't work
{%- endfor %}
{% endif %}
{% endblock %}
I've also trying to use a jinja variable to create headings for my documentation. However I don't know to achieve this. The code below will not work of course
{% block table %}
{% if classes %}
{% for objname in classes %}
{{objname}}
===========
{%- endfor %}
{% endif %}
{% endblock %}

Twig set reusable piece of html

I'm creating some templates with Twig and have an issue.
I'm trying to load a piece of html that is used several times troughout a webshop. So my idea is to create a reusable piece of code that I can load everytime when needed.
The problem I'm facing is that when I do a for loop and then include that piece of code I get an empty return. With other words my code doesn't recognize the data that need to be loaded for each product in the for loop. It returns empty info boxes.
To clarify:
I have a main template index.html which calls a snippet to include some products (don't look at rain extension!!):
{% if featured %}
{% include 'snippets/products.rain' with {'products': featured, 'type': 'grid'} %}
{% endif %}
My products.rain snippet looks like this:
{% if type %}
{% if type == 'grid' %}
{% for product in products %} {# Products in this case = feautured products #}
<li class="item clearfix">.... etc etc .... </li>
{% endfor %}
{% elseif type == 'other-layout' %}
<div class="item">.... etc etc .... </div>
{% endif %}
{% endif %}
In the for loop there's html that's for 95% the same as in each for loop. I want to place that code inside a block that can be included in the for loops.
So what I did was:
{% set product_html %}
.... a lot of html ....
<a href="{{ product.url | url }}" title="{{ product.fulltitle }}">
<img src="{{ product.image }}" width="100" height="100" alt="{{ product.fulltitle }}"/>
</a>
{% endset %}
And then included in the for loop, like so:
{% if type %}
{% if type == 'grid' %}
{% for product in products %} {# Products in this case = feautured products #}
<li class="item clearfix">{{ product_html | raw }}</li>
{% endfor %}
{% elseif type == 'other-layout' %}
<div class="item">{{ product_html | raw }}</div>
{% endif %}
{% endif %}
However this returns the html that is set however with empty product.image and empty product.fulltitle.
I tried the same with set block, but that has the same result.
Is there anything I'm doing wrong....??
When you are using {% set %}, content inside your variable is not dynamic, it will only use data in your current context, see it live.
You can achieve your goal using 2 ways: using include or using macros.
As your piece of code for a product is small and not reused somewhere else, I suggest you to use macros:
{% macro product_html(product) %}
Current product is: {{ product }}
{% endmacro %}
{% import _self as macros %}
{% for product in products %}
{{ macros.product_html(product) }}
{% endfor %}
See it live

display data in liquid

I'm looking to display information from a csv file on a jekyll-generated site. I need to search for the appropriate category in the csv file, then display four of them on the page. Filtering to the selected category is no problem, but I'm having difficulty limiting the output to four.
Is there a way to apply a limit to an if statement? Or is there any other way to write this? I'm not that savvy in Liquid, so it's extremely likely that I'm missing an obvious solution.
Basic code to make all the applicable data show up on the screen:
{% for study in site.data.studies %}
{% if study.category contains "foo" %}
<div class="col-sm-3">
<h3>{{ study.title }}</h3>
<div class="list-of-attributes">
<h6>Attributes: </h6>
{{ study.attributes }}
</div>
</div>
{% else %}
{% continue %}
{% endif %}
{% endfor %}
I've also tried unless and tablerow, neither of which worked at all. Am I at least on the right track? How can I limit this forloop to stop at four items?
Thank you!
Ideally data should be filtered before rendering however you can also create a variable in liquid to hold the number of stuff rendered
{% assign rendered = 0 %}
{% for study in site.data.studies %}
{% if study.category contains "foo" %}
<div class="col-sm-3">
<h3>{{ study.title }}</h3>
<div class="list-of-attributes">
<h6>attributes: </h6>
{{ study.attributes }}
</div>
</div>
{% assign rendered = rendered | plus: 1 %}
{% if rendered == 4 %}
{% break %}
{% endif %}
{% endif %}
{% endfor %}
The ideal solution as I said would be to create your own filter which does all the work (filter by category and limit the number of results)
{% assign filtered = site.data.studies | my_custom_filter %}
{% for study in filtered %}
<div class="col-sm-3">
<h3>{{ study.title }}</h3>
<div class="list-of-attributes">
<h6>attributes: </h6>
{{ study.attributes }}
</div>
</div>
{% endfor %}
Presuming that your category is a string, not an array, you can do :
{% assign selected = site.data.studies | where: 'category','foo' %}
{% for study in selected limit:4 %}
<div class="col-sm-3">
<h3>{{ study.title }}</h3>
<div class="list-of-attributes">
<h6>Attributes: </h6>
{{ study.attributes }}
</div>
</div>
{% endfor %}
And if your category is a string like "foo, bar, baz" or and array of strings you can use the jekyll 3.2 where_exp filter like this :
{% assign selected = site.data.studies | where_exp:"item", "item.category contains 'foo'" %}

Check if an array is not empty in Jinja2

I need to check if the variable texts is defined or not in index.html.
If the variable is defined and not empty then I should render the loop. Otherwise, I want to show the error message {{error}}.
Basically this in PHP
if (isset($texts) && !empty($texts)) {
for () { ... }
}
else {
print $error;
}
index.html
{% for text in texts %}
<div>{{error}}</div>
<div class="post">
<div class="post-title">{{text.subject}}</div>
<pre class="post-content">{{text.content}}</pre>
</div>
{% endfor %}
How do I say this in jinja2?
I think your best bet is a combination of defined() check along with looking at the length of the array via length() function:
{% if texts is defined and texts|length > 0 %}
...
{% endif %}
To test for presence ("defined-ness"?), use is defined.
To test that a present list is not empty, use the list itself as the condition.
While it doesn't seem to apply to your example, this form of the emptiness check is useful if you need something other than a loop.
An artificial example might be
{% if (texts is defined) and texts %}
The first text is {{ texts[0] }}
{% else %}
Error!
{% endif %}
Take a look at the documentation of Jinja2 defined(): http://jinja.pocoo.org/docs/templates/#defined
{% if variable is defined %}
value of variable: {{ variable }}
{% else %}
variable is not defined
{% endif %}
Is it clear enough? In your case it could look like this:
{% if texts is defined %}
{% for text in texts %}
<div>{{ error }}</div>
<div class="post">
<div class="post-title">{{ text.subject }}</div>
<pre class="post-content">{{ text.content }}</pre>
</div>
{% endfor %}
{% else %}
Error!
{% endif %}
As mentioned in the documentation, you could also write:
{% for text in texts %}
<div class="post">
<div class="post-title">{{text.subject}}</div>
<pre class="post-content">{{text.content}}</pre>
</div>
{% else %}
<div>{{ error }}</div>
{% endfor %}
It handles both the case where texts is undefined, and the case where texts is empty.
This is a neat and simple solution that worked well for me!
{% if texts is defined and texts[0] is defined %}
...
{% endif %}
It's possible that texts could be defined but contain a single list element which is an empty string; For example:
texts = ['']
In this case, testing if texts is defined will produce a true result so you should test the first element instead:
{% if texts[0] != '' %}
..code here..
{% endif %}
You might also want to combine that with the |length filter to make sure it only has one element.
This worked for me when working with the UPS API where if there is only one object in a parent object the child is just an object, but when there is more than one child it's a array of objects.
{% if texts[0] %}
..code here..
{% endif %}
This is what worked for my use case in my Django app:
I needed to pass a queryset as context to an html template and display the block only if the queryset had values
Queryset:
events = Event.objects.filter(schedule_end__gte=date.today()).order_by('-created_at')
Passed context dictionary as follows:
{ "events" : events }
HTML template
{% if events %}
<h3>Upcoming Events</h3>
<ul>
{% for event in events %}
<li><h4>{{ event.title }}</h4></li>
{% endfor %}
</ul>
{% endif %}
This works for me ( But I make sure to return an empty array [] and not None if its empty )
{% if array %}
<table class="table">
...
</table>
{% endif %}
We can check if array is not empty by writing below jinja code.
where the content2 is an array defined under py file. #app.route("/<name>") def home(name): return render_template("index.html", content=name, content2=[])
{% if content2 %}
<div>
<h2>Array elements are available</h2>
{% for con2 in content2 %}
<p> {{con2}} </p>
{% endfor %}
</div>
{% endif %}
Thanks