Templates built-in variables context and container

The docs state that

The container (usually a Folder) in which the template is kept. Use this to get Zope objects from locations relative to the template’s permanent home. The container and context variables refer to the same object when a template is called from its normal location. However, when a template is applied to another object (for example, a ZSQL Method) the container and context will not refer to the same object.

However, this is not the case if the template is a macro that is used in another template in a different folder. In this case, the folder of the "using" template will be taken over by the macro template references to "container".

I can workaround this using root or an absolute path instead of "container", but it complicates matters.

Is there a better alternative?

zfm via Plone Community wrote at 2023-10-17 20:45 +0000:

The docs state that

The container (usually a Folder) in which the template is kept. Use this to get Zope objects from locations relative to the template’s permanent home. The container and context variables refer to the same object when a template is called from its normal location. However, when a template is applied to another object (for example, a ZSQL Method) the container and context will not refer to the same object.

However, this is not the case if the template is a macro that is used in another template in a different folder. In this case, the folder of the "using" template will be taken over by the macro template references to "container".

I can workaround this using root or an absolute path instead of "container", but it complicates matters.

Is there a better alternative?

No: a template's execution context (including the predefined variables)
is set up when the template is called.
Using a macro defined in a template does not imply a template call.
Instead, the template is used for the macro extraction in source code mode,
i.e. without an execution context.

Conceptually, macro expansion happens before an execution starts.
The result is a consolidated template without macro uses.
This template is then rendered/evaluated.
The actual implementation typically does not separate macro
expansion from the rendering/evaluation but keeps the conceptional
semantics. Therefore, macros must use the execution context of
the using template, and not one set up for the hosting template.

When you think about this, you will find this a good thing.
In a macro, you often will want to access variables defined
by the user. Example: a batching macro; the caller has set up
the sequence to be batched, the macro is responsible to present
the batch with the associated actions "next", "previous", ...
Because macros do not have explicit parameters, their only way
to access context set up by the user is to access the same
execution context.

Thanks for the information Dieter.

I understand the logic and benefits but it also leads to ugly code having to resort to hard-coded paths. A macro implementing a component should be able to access its objects on its folder with a built-in variable like macroContainer. There is nothing providing this info then?

zfm via Plone Community wrote at 2023-10-18 05:51 +0000:

...
I understand the logic and benefits but it also leads to ugly code having to resort to hard-coded paths. A macro implementing a component should be able to access its objects on its folder with a built-in variable like macroContainer. There is nothing providing this info then?

I do not think so.

But you could make the information available, e.g. by wrapping
any
<XXX metal:use-macro="template-path/macros/MMM" ...> ...
into an
<tal:define define="macro_template *template-path">
...

or by an appropriate definition near the beginning of the macro definition.
Then during the evaluation of your macro, you could look
at macro_template for its defining template
(you can use obj.aq_inner.aq_parent to access the container holding obj
for any placeful obj).

Note that macro expansion is nested in general.
For the general case, you would need not a simple variable
(like macro_template above) but a stack maintained at macro
expansion boundaries.
The complexity explains why this is (almost surely) not provided
by default.

Depending on the template engine, a macro might know which
template has defined the macro (but I suppose this will not be the case).

Templates have an attribute macros containing the macros
defined by the template.
You could use Python's inspection facilities to learn what information
is provided for a macro and whether you get from a macro
to its defining template.

Thanks, this gives me better options to think about.

Can you clarify what is the *? I don't see it mentioned in the specs of TALES.

Another option might be to not use macros, just a normal template and call it from a python expression, tal:replace="structure python: container/part/subtemplate(params)"

Any thoughts on this one?

zfm via Plone Community wrote at 2023-10-18 08:16 +0000:

Can you clarify what is the *? I don't see it mentioned in the specs of TALES.

"template-path" is not a concept from the spec but used to
denote a path expression evaluting to a template.

An example "use-macro" is
use-macro="context/main_template/macros/master.

As you can see, the macro reference consistes of
a template reference (context/main_template in the example)
followed by macros and the macro name.

The template reference typically is expressed by a path expression.
I called this path expression "template-path".

The template-path corresponding to the example above
is context/main_template.

Ahh, got it, you were using the * not as a real TALES operator, more like "whatever" template-path is.

Also, I just tried calling a template like

tal:replace="structure python: container/part/subtemplate(param='test')"

and seems to work fine.