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.
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.
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.
Grab a drink and drop by the @vaadin booth at @s1p to see the drunk mode in action #SpringOne pic.twitter.com/vbGvPiqzIO
— Alejandro Duarte (@alejandro_du) October 9, 2019
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.
👌 exquisite pic.twitter.com/pzseW7hlyi
— Marcus Hellberg (@marcushellberg) October 10, 2019
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!