Keats / tera

A template engine for Rust based on Jinja2/Django
http://keats.github.io/tera/
MIT License
3.42k stars 278 forks source link

Allow block to be used multiple times #265

Open faulesocke opened 6 years ago

faulesocke commented 6 years ago

It should be possible to use a block multiple times in the template. This is possible in jinja2, because a block can be accessed through the special self variable as described for example here:

https://stackoverflow.com/questions/20929241/how-to-repeat-a-block-in-a-jinja2-template

Keats commented 6 years ago

I have no strong feeling about that. I don't really see the usecase though so I'd like to see more people wanting that

faulesocke commented 6 years ago

Probably the most common use case is for the page title as seen in the stackoverflow thread. Of course one can easily work around this, but I thought it would be nice, if I (and others) don't have to.

paulcmal commented 6 years ago

Probably the most common use case is for the page title as seen in the stackoverflow thread.

Yes, that's a very common use-case. I also had to scratch my head around it when making a Hugo theme. I just don't understand the logic of not being able to reuse a block elsewhere in the template ^^" (is there an actual reason?)

Keats commented 6 years ago

I just don't understand the logic of not being able to reuse a block elsewhere in the template ^^" (is there an actual reason?)

In the first template of the stackoverflow, thetitle block is defined twice. When using the title block in a child template, which one should the template engine inherits from? The template engine has no way of knowing which one so that's the reason you go through self in Jinja2

paulcmal commented 6 years ago

that's the reason you go through self in Jinja2

Thanks for the explanation! Am i wrong to think this could also be addressed differently by using a separate function for overriding the block? (such as define in Hugo)

Keats commented 6 years ago

Define in Hugo is just block in Tera. What you need is a way to render the content of a block, not redefine it. I'm not sure if the golang template engine has a way to do that

On Mon, 6 Aug 2018, 20:06 cmal, notifications@github.com wrote:

that's the reason you go through self in Jinja2

Thanks for the explanation! Am i wrong to think this could also be addressed differently by using a separate function for overriding the block? (such as define in Hugo https://gohugo.io/templates/base/#override-the-base-template)

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/Keats/tera/issues/265#issuecomment-410798820, or mute the thread https://github.com/notifications/unsubscribe-auth/AApho5BDIp3dYBPNtrAon9lRlokjgj7fks5uOIWVgaJpZM4SYYlx .

paulcmal commented 5 years ago

What you need is a way to render the content of a block, not redefine it.

Yes, I meant that we need different keywords for defining and using a block. Sorry if i wasn't clear. I understand that it would make sense in order to avoid a breaking change to have define a {% useblock BLOCK %} feature. Would that be hard to implement?

I think the title example from stackoverflow is a clear example of why people would want this. Are variables the appropriate way to deal with such issues? (I'm not sure about the flow of data of a template to/from other templates it extends)

faulesocke commented 5 years ago

I would definitely propose the useblock syntax since this ambiguity is one of the most disgusting things in jinja2.

Keats commented 5 years ago

Woops didn't see @paulcmal message

I think useblock makes sense and shouldn't be too hard to implement

BertalanD commented 4 years ago

What's wrong with the {{ self.block_name() }} syntax?

rpearce commented 3 years ago

Here's an example where title, description, site and url are used >1 time on a regular page that doesn't want to look like garbage on social media:

<!DOCTYPE html>
<html lang="en">
<head>
  <title>{% block title %}{% endblock title %}</title>

  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="description" content="{% block description %}{% endblock description %}" />
  <meta name="author" content="{% block author %}{% endblock author %}" />
  <meta name="keywords" content="{% block keywords %}{% endblock keywords %}" />
  <meta name="apple-mobile-web-app-capable" content="yes" />

  <meta property="og:site_name" content="{% block site %}{% endblock site %}" />
  <meta property="og:title" content="{% block title %}{% endblock title %}" />
  <meta property="og:url" content="{% block url %}{% endblock url %}" />
  <meta property="og:description" content="{% block description %}{% endblock description %}" />

  <meta property="og:image" content="{% block og_image_src %}{% endblock og_image_src %}" />
  <meta property="og:image:alt" content="{% block og_image_alt %}{% endblock og_image_alt %}" />
  <meta property="og:type" content="{% block type %}{% endblock type %}" />

  <meta property="twitter:card" content="summary_large_image" />
  <meta property="twitter:site" content="{% block site %}{% endblock site %}" />
  <meta property="twitter:title" content="{% block title %}{% endblock title %}" />
  <meta property="twitter:description" content="{% block description %}{% endblock description %}" />
  <meta property="twitter:image" content="{% block twitter_image_src %}{% endblock twitter_image_src %}" />
  <meta property="twitter:creator" content="{% block twitter_author %}{% endblock twitter_author %}" />

  <link rel="canonical" href="{% block url %}{% endblock url %}" />

  {% block extra_head %}
  {% endblock extra_head %}
</head>
<body>
  {% block content %}{% endblock content %}
</body>
</html>

Naturally, this breaks on the "can't have multiple whatevers".

Do y'all have a suggested workaround for this?

Keats commented 3 years ago

You can set the variables once depending on the variables present and render them. Eg

{% if page %}
{% set title = page.title %}
{% else %}
...
{% endif %}
...
<meta property="og:title" content="{{title}}" />
rpearce commented 3 years ago

Thanks for the quick reply, @Keats! I'll give this a roll later today

rpearce commented 3 years ago

@Keats Is what you suggested possible in a template that is being extended from another template? For example, what is above is my base.html, and here is post.html:

{% extends "base.html" %}
{% block author %}{{author}}{% endblock author %}
{% block description %}{{description}}{% endblock description %}
{% block keywords %}{{keywords}}{% endblock description %}
{% block site %}{{site}}{% endblock site %}
{% block title %}{{title}}{% endblock title %}
{% block twitter_author %}{{twitter_author}}{% endblock description %}
{% block type %}article{% endblock type %}
{% block url %}{{url}}{% endblock url %}
{% block content %}
<!-- some stuff here -->
{{content_html | safe}}
{% endblock content %}

and I'm trying to get base.html to use these values of title, description and url in a few places in base.html

Keats commented 3 years ago

Yep, it should work

rpearce commented 3 years ago

I can't seem to get that concept to work when I'm rendering post.html with data, providing it to a block, and trying to use it a few times in base.html.

I'm not rendering base.html, but I am extending it and trying to send it variables.

I do think it is this same issue of "Allow block to be used multiple times". How else can you render template2, which extends template1, and have template1 use an inserted variable from template2 more than 1 time?

Thank you for your time, and I can stop bothering you if this is annoying!

Keats commented 3 years ago

The way the template engine works, it will start rendering the base template (base.html) in all cases going through blocks. So if you define anything in the base.html, whether you're rendering template2 or template1 doesn't matter since the context will be what you care about. If you have an example repo I can have a look

LunNova commented 2 years ago

It looks like rpearce was trying to set something in post.html which extends from base.html which would then need to be used in base.html.

If you're using Zola you run into this situation.

If I do something like this:

index.html:
<title>{% block title %}{{ config.title }}{% endblock title %}</title>

page.html (which extends from index.html):
{% block title %} {{ page.title }} | {{ config.title }} {% endblock title %}

It works for setting the title block contents from page.html.

If instead I use a variable:

index.html:

{% set title_example = config.title %}
<title>{{ title_example }}</title>

page.html:
{% set title_example = page.title ~ ' | ' ~ config.title %}

There are no errors, but the value of title_example used in page.html is not used.

In this example page.title is not available in index.html.

An alternative approach is:

page.html:

        {% set title_example = config.title %}
        {% if page and page.title %}
            {% set title_example = page.title ~ ' | ' ~ config.title %}
        {% endif %}

        <title>{{ title_example }}</title>
        <!-- other stuff here -->
        <meta name=”twitter:title” content=”{{ title_example }}” />

This approach requires hardcoding specific cases where the title is used into index.html instead of having them be the responsibility of the child templates so it isn't great either.

Blog repo, Theme and link to page.html

Keats commented 2 years ago

What's wrong with the {{ self.block_name() }} syntax?

I find it hard to understand what self is there. Can I also render imported macros that way? Or render variable set in that template.

theowenyoung commented 1 year ago

@LunNova did you figure out this?

LunNova commented 1 year ago

Nothing better than the example at the bottom there :/

theowenyoung commented 1 year ago

Thanks @LunNova , I finally handle all these cases on base.html

 {%- block social_meta -%} 
  {%- if page.title -%}
    {%- set_global title = page.title -%}
  {%- elif term.name -%}
    {%- set titled_type_name = taxonomy.name | title -%}
    {%- set_global title = titled_type_name ~ " | " ~ term.name  -%}
  {%- elif taxonomy.name  -%}
    {%- set_global title = taxonomy.name | title -%}
  {%- elif section.title -%}
    {%- set_global title = section.title -%}
  {%- endif -%}
  {%- if title -%}
    {%- set_global full_title = title ~ " - " ~ config.title -%}
  {%- elif is_index == true -%}
    {%- set_global title = config.title -%}
    {%- set_global full_title = config.title -%}
  {%- endif -%}
  {%- if page.description -%}
    {%- set_global description =  page.description -%}
  {%- elif page.summary -%}
    {%- set_global description =  page.summary | spaceless | striptags -%}
  {%- elif page.content -%}
    {%- set_global description = page.content | spaceless | striptags | truncate(length=200) -%}
  {%- elif is_index -%}
    {%- set_global description = config.description -%}
  {%- elif section.content -%}
    {%- set_global description = section.content | spaceless | striptags | truncate(length=200) -%}
  {%- else -%}
    {%- set_global description = config.description -%}
  {%- endif -%}
  {%- if page.extra.image -%}
    {%- set_global image = page.extra.image -%}
  {%- elif is_index and config.extra.image -%}
    {%- set_global image = config.extra.image -%}
  {%- elif page.assets -%}
    {%- for asset in page.assets | sort -%}
      {%- if asset is matching("[.](jpg|png|jpeg)$") -%}
          {%- set_global image=get_url(path=asset) -%} 
          {%- break -%}
      {%- endif -%}
    {%- endfor -%}
  {%- endif -%}
<title>{{full_title}}</title>
  <meta property="og:title" content="{{title}}"/>
  <meta itemprop="headline" content="{{title}}"/>
  <meta property="og:description" content="{{description}}"/>
  <meta name="description" content="{{description}}"/>
<meta property="og:url" content="{{current_url | safe}}"/>
  {%- if image -%}
  <meta name="og:type" content="summary_large_image"/>
  <meta property="twitter:card" content="summary_large_image"/>
  <meta property="og:image" content="{{ image }}"/>
  <meta property="og:image:alt" content="{{ title }}"/>
  {%- else -%}
  <meta name="og:type" content="summary"/>
  {%- endif -%}
{%- endblock social_meta -%}
Keavon commented 3 months ago

A Working Solution for Reusing Values

Instead of trying to do this, which isn't allowed:

<!-- base.html -->

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- We can't use the same `title` block twice -->
  <title>{% block title %}{% endblock title %}</title>
  <meta property="og:title" content="{% block title %}{% endblock title %}" />
</head>
<body>
  {% block content %}{% endblock content %}
</body>
</html>
<!-- page.html -->

{% extends "base.html" %}

{% block title %}{{ page.title }}{% endblock title %}

{% block content %}{{ content_html | safe }}{% endblock content %}

We can instead do this:

<!-- base.html -->

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- This imports the variables that are set in the block in page.html
       and they're accessible anywhere below this block/endblock line -->
  {% block head_variables %}{% endblock head_variables %}

  <title>{{ title }}</title>
  <meta property="og:title" content="{{ title }}" />
</head>
<body>
  {% block content %}{% endblock content %}
</body>
</html>
<!-- page.html -->

{% extends "base.html" %}

{% block head_variables %}
{% set title = page.title %}
<!-- You can set more variables here as you wish -->
{% endblock head_variables %}

{% block content %}{{ content_html | safe }}{% endblock content %}