picocms / Pico

Pico is a stupidly simple, blazing fast, flat file CMS.
http://picocms.org/
MIT License
3.81k stars 616 forks source link

Access variables/arguments with include or macro #642

Closed ctuxboy closed 1 year ago

ctuxboy commented 2 years ago

Hello Pico users,

For better organisation trying build my theme in several twig-templates. I have a template snippets.twig with variables inside this template. When try 'include' snippets.twig in other twig-pages (ex. index.twig), i can't access the variables:

What i try with the 'include'-function:

snippets.twig:

{% set counter_dogzones = '' %}
   {% for page in pages("",offset=4) %} {# offset=4 => /provincie/province/city/dogzone #}
   {% set counter_dogzones = counter_dogzones + 1 %}
{% endfor %}

Try include in index.twig: {% include "inc/snippets.twig" %}

{% include "inc/snippets.twig" with variable %}

{% include "inc/snippets.twig" with { variable } %}

Trying with macro's:

The Macro itsef in the snippets.twig template:

{% macro count_dogzones() %}
  {% set counter_dogzones = '' %}
    {% for page in pages("",offset=4) %}
    {% set counter_dogzones = counter_dogzones + 1 %}
  {% endfor %}
{% endmacro %}

Import in the index.twig: {% import "inc/snippets.twig" as snippets %}

Try print the macro in the twig-template: {{ snippets.count_dogzones }}

All the above code-snippets doens't work :(

PS: I know this is not a specific Pico-problem, but hope some Pico-users can help me out with a simple solution.

mayamcdougall commented 2 years ago

Sorry for the delay. 😅

As far as I know, there isn't a good solution for this.

What you're encountering here is variable scoping. If you're unfamiliar, typically in programming when you define a variable, it only exists in the current namespace (whatever you're doing right now). This can be a function, a loop, or sometimes the top level of file itself. Once that function, loop, etc ends (or, if you're coding in another file, for the file example), the variable no longer exists. It goes "out of scope", and gets cleaned up.

So, that happens with Twig too. Your variable exist only within the context of the macro or imported snippet they were defined in.

If you use the variables within their macros and snippets, they exist... and once you go back out to the file that imported them, they're gone again.

There's effectively no way to import variables like this, even though it would be handy. 😔

(Also, the "include with variable" code you're seeing works in the opposite direction, it supplies a variable to the imported snippet).

The ugly way I've gotten around this in the past is to just define everything I need "globally" at the start of the base template, that way everything further down the chain has access to it. You can see this in Freelancer, where at the top of the file I set up a couple of things.

I make an index alias to pages["index"].meta, to simplify the many many times I use it. I create a single_page_mode variable to effectively do the opposite of the old is_front_page variable (😉). Then finally I make an array of portfolio_items from the pages (or I supply my own "demo content" if there aren't any pages yet, or I detect we're using Pico's content-sample files. (And yes, it really just checks for the string "Pico is stupidly simple" for that one. 😂)).

{% set index = pages["index"].meta %}

{% if current_page.id != "index" %}
    {% set single_page_mode = true %}
{% endif %}

{% if not index.portfolio.disabled %}
    {% set portfolio_items = [] %}
    {% for page in pages() %}
        {% if not page.hidden %}
            {% set portfolio_items = portfolio_items|merge([page]) %}
        {% endif %}
    {% endfor %}

    {% if not portfolio_items or "Pico is a stupidly simple" in index.description %}
        {% set portfolio_items = config.theme_config.demo.portfolio_items %}
    {% endif %}
{% endif %}

By adding these items to the top of the template, I've got access to it everywhere. However, the important thing to remember is that I only use one base Pico Template here (index.twig). If I had multiple Template choices, I'd have to put whatever variables they'd need at the top of them too.

Hopefully this helps. I know it's not the answer you were looking for, but at least you'll know why your attempts didn't work.

Don't worry, I've been there too. 😣

ctuxboy commented 2 years ago

@mayamcdougall ,

Sorry for the delay.

This is no problem, sometimes i have also no time to answer directly, the importing thing is... someone answer the issue, early or late, so i'm happy if there is an answer :smiley:

Okay, it is a little tricky accessing variables from a external twig-file.

Thanks a lot for explain, how you solved this in your Freelancer theme, so i learn how other solved this problem.

It's not a big disaster. The only thing i will do this, is do not repeat some code on different templates, but it was handy if this was possible :blush:

mayamcdougall commented 2 years ago

Okay, so I thought about this more just now, and I can come up with a better answer. 😉

First of all, you may need to change {% set counter_dogzones = '' %} to {% set counter_dogzones = 0 %}, because in my test Twig told me I couldn't add a String and an Integer together. 😂

So, at the end of your macro, print the result ({{ counter_dogzones }}) within the macro.

If you only need the value once per page, you're probably done, just call the macro in place of the variable.

If you need it more than once per page, you should be able to use set to assign it to a new variable: {% set dogzone_count = snippets.count_dogzones() %}. This way you'll only need to run the loop once, and save the result.

Doesn't really scale well if you've got a bunch of them, and it's not as elegant as being able to import a variable directly, but "re-capturing" it like this might be a workaround depending on the situation.

You might also want to add a |trim filter to the macro call (eg {% set dogzone_count = snippets.count_dogzones()|trim %}), because in practice I found that I was also capturing the whitespace around the printed value. My untrimmed string was " 4\n", with a space and a new line being accidentally included.

I guess the other day I got caught up on what I "couldn't do" in Twig, and didn't stop long enough to get creative about it. 😅

Sometimes a little creativity can save the day. 😁

PhrozenByte commented 2 years ago

@mayamcdougall You might wanna check out https://twig.symfony.com/doc/3.x/templates.html#whitespace-control to spare the trim

mayamcdougall commented 2 years ago

Ah, nice, that would probably work better! 👍🏻

I've never really had to use them before. 👀

Pretty sure I've seen other code from @ctuxboy that did though. So yeah, probably ignore my advice about the |trim and just use the {%- -%} modifiers like I think you usually do. 😉

Thanks for the tip. ❤️

ctuxboy commented 1 year ago

@mayamcdougall , First of all very sorry for the delay :persevere: Working also on other hobby-projects (Arduino/ESP32), so sometimes working on the website and sometimes experimenting with Arduino's, depends on what I'm in the mood for :wink: Update my code with your solution and works perfect!!!!!!!!! :heart_eyes: So now i can use a seperate file snippets.twig and used in different pages (see screenshots) Screenshot 2022-07-08 10 23 26 Screenshot 2022-07-08 10 23 10

Thanks a lot for the awesome support.

To be continued... :stuck_out_tongue:

mayamcdougall commented 1 year ago

Thanks a lot for the awesome support.

No problem. Glad it's working out for you. 😉