samskivert / jmustache

A Java implementation of the Mustache templating language.
Other
834 stars 128 forks source link

Accessing nested context in lambdas #92

Closed hookumsnivy closed 6 years ago

hookumsnivy commented 6 years ago

Is there a way to access and utilize the full nested context in a lambda? I'm trying to implement 2 lambda's that will require the full context:

  1. a dynamic include. Just like the normal include {{> include}} I want the full context
  2. the equivalent of the #evaluate macro in velocity.

Unfortunately I don't see a way to access the full context from the fragment. To make it more difficult, even if I had the full context, I don't have access to the necessary methods on Template. The execute methods wrap the passed in data object since it's not expected to be a Template.Context object. Instead, I would need to call executeSegs which accepts the Context object properly.

In theory I could probably rebuild the context (yuck) by getting all the ancestor contexts until I hit an exception, but that isn't a good solution. It would be great if there was a way to execute a template with the full context from a given fragment.

Ideally my code would look something like this for the dynamic include:

class DynamicIncludeLambda implements Mustache.Lambda {
        private final Mustache.TemplateLoader templateLoader;
        private final Mustache.Compiler compiler;

        public DynamicIncludeLambda(Mustache.TemplateLoader templateLoader, Mustache.Compiler compiler) {
            this.templateLoader = templateLoader;
            this.compiler = compiler;
        }

        @Override
        public void execute(Template.Fragment frag, Writer out) throws IOException {
            Template template = getTemplate(frag.execute());
            template.executeWithContext(frag.getFullContext(), out);
        }

        private Template getTemplate(String templateName) {
            try (Reader templateReader = templateLoader.getTemplate(templateName)){
                return compiler.compile(templateReader);
            } catch (Exception e) {
                throw new MustacheException("Unable to load template: " + templateName, e);
            }
        }
    }

Ignore the poor naming :) There are probably better ways to pass the context. Maybe passing in the fragment itself.

For the moment, I've proven the concept by using reflection to bypass the security constraints :)

samskivert commented 6 years ago

I think the cleanest way would be to have a method on Fragment that allows one to execute a template with the Fragment's context. So your Lambda execute method would look like:

Template template = getTemplate(frag.execute());
frag.executeTemplate(template, out);

The alternative, passing the fragment to the template would require the template to be able to reach inside the fragment and get its context which is a little bit messier. I think I'd rather do one of these two things than expose the Context object to client code and prevent that from ever changing in the future.

hookumsnivy commented 6 years ago

That will work for me. Thank you. I wasn't sure if you wanted to add something like that to Fragment as it seems weird to have a fragment execute a segment. Is there an upcoming release?

samskivert commented 6 years ago

I went ahead and shipped 1.14 today. It should be out to Maven Central in a few hours.

dawnofclarity commented 6 years ago

This is a v. useful change. I have a small library that emits "boilerplate" bootstrap html for the standard control types via a lambda, which I can now update to use mini-templates. This should reduce, possibly eliminate, the need for it to poke about in the context for data. Ty.