cms icon indicating copy to clipboard operation
cms copied to clipboard

Inconsistent behaviour for variables set in a partial

Open hybridvision opened this issue 2 years ago • 8 comments

Bug description

Hello!

I've been testing out Statamic for a small project and I think I've discovered a bug with variables set inside a partial. Originally I was using the excellent Peak starter kit when I saw this bug but I've since reproduced it on a blank installation.

I watched Jack's informative video, which explains how the new Antlers runtime allows more flexibility with variables thanks to the AST parser. This seems to confirm that what I'm attempting should work by design.

When including a specific partial, I want it to influence the main layout's behaviour (I set a variable in the partial and the layout checks for this variable). This works, but not consistently.

If my included partial just sets the variable and doesn't do anything else, the variable isn't available elsewhere. However, I discovered if I include any of the following snippets in my partial, suddenly my variable is available in the main layout:

  • {{ dump }}
  • {{ partial:whatever }}
  • or even... {{ partial:if_exists src="neverexists" }}

It seems that the inclusion of the dump or partial tags change the way the variable is handled. 🤔

How to reproduce

Minimal setup to reproduce this problem:

  1. Install a blank site from the CLI.
  2. Create a page with the default template (template: default in the front-matter).
  3. Create new partial in resources/views/_test.antlers.html with the following content:
<!-- _test.antlers.html -->
{{ $var_from_partial = "#### VAR SET in _test PARTIAL ####" }}
  1. Update resources/views/default.antlers.html with the following:
<!-- default.antlers.html -->
{{ partial:test }}
<b>default.antlers.html: {{ $var_from_partial ?? '*** NO PARTIAL VAR ***' }}</b><br>

{{ content }}
  1. Add the following line anywhere in resources/views/layout.antlers.html:
<!-- layout.antlers.html -->
<b>layout.antlers.html: {{ $var_from_partial ?? '*** NO PARTIAL VAR ***' }}</b><br>
  1. Visit the page created in Step 2. There will be two instances of the *** NO PARTIAL VAR *** text because the variable doesn't exist in either scope.

  2. Now edit resources/views/_test.antlers.html and add a {{ dump }} tag or a partial (eg. {{ partial:whatever }} or {{ partial:if_exists src="neverexists" }})

  3. Refresh the page and observe that now the main layout is receiving the value of $var_from_partial. Curiously, the default.antlers.html still doesn't see the variable. I can live with this for now since I only really need it in the layout / header partial but from everything I've understood, it should be available in the default template too, right?

Logs

N/A

Environment

Environment
Application Name: Statamic
Laravel Version: 10.11.0
PHP Version: 8.2.5
Composer Version: 2.5.5
Environment: local
Debug Mode: ENABLED
URL: statamic-vanilla.test
Maintenance Mode: OFF

Cache
Config: NOT CACHED
Events: NOT CACHED
Routes: NOT CACHED
Views: CACHED

Drivers
Broadcasting: log
Cache: statamic
Database: mysql
Logs: stack / single
Mail: smtp
Queue: sync
Session: file

Statamic
Addons: 0
Antlers: runtime
Stache Watcher: Enabled
Static Caching: Disabled
Version: 4.1.3 Solo

Installation

Fresh statamic/statamic site via CLI

Antlers Parser

runtime (new)

Additional details

To give some extra context on what I'm trying to achieve: I want to be able to change the body CSS class in the main layout (or maybe a CSS class in the header partial) when a particular partial is included.

More specifically, I have a hero block that is included via Peak's page builder and when it is present and in the first position, the header should switch to being absolutely positioned, allowing it to overlap the hero block. I don't know if my explanation makes sense but this kind of hero block that sits under a transparent header/nav is quite common. On other pages without the hero block, the header should maintain its place in the flow with the correct spacing and background colour.

Lastly, I did a bunch of searches before posting this and couldn't find anything quite like it. The closest I found was https://github.com/statamic/cms/issues/7587 but it is coming from a different angle and is currently unresolved.

Thanks for reading! 👋

hybridvision avatar May 19 '23 20:05 hybridvision

Does anyone have any ideas about this problem?

The issue still exists in the latest CMS update (4.11.0) and unfortunately my workaround of adding a dummy partial tag is not working in all cases.

Specifically, if I include a form ( {{ form:create ... }}), the variable I previously set in the partial is no longer present in the main layout (see above for more details).

Maybe I'm approaching this the wrong way but this definitely seems like a bug. I'd appreciate any input!

hybridvision avatar Jul 15 '23 12:07 hybridvision

Dont know if this help but :

  • there is an addon for what your trying to do (by the creator of the new antlers parser @JohnathonKoster ) : https://statamic.com/addons/stillat/antlers-layouts
  • here a blog post about this exact addon and the "why-s" : https://stillat.com/blog/2023/04/10/creating-a-custom-statamic-layout-tag

In addition, from what I know, a variable defined in a partial "leaks" in the outer scope (ie parent scope) ""only"" if a variable with the same name exist in it (as you read in #7587) ... But there are also other edge case where it happens (like in #7633 which is still opened)

dadaxr avatar Jul 15 '23 13:07 dadaxr

Salut @dadaxr,

Thanks very much for your reply! I still need to test it out but the add-on you suggested sounds like it will do what I need. Reading some of your tickets, it's clear that there are still some grey zones in how this should work and it's not always easy to understand what is happening. 🤯

If you see this video around the 1:23 mark, Jack describes Antlers' unique ability to have variables set out of order and still be available everywhere. It's a cool feature but it also creates some interesting headaches...

I'm going to test out the add-on and see if the variable sharing idea will solve my problems for now.

PS: Je pense qu'on est voisins... 👋 😃

hybridvision avatar Jul 15 '23 16:07 hybridvision

un ptit frenchy, cool :) (voisin de nantes?)

Regarding the antlers video :

Antlers' unique ability to have variables set out of order and still be available everywhere

I didnt read anything about that ability in the doc, and I have not seen it in action either, (for instance, if you define a variable a the bottom of a template, it wont be available before the line where you define it (in contrast with javascript parser which "compute" keywords tokens first (var / const / let / function / ...))

Regarding your problem, it's indeed sometimes hard to understand how it works internally due to some inconsistent behavior... but the main idea is : 1 - the main template can defined variable 2 - it also defines a layout, which can access variables defined inside the main template (but not otherwise : ie you cant access variables defined inside layout from the main template) 3 - the main template can defined partials, and pass its context (all existing variables) to them 4 - partials can create their own variables but they wont "leak" in "parent context" (but eventually in their own nested partials) => except in rare edge-case where a "bug" is probably happening

So knowing that, you have few options left :

  • using the layout addon (which does more than what you want)
  • creating a custom tag and use your own logic to define a variable somewhere and retrieve it elsewhere (for instance in the layout), you can for example use the php session array to pass them (even if it's a bit hacky), or inject them in the laravel service container, or use laravel config helper, or whatever method which suit your need
  • define a variable in your main template, and then update it in your partials (you can use a multipurpose "array" variable if you dont want to pollute the context too much)

dadaxr avatar Jul 17 '23 07:07 dadaxr

Bonjour @dadaxr (oui, voisin de Nantes... sur l'île 🐘 – je répondrai en anglais pour Github, et en plus c'est ma langue maternelle 🙃)

Thank you for taking the time to give such a detailed reply, it's much appreciated! I'm still learning all the quirks of Statamic, so this helps a lot.

Upon reflection, I think you're right about how scoping works and we shouldn't expect any JS-style hoisting behaviour.

For my use-case, I assessed the different options you suggested and I think the first one is the cleanest and most maintainable. It's unfortunate that another add-on is required for what seems like a core feature (maybe antlers-layouts' functionality will be part of core one day?) but considering it's authored by the same person behind the Antlers parser, I'm ok with adding it.

I also liked your suggestion of creating a multi-purpose array to avoid polluting the context too much – it's something I'll keep in mind if I need many different variables.

I'm just happy to have a solution now, merci encore !

hybridvision avatar Jul 19 '23 15:07 hybridvision

By the way, although I have enough of a solution, I'm going to leave this ticket open...

@jackmcdade and @JohnathonKoster, I'd be interested in your input when you have time :)

hybridvision avatar Jul 19 '23 15:07 hybridvision

This is intentional. Any variables defined in a partial shouldn't be available in the scope of the template/layout.

If you need to share something with a template or layout, you could potentially look at using the {{ yield }} tag or using the Antlers Layouts addon which has already been suggested.

duncanmcclean avatar Jan 08 '24 14:01 duncanmcclean

Hello @duncanmcclean,

Thanks for your reply – I appreciate that this may be by design but I still think the behaviour I observed is inconsistent and should be considered a bug.

I would also like to understand what Jack really meant in this video around the 1:23 mark when he talks about out of order code and being able to create variables and them being "available all throughout your template, into partials or up into layouts...". Am I misinterpreting this statement?

I know Antlers is close to the heart of the Statamic team but having completed a project using it and needing to deal with these kinds of ambiguous issues, I would hesitate to use it for any future projects, which is a shame because there are plenty of neat things about it.

hybridvision avatar Jan 09 '24 02:01 hybridvision