12

I'm essentially looking for a way to have a template that runs through a bunch of sets that I might use throughout my templates.

The problem is, if I include in a template with an extends, it dies up with:

A template that extends another one cannot have a body.

{% include '/_resources/variables.twig' %}
{% extends '/_layouts/default.twig' %}

Some research indicates that maybe this isn't easily done with Twig parsing alone: https://stackoverflow.com/questions/20410194/use-twig-custom-set-variables-from-an-include https://groups.google.com/forum/#!topic/twig-users/QKdpWAxLdjE

So, what would be the best way to achieve this? My initial thought would be a plugin that you gave the path of a template, which preloaded things into variables.

I realize some variables (strings) could be done via macros. But, for example, I have a set usStates = {...} that is a big hash of state codes and names.

0

5 Answers 5

18

There are actually two issues with this approach. The first, and the direct reason you’re getting that Twig error, is that templates that extend another template can only output things within {% block %} tags. Any HTML that gets output outside of those tags (either directly or via {{ output tags }}) will result in that Twig error.

The {% include %} tag is in the same boat. Its only purpose (as far as the parent template is concerned) is to output the results of a child template. So like {{ output tags }}, you can’t place it outside of {% block %} tags. If you really want to include a template within these nested templates, you will need to move it to within your {% block %}.

Even if you did that, though, you’d then run into the second issue: Any variables that are set/modified within an included template will not bubble back up to the parent template. For example, if you had these two templates:

  • parent template:

    {% include "_inc" %}
    {{ foo }}
    
  • child template (_inc.html):

    {% set foo = 'bar' %}
    

That foo variable would actually be discarded as soon as Twig was finished rendering _inc.html; nothing that happens in there will have any effect on the parent template. (As I said, the only purpose of the {% include %} tag as far as the parent template is concerned is to output the rendering result of the included template.)

However, it’s still probably possible to achieve what you want when you consider the order in which these templates are going to be processed:

  1. Everything besides the {% block %} tags in the requested template (the one with the {% extends %} tag)
  2. The layout template (_layouts/default.twig), including its {% block %} tags
  3. The requested template’s {% block %} tags

Knowing that, you can probably just set your variables you’re trying to set from within _layouts/default.twig, and they will be available within the requested template’s {% block %} tags.

  • requested template:

    {% extends '_layouts/default.twig' %}
    
    {% block content  %}
        {{ foo }}
    {% endblock %}
    
  • layout template (_layouts/default.twig):

    {# Set the variables you were trying to set in _resources/variables.twig #}
    {% set foo = 'bar' %}
    
    {% block content %}
    {% endblock %}
    
4
  • You could wrap the whole page in a single block (for example named "HTML") in _layouts/default.twig and extend the variables template using {% extends '_resources/variables.twig' %}. This allows you to separate variables and layout HTML.
    – carlcs
    Commented Dec 18, 2014 at 9:22
  • Brandon, that solution doesn't work for me when trying to set variables from an entry. I have a need to set meta data from the current entry on ALL pages, so I've tried this layout: gist.github.com/russback/230310fe4c8f849958bb. However the data doesn't get rendered. Am I missing something? In my partials/_global_header.html template I am attempting the output those variables. @carics, I don't follow your suggestion - do you have an example?
    – Russ Back
    Commented Dec 24, 2014 at 13:21
  • @RussBack That’s not quite the same question. Can you please post a new one? Commented Dec 24, 2014 at 15:25
  • Brandon, thanks. Done at craftcms.stackexchange.com/questions/5400/….
    – Russ Back
    Commented Dec 24, 2014 at 21:09
5

Using macro command you can replace the set command, avoiding the disadvantage of long code every time you need to call a variable. I had the same need, and my solution is:

Parent template "main.twig.html":

{% import "constants.twig" as constants %}
var priceA = {{ constants.priceA }}

Child template "constants.twig":

{% macro priceA () %}   15.10   {% endmacro %}
{% macro priceB () %}   26.20   {% endmacro %}
{% macro priceC () %}   30.40   {% endmacro %}

This is nice and simple to read AND short to write whenever you need to call a variable.

[EDIT 2022-05-18]

Here it is a full working example for file "main.twig.html":

{% import "constants.twig" as constants %}
<html><body>
<div id="tot"></div>
    <script>
        let quantity_A = 3, quantity_B = 5;
        let total_amount = {{ constants.priceA }} * quantity_A  + {{ constants.priceB }} * quantity_B ;
        document.getElementById("tot").innerHTML = total_amount; // Result will be 176.3
    </script>
</body></html>
7
  • This is my preferred solution because then you can access the variable from inside another macro.
    – kmgdev
    Commented Aug 24, 2019 at 0:07
  • Problem is a number or boolean is a string, so "false" would equate to true :(
    – iamkeir
    Commented May 13, 2022 at 10:15
  • @iamkeir I am not clear what you are referring to. Your comment does not seem to apply to my code.
    – Kar.ma
    Commented May 16, 2022 at 8:20
  • @Kar.ma sorry, to be more clear, macros can only return values as strings, so 15.10, for example, will return as a string not a number, so you can't do math logic with it. You'd need to do {{ constants.priceA | number }}. Annoying limitation of macros. Just thought important to point out for people. Equally if return "false", it'll be a string of "false", not a boolean of false, and the string of "false" will actually return as boolean true.
    – iamkeir
    Commented May 17, 2022 at 9:35
  • @Kar.ma I do actually use the macro approach for constants/functions myself, but have to workaround the string limitation.
    – iamkeir
    Commented May 17, 2022 at 9:36
4

Another option would be to create a small plugin with a variables file dedicated to storing this data.

class MyPluginVariable {

    public function exampleVariable($optional = null)
    {
        return "Return anything you want.";
    }

}

Then in your template, you can just use:

{{ craft.myPlugin.exampleVariable }}
2

It looks like another good solution is to use a template hook. I just became aware of these, but they look very useful for this type of thing.

1

Use macros in your variables.html:

{% macro myMacro(param1, param2) %}
  {{param1}} / {{param2}}
{% endmacro %}`

Put this in your layout.html:

{% import 'folder/to/variables' as helpers %}

and then in your partials:

{{helpers.myMacro(param1, param2)}}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.