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

1 Answer 1

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 = ""
    })
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.