Hey Daniel,

I looked at DirectiveCallPlace.getOrCreateCustomData, but its lifetime seems to 
be related to the template and not the environment,
Are you saying that I could use getOrCreateCustomData to give me a stable key 
and then do something with that key to store data in the environment?
(Updated render_once code below.)


For adding positional parameters to TemplateDirectiveModel, would it be one of 
the following:

a) a new default method that by will throw an exception unless overridden, 
similarly to how Iterator.remove() behaves?
    i.e. the user is forced to implement the existing named parameters method 
and can optionally also implement the positional parameters method
b) the existing method would stay the same, but if the caller uses positional 
parameters,
    then the map keys will be some kind of special object (e.g. instances of 
PositionalParameterKey, possibly from a cache) and the map would be a 
LinkedHashMap to preserve order.


Thanks you for answering my questions, this is really helpful.

Regards,
Simon



//    <#macro require_jQuery>
//        <@render_once>
//            <script 
src="https://code.jquery.com/jquery-3.7.1.min.js";></script><#t>
//        </@render_once>
//    </#macro>
//    <@require_jQuery />  <#-- Renders something -->
//    <@require_jQuery />  <#-- Nothing to render -->
//
// Inspiration: https://templ.guide/syntax-and-usage/render-once/
//
// If you #include a use of this directive multiple times then each will reset 
whether it has rendered.
// If we wanted to work around this, then this directive could be enhanced to 
accept an optional key,
// perhaps using object-equality, rather than reference-equality, e.g. a string 
scalar.
public class RenderOnceDirective implements TemplateDirectiveModel {

    private static final CustomAttribute IDENTITY_BASED_STATE = new 
CustomAttribute(SCOPE_ENVIRONMENT) {
        @Override
        protected Set<?> create() {
            return Collections.newSetFromMap(new IdentityHashMap<>());
        }
    };

    @Override
    public void execute(Environment env, Map params, TemplateModel[] loopVars, 
TemplateDirectiveBody body)
            throws TemplateException, IOException {
        if (!params.isEmpty()) {
            throw new TemplateModelException("This directive doesn't allow 
parameters.");
        }
        if (loopVars.length != 0) {
            throw new TemplateModelException("This directive doesn't support 
loop variables.");
        }
        if (body == null) {
            throw new TemplateModelException("missing body");
        }
        try {
            @SuppressWarnings("unchecked")
            Set<Object> alreadyRun = (Set<Object>) 
IDENTITY_BASED_STATE.get(env);
            Object key = env.getCurrentDirectiveCallPlace()
                    .getOrCreateCustomData(RenderOnceDirective.class, 
Object::new);
            boolean firstTime = alreadyRun.add(key);
            if (firstTime) {
                body.render(env.getOut());
            }
        }
        catch (CallPlaceCustomDataInitializationException e) {
            throw new TemplateException(e, env);
        }
    }

}




On Tuesday 3 September 2024 at 19:04:46 BST, Daniel Dekany 
<daniel.dek...@gmail.com> wrote: 





DirectiveCallPlace doesn't make any promises regarding equality (even if in
the current implementation it happens to work), but you can instead use
DirectiveCallPlace.getOrCreateCustomData, which was exactly made for things
like what you try to do.

TemplateDirectiveModel doesn't currently support positionals. But that
feature actually can be implemented in 2.x too. (The 3 branch is not too
relevant, as it's questionable at best if it will ever get enough time, and
even then the point of it is breaking backward compatibility, so it's not
an answer for most current users.)

Yes, directives defined with macros can be called either as
fully positional, or as fully by-name. There's an ambiguity issue with it
otherwise. Like if you have <@m x y=2 />, then currently means that 1st
parameter is the value of x, and 2nd parameter is the boolean result of y
== 2. Because, in FreeMarker, unfortunately, you can write = instead == of
=.

On Tue, Sep 3, 2024 at 5:54 PM Simon Hartley <scrhart...@yahoo.co.uk.invalid>
wrote:

> Heya,
>
>
> I was hoping you could verify a couple of things for me.
> Please let me know if you would prefer that I make either of these be
> Stack Overflow questions.
>
>
> 1. Is it valid to rely on identity / reference-equality with
> env.getCurrentDirectiveCallPlace() ?
>    Is the result stable so that I can store per-Environment data while
> taking advantage of this?
>    For reference, below I've included the source for my render_once
> directive to clarify.
>
>
> 2. I was wondering about the possibility of invoking
> TemplateDirectiveModel's with positional parameters.
>    I found https://issues.apache.org/jira/browse/FREEMARKER-63, so does
> that mean I must wait for V3?
>    Also am I correct that this implementation makes each parameter be
> strictly either positional or named?
>    This seems a shame, since I like the flexibility of being able to
> choose, and some teams will prefer brevity while others explicitness:
>    <@my_directive "my arg" /> or <@my_directive param="my arg" />
>
>
> Many thanks and best regards,
> Simon Hartley
>
>
>
> //    <#macro require_jQuery>
> //        <@render_once>
> //            <script src="https://code.jquery.com/jquery-3.7.1.min.js
> "></script><#t>
> //        </@render_once>
> //    </#macro>
> //    <@require_jQuery />  <#-- Renders something -->
> //    <@require_jQuery />  <#-- Nothing to render -->
> //
> // Inspiration: https://templ.guide/syntax-and-usage/render-once/
> //
> // If you #include a use of this directive multiple times then each will
> reset whether it has rendered.
> // If we wanted to work around this, then this directive could be enhanced
> to accept an optional key,
> // perhaps using object-equality, rather than reference-equality, e.g. a
> string scalar.
> public class RenderOnceDirective implements TemplateDirectiveModel {
>
>    private static final CustomAttribute IDENTITY_BASED_STATE = new
> CustomAttribute(SCOPE_ENVIRONMENT) {
>        @Override
>        protected Set<?> create() {
>            return Collections.newSetFromMap(new IdentityHashMap<>());
>        }
>    };
>
>    @Override
>    public void execute(Environment env, Map params, TemplateModel[]
> loopVars, TemplateDirectiveBody body)
>            throws TemplateException, IOException {
>        if (!params.isEmpty()) {
>            throw new TemplateModelException("This directive doesn't allow
> parameters.");
>        }
>        if (loopVars.length != 0) {
>            throw new TemplateModelException("This directive doesn't
> support loop variables.");
>        }
>        if (body == null) {
>            throw new TemplateModelException("missing body");
>        }
>
>        @SuppressWarnings("unchecked")
>        Set<Object> alreadyRun = (Set<Object>)
> IDENTITY_BASED_STATE.get(env);
>        Object key = env.getCurrentDirectiveCallPlace();
>        boolean firstTime = alreadyRun.add(key);
>        if (firstTime) {
>            body.render(env.getOut());

>        }
>    }
>
> }
>


-- 
Best regards,
Daniel Dekany

Reply via email to