Last week, my post was about how you could customize the default Vaadin HTML template so you could add a lang
attribute for example.
I didn’t ask for feedback, but I got it anyway, so let’s use the occasion to analyze it.
First, I must admit that my solution was not Vaadin-esque as I used AOP to get the job done. My friend Matti Tahvonen from the Vaadin team was kind enough to provide not only one but 2 alternatives to my proposal. Thanks Matti!
Alternative solutions
The first solution - also the one I got the last, was submitted by AMahdy AbdElAziz:
JavaScript.eval("document.querySelector('html').setAttribute('lang', 'fr')")
Evil but it works and couldn’t be simpler!
The second solution uses Matti’s own framework - Viritin, to achieve that. I haven’t looked at the framework yet (but it’s in my todo list) so I cannot comment it but here is the snippet:
@Theme("mytheme")
public class MyUI extends UI {
@Override
protected void init(VaadinRequest vaadinRequest) {
// HtmlElementPropertySetter is part of Viritin add-on https://vaadin.com/directory=!addon/viritin
HtmlElementPropertySetter heps = new HtmlElementPropertySetter(this);
heps.setProperty("//html", "lang", "fr");
setContent(new Label("Hello lang attr"));
}
@WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
@VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
public static class MyUIServlet extends VaadinServlet {}
}
The standard Vaadin-esque server-side way is a bit more convoluted:
@Theme("mytheme")
public class MyUI extends UI {
@Override
protected void init(VaadinRequest vaadinRequest) {
setContent(new Label("Hello lang attr"));
}
@WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
@VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
public static class MyUIServlet extends VaadinServlet {
@Override
protected void servletInitialized() throws ServletException {
super.servletInitialized();
getService().addSessionInitListener(e ->
e.getSession().addBootstrapListener(new BootstrapListener() {
@Override
public void modifyBootstrapFragment(
BootstrapFragmentResponse response) {
// NOP, this is for portlets etc
}
@Override
public void modifyBootstrapPage(BootstrapPageResponse response) {
response.getDocument().child(0).attr("lang", "fr");
}
})
);
}
}
}
However, this one I can analyze and comment :-)
Flow decomposition
Note: readers who are more interested in possoble improvements can skip directly to that section.
The first part is quite easy. It just registers a new SessionInitListener (the proposed code uses a lambda to do that):
The second part happens when a request is made and Vaadin notices a new session must be created:
The end of the previous sequence diagram is designed in a generic way in Vaadin. In our specific case, it’s rendered as such:
Improvements
I think a couple of improvements can be made.
- The code is awfully verbose - even using Java 8’s lambda
- Two objects are created each time a session is initialized, the session listener and the bootstrap listener
- Setting the servlet class as an internal class of the UI sends shiver down my spine. Though I understand this is a Gist, this is unfortunately what you get by using the Vaadin Maven archetype. This is very far from the Single-Responsibility Principle.
Since my initial example uses both Spring Boot and Kotlin, here’s my version:
@Configuration
open class AppConfiguration {
// ... Abridged for readability's sake
@Bean
open fun vaadinServlet() = CustomVaadinServlet{ event: SessionInitEvent ->
event.session.addBootstrapListener(object : BootstrapListener {
override fun modifyBootstrapFragment(response: BootstrapFragmentResponse) {
// NOP, this is for portlets etc
}
override fun modifyBootstrapPage(response: BootstrapPageResponse) {
response.document.child(0).attr("lang", "fr")
}
})
}
}
class CustomVaadinServlet(private val listener: (SessionInitEvent) -> Unit) : SpringVaadinServlet() {
override fun servletInitialized() {
super.servletInitialized();
service.addSessionInitListener(listener)
}
}
With Spring Boot, I can manage the SessionInitListener
as a singleton-scoped Bean.
By passing it as a servlet parameter, I can create only a single instance of SessionInitListener
and BootstrapListener
each.
Of course, it’s only possible because the language value is set in stone.
Thanks to Kotlin, I co-located the overriden servlet class within the configuration file but outside the configuration class. Since the servlet is used only by the configuration, it makes sense to put them together… but not too much.
Finally, note that SessionInitListener
is a functional interface, meaning it has a single method.
This single method is the equivalent of a function taking a SessionInitEvent
and return nothing.
In Kotlin, the signature is (SessionInitEvent) → Unit
.
Instead of creating an anonymous inner class, I preferred using the function.
It’s not an improvement, but a more functional alternative.
At runtime, both alternatives will allocate the same amount of memory.
The complete source code can be found on Github in the manage-lang branch.