pebble
pebble copied to clipboard
Feature Request: Fragment Renderer
I think we need a "fragment renderer" mechanism in pebble. It acts like an include/macro but has an additional Java-Class attached, that can execute business logic or queries the database.
Example:
In template:
{% render "latestPosts" with {"count":5} %}
In Java:
public class LatestPosts implements PebbleFramgmentRenderer {
public Map render (Object... args) {
...
model.put("posts", getLatestsPosts());
}
public String provideTemplateName() {
return "/framgements/latestPosts";
}
In Template: (/framgements/latestPosts)
{% for post in posts %}
<h3>{{ post.title }}</h3>
{% endfor %}
In configuration:
public class PebbleExtension extends AbstractExtension {
@Override
public Map<String, FragmentRenderer> getFragmentRenderer () {
Map<String, FragmentRenderer> renderer = new HashMap();
renderer.put("latestPosts", new LatestsPosts());
return renderer;
}
}
.net has View Components (https://docs.asp.net/en/latest/mvc/views/view-components.html), symfony has Subrequests (http://symfony.com/doc/current/templating/embedding_controllers.html). I think, the .net approach is very good for this.
if the current HttpServletRequest could be passed to the "render" method, this would be even more useful!
I do something similar to this using a plain function. In the function, I use a PebbleEngine
to render a "fragment" and use the result to generate the result of the function itself.
The only difference I see from your example is that the current PebbleEngine
could be more easily available from inside a function/filter. I inject mine using Guice since my custom Extension
is part of the Guice context.
That is a nice idea. I'll try this - thank you!
@jochen777 Have you come to a conclusion? Can you share some code?
At first I really liked this idea but the more I think about it the more it feels like we would just be implementing a new technique for something that is already possible. I think I'm of the opinion that this feature is better implemented at the application/framework level instead of within the Pebble library (similar to how this feature exists in Symfony but does not exist in the Twig library). A framework may already have something that is synonymous with a "fragment renderer" (ex. a controller in an MVC app) and implement it with that in mind, similar to how Symfony did theirs. I may put more thought into implementing this feature into our pebble-spring4 library.
Below I've listed a couple alternate methods of how we can access native Java code prior to rendering a sub-template; the first being suggested by @electrotype. Neither of which require a lot of code and both could easily be expanded or made more generic.
Custom Pebble function that internally invokes a PebbleEngine
main.html:
{{ latestPosts() }}
custom pebble function:
public String latestPosts(Map<String, Object> args) {
Map<String, Object> context = new HashMap<>();
context.put("posts", database.query("SELECT * FROM posts"));
PebbleEngine engine = new PebbleEngine.Builder().build();
return engine.getTemplate('/fragments/latestPosts').evaluate(new StringWriter(), context);
}
Logic contained within the provided context
main.html:
{% include '/fragments/latestPosts' %}
/fragments/latestPosts.html:
{% for post in latestPosts.posts %} {{ post }} {% endfor %}
LatestPostsRenderer.java:
public class LatestPostsRenderer {
public List<Post> posts(){
return database.query("SELECT * FROM posts");
}
}
rendering the main
template:
Map<String, Object> context = new HashMap<>();
context.put("latestPosts", new LatestPostsRenderer());
PebbleEngine engine = new PebbleEngine.Builder().build();
return engine.getTemplate("main").evaluate(new StringWriter(), context);
Thank you for your answer!
The problem with the custom-function: It does not get a handle to the current http-request. So you can't react on cookies or the current path...
And the problem with provided context: Your controller must be aware of that and collect all data.
I agree with you, that this may be better included in the webframework-integration layer. So I really would love to have it in the pebble-spring4 project!
But in the first place, the custom function can solve a lot of problems like this for me. I can easily access the Pebble-engine within the spring - context.
The problem with the custom-function: It does not get a handle to the current http-request. So you can't react on cookies or the current path...
Maybe you could use RequestContextHolder
?
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
Yes, I know RequestContextHolder. But it is kind of ugly and hard to test... (But of course better than nothing!)