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

> 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.)
>

Yes. Note that if the template is re-loaded (not just reused from template
cache) during the Environment life-cycle, then you have a problem. But if
we assume that that can happen, then you also have a problem with any other
call place identification. Like if you use {path, row, column} as the key,
the modified file might have a different row or column.

If you are using #import for your directives that use render_once, and not
#include, then no reloading will happen. So maybe it's still good enough.

The most straightforward and robust way is to have something like
<@render_once "jquery">...</@>.


> 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.
>

I haven't worked out anything for FM2, so, just quick thoughts. The most
basic solution is to add the TemplateDirectoveModelEx interface that
extends  TemplateDirectoveModel, to add a method that gets a List of
positional parameters. And then, the implementer does whatever they want
there. But certainly I would add a convenience abstract class where you
only have to add a Java method that doesn't override anything, and has
plain Java positional parameters, but then annotate the parameter with the
desired name, maybe even the default value. And then the instantiation can
generate whatever is needed to bridge the TemplateDirectoveModelEx  methods
with that.

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
>
>

-- 
Best regards,
Daniel Dekany

Reply via email to