I've also discovered that DirectiveCallPlace.getOrCreateCustomData doesn't guarantee that it will be stable.
"the object will be usually created only once, even if multiple threads request the value when it's still null. It doesn't stand though when providerIdentity mismatches occur" "if multiple directives that use the custom data feature use the same call place, the caching of the custom data can be inefficient, as they will keep overwriting each other's custom data" 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