pippo
pippo copied to clipboard
Add route method helper in templates
In my applications based on Pippo I feel that I'm missing a route(<template_name>, paramId_1:paramValue_1, ...)
method in my html templates.
For example, in companies.peb file I have something like:
<a href="{{ appPath }}/admin/companies/{{ company.id }}">{{ i18n('edit') }}</a>
and I wish something like (pseudo code):
<a href="{{ route("admin.companies.edit", "id":company.id) }}">{{ i18n('edit') }}</a>
and in .java
file (I will don't use route group for simplicity):
GET("/admin/customers/{id}, ...).named("admin.companies.edit");
I built a template function route
that basically concatenates parameters (up to 5) with each other, smartly collapsing slashes if needed (but still allowing you to double up if needed). Parameters will treated as strings by calling .toString()
, so they should implement that method. If they don't, Object
does that anyway, but you might get weird output with custom objects.
Before I do a pull request, I want to verify the functionality that is desired. In your example, with my method, you would do this:
<a href="{{ route(appPath, "/admin/companies/", company.id) }}">{{ i18n('edit') }}</a>
and (if appPath = "/my/app/path"
and company.id = 7
) you would get:
<a href="/my/app/path/admin/companies/7">edit</a>
Is that the functionality that is desired?
It might be helpful to give 2 or 3 test cases showing input and desired output.
-gns
My idea is to use Router.uriFor (reverse routing) with a route name in this template function.
The implementation of template function do a simple job: read the function parameters, call Router.getUri
and it returns the result (a string) of the method call.
I prefer this approach with route name because is short (I don't write too much). As a bonus Router.getUri
accept as first parameter nameOrUriPattern
so in theory (you should check), this implementation resolved your case (pass uri pattern but without appPath
that it's unnecessary because it is present in all scenarious and we can retrieve the value of this variable directly in template function, no need to carry on this variable any time).
Your version is good but it's not the shortest one.
Code snippet for template:
- variant with route name (you must give a name to your route)
<a href="{{ route("admin.companies.edit", "id":company.id) }}">{{ i18n('edit') }}</a>
- variant with uri pattern
<a href="{{ route("/admin/companies", "id":company.id) }}">{{ i18n('edit') }}</a>
Make sense for you what I say?
Not completely, but it could be because I'm not an expert. What output do you expect from the examples you gave?
-gns
I do understand that you want to load appPath
automatically (which makes sense to me), but it is the second part that I'm not entirely grasping. I think if you show me the expected output, then I will understand better.
-gns
The expected output is:
<a href="/admin/companies/32">Edit</a>
where 32 is the value for id
.
Take a look at javadoc of Router.uriFor method. This method is used extensive in Java code (back-end) when I wish to redirect a request to another route. The idea is that Pippo knows how to create an url for a route starting from the route name (or uri pattern) and parameters values (query and path parameters). For example:
// register a route (a route with name "user")
GET("/user/{id}", new MyRouteHandler()).named("user");
// register other route that redirect to "user" route
GET("/admin", routeContext -> {
Map<String, Object> parameters = new HashMap<>();
parameters.put("id", 0); // the admin user has id 0 (zero)
routeContext.redirect(getRouter().uriFor("user", parameters)); // "user" is the name of the first declared route
}
In the second route instead of
routeContext.redirect(getRouter().uriFor("user", parameters)); // "user" is the name of the first declared route
//routeContext.redirect("user", parameters); // it's a shortcut of above line
you can use directly, the uri (url) generated by uriFor
method
routeContext.redirect("/user/0"); // "user" is the name of the first declared
but it's safe (against refactoring) to use the variant with getRouter().uriFor()
.
Is it a bit more clear for you? Do you have questions?
Any interest for this feature?
This sounds very attractive to me. Especially if it could be compile time safe, which it probably can't be, but at least fail loudly at run time then so we can unit test it.
@Wavesonics I will try an implementation for Pebble template engine in first step (I use this template engine in my projects) and I will propose you a PR.
I was sitting down to implement this for my self, and I realize after some looking that it doesn't actually look like it's possible to add more helpers to a template engine at run time?
Currently the builder happens entirely in the init()
method with no hooks to modify it:
https://github.com/pippo-java/pippo/blob/995773510b7ae19d45b8f881820d689d093655f6/pippo-template-parent/pippo-trimou/src/main/java/ro/pippo/trimou/TrimouTemplateEngine.java#L70
Not sure which way you'd guys prefer going about this, but might be nice to provide a method to override where you are passed the builder object and allowed to customize it?
@Wavesonics
You can create the MyTrimouTemplateEngine
class, like this:
public class MyTrimouTemplateEngine extends TrimouTemplateEngine {
@Override
protected void init(Application application, Configuration configuration) {
// your custom logic here
}
}
In your Application / ControllerApplication sub-class, you can do the following:
public class PippoApplication extends ControllerApplication {
@Override
protected void onInit() {
setTemplateEngine(new MyTrimouTemplateEngine());
}
}
I think this solves your problem. I needed to do this with the Freemarker and it worked.
Oh man I totally missed that, and it's exactly what I was looking for. I apologize.
I have a very basic Helper implemented for this that is working quite well: https://gist.github.com/Wavesonics/839b283eaf1245ffbe251f8d8a6e3016 It's currently implemented in Kotlin, but I can convert it to Java of course.
Which in use looks like this:
<a href="{{route 'index.loginPage'}}">Login</a><br/>
Being able to handle variables would be very useful, I need to figure out how to go about that using the template system.
Updated it to accept parameters for the routes: https://gist.github.com/Wavesonics/d1e65a4c094a04e0a7066d92545e9e4f
<a href="{{route 'user.userForName' 'name: user.userName'}}">User Page</a><br/>
I think that it's a good practice to add the missing information in doc incremental, when someone find a good solution to his problems, solution that is not present in documentation. If a contribution with an update is not possible, it's enough an issue that point to solution, I think that documentation together with tests are things that are not sufficiently developed and from this reason the adoption of Pippo is not high enough.
I implemented this feature in Pebble template engine (I use this template engine in my projects). The implementation is not complete (need to add more defense code - deal with route not found) but is complete functional.
How to use:
- define a route with a name
GET("/quote", routeContext -> {
ParameterValue symbol = routeContext.getParameter("symbol");
if (symbol.isEmpty()) {
routeContext.getResponse().badRequest().send("Specify a symbol as parameter");
} else {
Stock stock = dataStore.select(Stock.class).where(Stock.SYMBOL.eq(symbol.toString())).get().first();
List<Quote> quotes = dataStore.select(Quote.class).where(Quote.SYMBOL.eq(stock.symbol)).get().toList();
Map<String, Object> model = new HashMap<>();
model.put("stock", stock);
model.put("quotes", quotes);
routeContext.render("quote", model);
}
}).named("quote");
- reference the route defined above using
route
method
<a href="{{ route('quote', {'symbol': stock.symbol }) }}">{{ stock.symbol }}</a>
I want to say that I consider this feature very nice. The html template is more clear and short :smile: .