/ KOTLIN, VAADIN

Feedback on customizing Vaadin HTML template

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):

Add Session Initialization Listener

The second part happens when a request is made and Vaadin notices a new session must be created:

handlerequest

The end of the previous sequence diagram is designed in a generic way in Vaadin. In our specific case, it’s rendered as such:

Add Bootstrap Listener

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.

Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Currently working for Hazelcast. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More