Writing full-stack statically-typed web apps on JVM at its simplest
This project is maintained by mvysny
Index | Getting Started | Guides |
Vaadin-on-Kotlin uses the Vaadin Platform to deliver the UI. Vaadin lets you forget the web and develop user interfaces much like you would develop a desktop application with conventional Java toolkits such as AWT, Swing, or SWT. You add components such as Button and TextField into the page, nesting them in layouts which then position the components.
The web is composed of HTML pages. The basic building block of a HTML page is the HTML element, such as <div>
or <a>
.
However, with the advent of Web Components the paradigm is shifted - along
with the HTML elements, you compose
the page by using the Web Components such as Paper Input
or Vaadin Grid, which then host the necessary HTML elements themselves.
The web has been reinventing itself, pushing typical Java frameworks such as JSF away and bringing JavaScript frameworks such as Angular and React to the front, reducing Java server side to a mere directory of REST endpoints. In this regard, Vaadin is different.
Instead of being a mere REST directory, Vaadin provides means to control the Web Components from Server side. It employs so-called Vaadin Flow to synchronize a subset of Web Component properties (the Web component state) between client-side and server-side. Every Vaadin component thus consists of two parts:
PolymerTemplate
s).Component
class and uses
APIs provided by the Component
class, such as support for transmitting events (such as button click events);
it also allows you to control the underlying web component by setting
values either to particular Polymer properties, to plain HTML attributes,
or via calls to JavaScript functions exposed on the web component itself.This kind of approach makes it incredibly easy to add e.g. Google Maps to your site. You just use the components’ server-side Java API and you don’t have to care about what the HTML will look like, or how exactly it will fetch the data. The code looks like follows:
class MyView : VerticalLayout {
init {
setSizeFull()
val maps = GoogleMaps()
maps.setSizeFull()
addComponent(maps)
}
}
Info: There are great resources on how to write a web component using Polymer 2, for example the Getting Started With Polymer 2. In this guide we will not focus on the client-side part; instead we will focus on how to compose the server-side components.
The components are typically rich in functionality. For example ComboBox
does not render into the HTML <input>
element but it instead renders
a rich <div>
hierarchy which allows for features which are not possible with the <input>
element, such as auto-completion.
There is a big palette of pre-made components, and we use server-side Java code to create them and add them into layouts. Vaadin
then makes sure to create the client-side web component part for every component and inserts it into the HTML into appropriate location. The rendering
process is typically self-contained, implemented in the component client-side code and typically can not be controlled directly from server-side Java.
For example, a typical Vaadin form uses the FormLayout
component and adds a couple of CheckBox
, TextField
and
ComboBox
components. The code on server-side would look like this:
FormLayout layout = new FormLayout("New Employee Form");
TextField nameField = new TextField("Name:");
nameField.setValue("Donald Knuth");
layout.addComponent(nameField);
layout.addComponent(new CheckBox("Internal employee"));
layout.addComponent(new DatePicker("Date of birth:"));
This code builds a component hierarchy (a tree of components, in this case fields nested in a form layout). The Vaadin Flow then takes care to render appropriate web components and control them from server-side.
With VoK, we create component hierarchies by employing the DSL Kotlin language feature. We will now show how that is done in VoK in a minute.
The following text doesn’t expect you to be familiar with the Vaadin framework. However, it is best to have at least basic understanding of the Kotlin programming language. If you feel lost in the following text, please take your time to learn of the Kotlin language features first.
Lots of components are already built-in. You can browse built-in Vaadin 10 components. You can also visit Vaadin 10 Documentation on Components to read about all of the available components.
You can find additional components at the Vaadin Directory; just make sure to search for components intended for Vaadin 10 since Vaadin 8/7 components won’t work with Vaadin 10.
It is also possible to integrate a standalone Web Component into Vaadin. Just find a (preferably Polymer 2-based) component at the webcomponents.org page, then find and include appropriate webjar to include the web component into your project. Then, read the Integrating a Web Component Vaadin 10 documentation on how to integrate web component into Vaadin 10.
Please git clone the VoK Hello World App Vaadin 10 - we’re going to experiment on that app.
If you open the WelcomeView.kt
file, you’ll notice that it extends from the VerticalLayout
. By extending the VerticalLayout
we state that the root layout of this view is going to be vertical.
Vaadin 10 no longer introduces its own layout manager but uses the CSS Flex Layout extensively. Yet, we still provide
VerticalLayout
andHorizontalLayout
as a wrappers over the Flex Layout, for those familiar with Vaadin 8’sVerticalLayout
andHorizontalLayout
and unfamiliar with the Flex layout. However, the behavior of those two classes is a bit different with Vaadin 10 than it was in Vaadin 8, please read Vaadin 10 server-side layouting for Vaadin 8 and Android developers for a full explanation.
Let’s now replace the contents of the WelcomeView
by a single button. Rewrite the WelcomeView
class as follows:
package com.example.vok
import com.github.vok.karibudsl.flow.*
import com.vaadin.flow.component.notification.Notification
import com.vaadin.flow.component.orderedlayout.VerticalLayout
import com.vaadin.flow.router.Route
@Route("")
class WelcomeView: VerticalLayout() {
init {
button("Click me") {
onLeftClick {
Notification.show("Clicked")
}
}
}
}
The button()
function simply creates the Vaadin Button
, sets a caption for it, inserts it into the parent layout (in this case,
the root VerticalLayout
/WelcomeView
) and runs the configuration block for that button. The configuration block adds a left click
listener. You can ctrl+click to see the sources of the button()
function; the definition of the function looks very cryptic:
fun (@VaadinDsl HasComponents).button(caption: String? = null, block: (@VaadinDsl Button).() -> Unit = {})
= init(Button(caption), block)
This is a Kotlin function definition which allows us to build UIs in a structured way, by employing so-called DSLs. Don’t worry if this doesn’t make any sense right now - we will explain this in a great detail later on. What the function does is that it creates a button, adds it to the parent layout and allows the button to be configured further.
Info: A technique called DSL (domain-specific language) is used to construct hierarchical structures using just the Kotlin language features. Since the UI is a hierarchical structure with components nested inside layouts, the DSL approach is applicable. In VoK we have constructed a set of functions which will allow you to construct Vaadin UIs in a hierarchical manner. Please read the Using DSL to write structured UI code article on why a hierarchical code beats plain Java code.
Let us add a simple form, consisting of two text fields:
package com.example.vok
import com.github.vok.karibudsl.flow.*
import com.vaadin.flow.component.notification.Notification
import com.vaadin.flow.component.orderedlayout.VerticalLayout
import com.vaadin.flow.router.Route
@Route("")
class WelcomeView: VerticalLayout() {
init {
formLayout {
textField("Name:")
textField("Age:")
}
button("Click me") {
onLeftClick {
Notification.show("Clicked")
}
}
}
}
The formLayout()
function creates Vaadin FormLayout
component and adds it into the root VerticalLayout
. Then it runs the configuration
block, acting as a parent layout in that block. This is very important since that will correctly allow the textField()
function to insert
the newly created TextField
class into the FormLayout
itself, and not into the root VerticalLayout
.
This “magic” is actually just a feature of Kotlin. Kotlin simply passes current layout as a receiver
to the textField()
function. But more on that later.
Important: the notation is that the DSL function for a component starts with a lower-case letter. For example, the function which creates
FormLayout
is calledformLayout()
, the same goes forlabel()
,verticalLayout()
and all other built-in Vaadin components. To add DSL function for add-ons and custom components, please read below.
Info: The
FormLayout
is a powerful responsive layout which can change the number of columns depending on its width. Please find out more at the vaadin-form-layout documentation page.
Vaadin 10 delegates all layout work to CSS. There are no JavaScript-based layouts like in Vaadin 8 - there is no need for that since we have CSS flexbox and CSS grid positioning standards which are now powerful enough to cater for all layouting needs.
In order to position the components, it’s encouraged to nest the components inside the FlexLayout
layout (which translates to <div style="display: flex">
)
and position the components using the CSS flexbox.
You can read more about flexbox at A Complete Guide To Flexbox.
You can then control the flexbox algorithm from your server-side Kotlin code, via the following properties:
flexLayout.alignContent
flexLayout.flexWrap
flexLayout.justifyContent
flex-direction
so just call flexLayout.elemment.style.put("flexDirection", "row-reverse")
child.flexGrow
or child.isExpand
child.flexShrink
child.flexBasis
child.alignSelf
Coming from Vaadin 8 or Android, it may be easier for you to use familiar language to define the layouts.
You can then use VerticalLayout
and HorizontalLayout
classes. They still use flexbox underneath, but they translate
flexbox properties to a more familiar terminology.
You can learn more in the VerticalLayout and HorizontalLayout blogpost.
In order to position the child components inside of the VerticalLayout
, you need to call the align()
function inside of the
content{}
block, as follows:
verticalLayout {
content { align(stretch, top) }
width = "300px"; height = "100px"
button("Click me")
}
The VerticalLayout uses flexbox flex-direction column to lay out components vertically downwards. The align(stretch, top)
will make all
child components match VerticalLayout’s width, and will position them to the top of the VerticalLayout. It will make the button wide, even though
the button’s width is undefined (wrap the caption). Please consult the documentation on the align()
function and the stretch
/top
constants for more info.
Now that you understand the concepts, let us list some examples. The first example is a classical example of doing a perfect centering of a component inside of its parent. It was impossible to do with CSS prior the flexbox, yet with flexbox it’s just a simple case of setting the proper alignment to the child:
@Route("")
class WelcomeView : VerticalLayout() {
init {
flexLayout {
justifyContentMode = FlexComponent.JustifyContentMode.CENTER
alignItems = FlexComponent.Alignment.CENTER
width = "300px"; height = "100px"
button("Click me")
}
}
}
The result is as follows:
The same can be achieved by using the VerticalLayout
, albeit with a different API. Note how we specify the alignment of the content
in the content{}
block:
@Route("")
class WelcomeView : VerticalLayout() {
init {
verticalLayout {
content { align(center, middle) }
width = "300px"; height = "100px"
button("Click me")
}
}
}
Another case would be a button bar, having a bunch of buttons both on the left side and on the right side.
There is a Div
acting as an spacer; since it’s expanded it consumes all of the available space,
pushing follow-up buttons to the right:
@Route("")
class WelcomeView: VerticalLayout() {
init {
flexLayout {
width = "300px"
icon(VaadinIcon.EDIT)
icon(VaadinIcon.TRASH)
div { isExpand = true }
icon(VaadinIcon.AIRPLANE)
}
}
}
Info: you can use the Vaadin Icons page to search for available Vaadin icons, or you can simply use IDE’s autocompletion feature for available
VaadinIcons
enum constants.
You can use the same approach for building the application frame. We’re going to have the main menu to the right, and expand the left area so that the views can be nested inside of it:
@BodySize(width = "100vw", height = "100vh")
@HtmlImport("frontend://styles.html")
@Viewport("width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes")
@Theme(Lumo::class)
class RootLayout : HorizontalLayout(), RouterLayout {
private val viewContainer: Div
init {
setSizeFull()
verticalLayout {
width = null; height = "100%"; isSpacing = false; isMargin = false
button("About", icon = Icon(VaadinIcon.QUESTION)) { themes.add("tertiary") }
button("Users", icon = Icon(VaadinIcon.USERS)) { themes.add("tertiary") }
button("Log Out", icon = Icon(VaadinIcon.USERS)) { themes.add("tertiary") }
}
viewContainer = div {
isExpand = true
}
}
override fun showRouterLayoutContent(content: HasElement) {
viewContainer.removeAll()
viewContainer.element.appendChild(content.element)
}
}
/**
* The main view contains a button and a template element.
*/
@Route("", layout = RootLayout::class)
class WelcomeView : VerticalLayout() {
init {
div {
text("Hello world!")
}
}
}
Another very important set of components are those that handle user input. All of the input components are documented on the Vaadin site, for example on the TextField documentation page.
Every field has a different purpose which we will not document here; for further questions please take a look at the following resources:
Also please read the Creating Forms article for more information on how to build forms in VoK.
The textField()
function also returns the newly created TextField
. This is handy if we want to reference those text fields later, for
example from the button click handler:
package com.example.vok
import com.github.vok.karibudsl.flow.*
import com.vaadin.flow.component.notification.Notification
import com.vaadin.flow.component.orderedlayout.VerticalLayout
import com.vaadin.flow.component.textfield.TextField
import com.vaadin.flow.router.Route
@Route("")
class WelcomeView: VerticalLayout() {
private lateinit var nameField: TextField
private lateinit var ageField: TextField
init {
formLayout {
nameField = textField("Name:")
ageField = textField("Age:")
}
button("Click me") {
onLeftClick {
Notification.show("Hello, ${nameField.value} of age ${ageField.value}")
}
}
}
}
The core principle of Vaadin is that it is very easy to create reusable components. For example, in order to create a reusable form, all you need to do is to define a class:
class NameAgeForm : FormLayout() {
private val nameField = textField("Name:")
private val ageField = textField("Age:")
val greeting: String get() = "Hello, ${nameField.value} of age ${ageField.value}"
}
This class is a form layout with two text fields nested inside of it. However, we can’t use it directly in the DSL fashion yet - the integration function is missing:
fun HasComponents.nameAgeForm(block: NameAgeForm.()->Unit = {}): NameAgeForm = init(NameAgeForm(), block)
The function instantiates the form and calls the init()
method which will add the newly created form into the parent layout and then
it will call the configuration block
on it. Now we can rewrite the WelcomeView
as follows:
package com.example.vok
import com.github.vok.karibudsl.flow.*
import com.vaadin.flow.component.HasComponents
import com.vaadin.flow.component.formlayout.FormLayout
import com.vaadin.flow.component.notification.Notification
import com.vaadin.flow.component.orderedlayout.VerticalLayout
import com.vaadin.flow.router.Route
@Route("")
class WelcomeView: VerticalLayout() {
init {
val form = nameAgeForm()
button("Click me") {
onLeftClick {
Notification.show(form.greeting)
}
}
}
}
class NameAgeForm : FormLayout() {
private val nameField = textField("Name:")
private val ageField = textField("Age:")
val greeting: String get() = "Hello, ${nameField.value} of age ${ageField.value}"
}
fun HasComponents.nameAgeForm(block: NameAgeForm.()->Unit = {}): NameAgeForm = init(NameAgeForm(), block)
KComposite
PatternThe advantage of extending from KComposite
, instead of extending the layout (e.g. VerticalLayout
) directly, is as follows:
VerticalLayout
,
resulting in a more compact and to-the-point API. The API coming from KComposite
is
tiny in comparison.VerticalLayout
API doesn’t leak into our component, we are free to
replace the VerticalLayout
with any other layout in the future, without breaking the API.ButtonBar
class below as
an example: it can clearly be seen that the buttons are nested in the HorizontalLayout
:Example 1.: ButtonBar extending KComposite with a clear UI hierarchy
class ButtonBar : KComposite() {
val root = ui {
horizontalLayout {
button("ok")
}
}
}
Example 2.: ButtonBar extending HorizontalLayout without a clear indication that the button is nested in a horizontal layout:
class ButtonBar : HorizontalLayout() {
init {
button("ok")
}
}
The root
variable will be marked by the IDE as unused. This is okay: the
side-effect of the ui {}
is that it runs the horizontalLayout()
function
which then attaches the HorizontalLayout
to the KComposite
itself.
However, you may prefer to get rid of this unused root
variable and call the
ui {}
from the init {}
Kotlin initializer. The downside is that the
UI-creating code will be indented by two tabs instead of one.
To learn Vaadin:
To learn about Kotlin DSLs: