2

This question is about a Kotlin JS project which uses the Kotlin Frontend Plugin.

I want to use some UI components from the Vaadin Components library.

I have two questions about this:

(1) What would be the best way to include web components in Kotlin JS

=> for my complete code, see the link to the source below. In summary the relevant details are:

build.gradle.kts

kotlinFrontend {
    npm {
        dependency("@vaadin/vaadin-grid")
    }
}

vaadin.grid.Imports.kt

@file:JsModule("@vaadin/vaadin-grid")
@file:JsNonModule
package vaadin.grid
external class GridElement {
    companion object
}

Why the companion object? I need it for the workaround (see below).

foo.kt

fun main() {

    document.getElementById("container")!!.append {
        vaadin_grid {
            attributes["id"] = "grid"
        }
    }

    initUI()

}

fun initUI() {
    // Force the side-effects of the vaadin modules. Is there a better way?
    console.log(GridElement)

    val grid = document.querySelector("#grid") /* ?? as GridElement ?? */
}

The console.log is the ugly workaround trick I want to avoid. If I don't do anything with GridElement then it's just not included in my bundle.

The vaadin_grid DSL is defined as a custom kotlinx.html tag which is unrelated code.

(2) I want to keep my code as typed as possible to avoid asDynamic but when I cast the HTMLElement to a Vaadin Element I get ClassCastExceptions (because GridElement is undefined).

For example I want to write something like this:

val grid : GridElement = document.querySelector("#grid") as GridElement
grid.items = ... // vs grid.asDynamic().items which does work

Here is how I define the external GridElement

vaadin/button/Imports.kt

@file:JsModule("@vaadin/vaadin-grid")
@file:JsNonModule

package vaadin.grid

import org.w3c.dom.HTMLElement

abstract external class GridElement : HTMLElement {
    var items: Array<*> = definedExternally
}

build/node_modules/@vaadin/vaadin-grid/src/vaadin-grid.js

...
customElements.define(GridElement.is, GridElement);
export { GridElement };

Source example

To run:

From the root of the git repo:

./gradlew 05-kt-frontend-vaadin:build && open 05-kt-frontend-vaadin/frontend.html
monk
  • 640
  • 1
  • 5
  • 17

1 Answers1

2

I found the answer(s)

For the first question

(1) What would be the best way to include web components in Kotlin JS

Instead of the console.log to trigger the side effects I use require(...)

external fun require(module: String): dynamic

fun main() {

    require("@vaadin/vaadin-button")
    require("@vaadin/vaadin-text-field")
    require("@vaadin/vaadin-grid")
    ...
}

(credits to someone's answer on the kotlin-frontend-plugin list)

(2) I want to keep my code as typed as possible to avoid asDynamic

Instead of importing @vaadin/vaadin-grid I have to import the file which actually exposes the element. Then it seems to work and I can even add generics to my GridElement:

@file:JsModule("@vaadin/vaadin-grid/src/vaadin-grid")
@file:JsNonModule

package vaadin.grid

import org.w3c.dom.HTMLElement

abstract external class GridElement<T> : HTMLElement {
    var items: Array<out T> = definedExternally
}

This way I was able to get rid of all the asDynamics

    val firstNameField = document.querySelector("#firstName") as TextFieldElement?
    val lastNameField = document.querySelector("#lastName") as TextFieldElement?
    val addButton = document.querySelector("#addButton") as ButtonElement?
    val grid = document.querySelector("#grid") as GridElement<Person>?

    val initialPeople: Array<out Person> = emptyArray()
    grid?.items = initialPeople

    addButton?.addEventListener("click", {
        // Read the new person's data
        val person = Person(firstNameField?.value, lastNameField?.value)

        // Add it to the items
        if(grid != null){
            val people = grid.items
            grid.items = people.plus(person)
        }

        // Reset the form fields
        firstNameField?.value = ""
        lastNameField?.value = ""
    })
monk
  • 640
  • 1
  • 5
  • 17