Blog

How we implemented a drunk mode for Vaadin at SpringOne Platform 2019

By  
Alejandro Duarte
Alejandro Duarte
·
On Oct 24, 2019 3:15:00 PM
·

Recently, I had the chance to attend the SpringOne Platform conference in Austin, Texas. It was a high-quality conference with topics on Java, .NET, Kotlin, modern web, DevOps, and Spring. I had fun, learned a lot, and hacked with Vaadin. So keep reading to learn how we created a custom annotation to enable an easter egg in a Vaadin application.

Photo of Austin, Texas by the Vaadin team

The conference

We set up a Vaadin booth and shared time with Java developers interested in modern web development. It's always nice to see the "wow! I didn't know you could do that!" kind of reactions when you show how to code a web UI using Java.

Vaadin at SpringOne Platform 2019

Besides learning about Java technologies and trying the tasty American food and beers, we coded a "drunk mode" for Vaadin. Yes, a "drunk mode" for Vaadin.

Fun with CSS and JavaScript

It all started when my colleague Marcus added an easter egg to our booth demo. The demo was a simple web form with data binding implemented with Spring Boot and Vaadin. Marcus's hacked the app to add animations that resembled the effects of being drunk: A slow rotation and blurriness cyclic motion activated when the user pressed a key.

With Vaadin 14, you can implement this quite easily. First, we had a CSS file (src/main/webapp/drunk-mode.css) that defined the animation using CSS keyframes:

@keyframes drunk-animation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(3deg);
filter: blur(2px);
}
}

.drunk {
animation: 2s infinite alternate drunk-animation;
}

To add the drunk CSS class to the page when the user pressed a key, we had a JavaScript file (src/main/webapp/drunk-mode.js) with the following code:

window.addEventListener('keydown', e => {
document.body.classList.add('drunk');
});

This listener adds the drunk CSS class to the body element on the page when the user presses a key.

Finally, we included the CSS and JavaScript files in a Vaadin view as follows:

@CssImport("./drunk-mode.css")
@JavaScript("./drunk-mode.js")
public class MainView extends VerticalLayout {
...
}

This is what we had at the booth demo app at some point. But for some reason, I thought it would be a good idea to have a Java annotation to activate these animations without having to add @CssImport or @JavaScript annotations to the view. So we started a pair-programming session to explore the concept.

Implementing a custom Java annotation

We began by implementing a DrunkMode annotation:

@Retention(RetentionPolicy.RUNTIME)
public @interface DrunkMode { }

The @Retention mark makes the DrunkMode annotation available for reflection during runtime.

We needed the Vaadin app to include the CSS and JavaScript files when the annotation was present in the view. The easiest way to do this is by moving the @CssImport and @JavaScript annotations from the view class to the DrunkMode annotation. Here's what that possible implementation would look like:

@Retention(RetentionPolicy.RUNTIME)
@CssImport("./simple-drunk-mode.css") // place files in PROJECT_ROOT/frontend/
@JavaScript("./simple-drunk-mode.js")
public @interface SimpleDrunkMode { }

Note that if you use this approach, you have to move the CSS and JavaScript files to the PROJECT_ROOT/frontend/ directory since this is where the @CSSImport and @JavaScript annotations will look for the resources.

This implementation has a caveat, though. Marcus cleverly pointed out a physiological fact: "If you are drunk, you are drunk while using all the views, not just the one with the annotation." Good point.

Implementing a VaadinServiceInitListener

We needed a way to scan the views, test whether the DrunkMode annotation is present in at least one of the views, and add the CSS and JavaScript files accordingly at runtime. Luckily, you don't have to use something like AOP to implement this with Vaadin.

Vaadin comes with a VaadinServiceInitListener interface that you can implement to run any code when the VaadinService class is initializing. So we, naturally, coded a guess what… DrunkServiceInitListener:

public class DrunkServiceInitListener implements VaadinServiceInitListener {
@Override
public void serviceInit(ServiceInitEvent serviceInitEvent) {
}
}

The ServiceInitEvent class has a convenient method to filter the client-side dependencies added to the views. This includes adding dependencies. Something like:

serviceInitEvent.addDependencyFilter((dependencies, filterContext) -> {
dependencies.add(new Dependency(
Dependency.Type.STYLESHEET,
"/some-file.css",
LoadMode.EAGER)
);
return dependencies;
});

Combining this with the Router API and adding a bit of Java Reflection, we came up with the following implementation:

serviceInitEvent.addDependencyFilter((dependencies, filterContext) -> {
boolean isDrunk = UI.getCurrent().getRouter().getRegistry()
.getRegisteredRoutes().stream()
.map(RouteBaseData::getNavigationTarget)
.anyMatch(view ->
view.isAnnotationPresent(DrunkMode.class));

if (isDrunk) {
dependencies.add(new Dependency(
Dependency.Type.STYLESHEET,
"/styles/drunk-mode.css",
LoadMode.EAGER)
);
dependencies.add(new Dependency(
Dependency.Type.JAVASCRIPT,
"/js/drunk-mode.js",
LoadMode.EAGER)
);
}

return dependencies;
});

Vaadin uses SPI to discover implementations of VaadinServiceInitListener. This requires adding a file containing the fully-qualified name of the implementation class in a file with name com.vaadin.flow.server.VaadinServiceInitListener placed inside the src/main/resources/META-INF/services/ directory:

org.vaadin.drunkmode.DrunkServiceInitListener

With this, Vaadin can discover the DrunkServiceInitListener class, create an instance, and call its serviceInit method.

JAR packaging and publishing on Vaadin Directory

It was great to see how simple the code was and how the annotation worked as expected. Marcus added some more effects using more CSS keyframes, and then I packaged the thing in a Vaadin add-on available in the Vaadin Directory. Since the add-on is packaged as a JAR file, this last step required moving the drunk files to the src/main/resources/META-INF/resources/frontend/ directory.

I really enjoyed the conference and got to speak with tech enthusiasts and developers not only from the Java world but also from .NET and Python. Vaadin seems to be a web framework that most back end developers find unique, modern, and productive. I hope to meet more of you in the future!

You can find future events, including Vaadin Dev Day, conferences, and online training sessions at https://vaadin.com/events. See you next time!

Alejandro Duarte
Alejandro Duarte
Software Engineer and Developer Advocate at MariaDB Corporation. Author of Practical Vaadin (Apress), Data-Centric Applications with Vaadin 8 (Packt), and Vaadin 7 UI Design by Example (Packt). Passionate about software development with Java and open-source technologies. Contact him on Twitter @alejandro_du or through his personal blog at www.programmingbrain.com.
Other posts by Alejandro Duarte