You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by jo...@apache.org on 2021/09/24 09:04:34 UTC

[isis] 03/03: ISIS-2872 Upgrade Kotlin/KVision Dependencies

This is an automated email from the ASF dual-hosted git repository.

joergrade pushed a commit to branch ISIS-2872
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 526eee54028d36389271c2a9fba8ca02d60fded8
Author: Jörg Rade <jo...@kuehne-nagel.com>
AuthorDate: Fri Sep 24 11:01:54 2021 +0200

    ISIS-2872 Upgrade Kotlin/KVision Dependencies
---
 incubator/clients/kroviz/TODO.adoc                 |  11 ++
 incubator/clients/kroviz/build.gradle.kts          |  83 +++----------
 incubator/clients/kroviz/gradle.properties         |   8 +-
 .../kotlin/org/apache/isis/client/kroviz/App.kt    |  30 ++++-
 .../isis/client/kroviz/ui/core/MenuFactory.kt      | 102 +++++++++-------
 .../org/apache/isis/client/kroviz/ui/core/RoApp.kt |   3 +-
 .../apache/isis/client/kroviz/ui/core/RoDialog.kt  |  76 ++++++------
 .../apache/isis/client/kroviz/ui/core/RoMenuBar.kt |  67 ++++++-----
 .../isis/client/kroviz/ui/core/RoStatusBar.kt      |  31 ++---
 .../apache/isis/client/kroviz/ui/core/RoTable.kt   |   7 +-
 .../isis/client/kroviz/ui/kv/override/RoTab.kt     |  59 ++++-----
 .../client/kroviz/ui/kv/override/RoTabPanel.kt     | 132 +++++++++------------
 .../isis/client/kroviz/ui/kv/override/RoWindow.kt  |   9 +-
 .../isis/client/kroviz/ui/panel/EventLogTable.kt   |   2 -
 14 files changed, 297 insertions(+), 323 deletions(-)

diff --git a/incubator/clients/kroviz/TODO.adoc b/incubator/clients/kroviz/TODO.adoc
new file mode 100644
index 0000000..ce1e295
--- /dev/null
+++ b/incubator/clients/kroviz/TODO.adoc
@@ -0,0 +1,11 @@
+# KVision
+
+* upgrade to 5.latest, kotlin to 1.5.10
+* revert patch to build.gradle.kts
+* https://github.com/rjaros/kvision/issues/306 (select in dropdown)
+* https://github.com/rjaros/kvision/issues/247 (map to fill whole container)
+* https://github.com/rjaros/kvision/issues/299 (header footer access)
+* Remove duplicated / copied KV files
+
+# Long Term
+* use docker for integration test
diff --git a/incubator/clients/kroviz/build.gradle.kts b/incubator/clients/kroviz/build.gradle.kts
index 455b821..5652244 100644
--- a/incubator/clients/kroviz/build.gradle.kts
+++ b/incubator/clients/kroviz/build.gradle.kts
@@ -16,15 +16,15 @@
 //  specific language governing permissions and limitations
 //  under the License.
 //
-import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
-import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
 import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack
 import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
 
 plugins {
     val kotlinVersion: String by System.getProperties()
-    id("kotlinx-serialization") version kotlinVersion
+    kotlin("plugin.serialization") version kotlinVersion
     kotlin("js") version kotlinVersion
+    val kvisionVersion: String by System.getProperties()
+    id("io.kvision") version kvisionVersion
 }
 
 version = "2.0.0-SNAPSHOT"
@@ -40,6 +40,7 @@ repositories {
 val kotlinVersion: String by System.getProperties()
 val kvisionVersion: String by System.getProperties()
 
+// Custom Properties
 val webDir = file("src/main/web")
 
 kotlin {
@@ -47,16 +48,13 @@ kotlin {
         browser {
             runTask {
                 outputFileName = "main.bundle.js"
-                sourceMaps = true
+                sourceMaps = false
                 devServer = KotlinWebpackConfig.DevServer(
                     open = false,
                     port = 3000,
                     proxy = mutableMapOf(
                         "/kv/*" to "http://localhost:8080",
-                        "/kvws/*" to mapOf(
-                            "target" to "ws://localhost:8080",
-                            "ws" to true
-                        )
+                        "/kvws/*" to mapOf("target" to "ws://localhost:8080", "ws" to true)
                     ),
                     static = mutableListOf("$buildDir/processedResources/js/main")
                 )
@@ -73,15 +71,17 @@ kotlin {
         binaries.executable()
     }
     sourceSets["main"].dependencies {
+        implementation(npm("react-awesome-button", "*"))
+        implementation(npm("prop-types", "*"))
         implementation("io.kvision:kvision:$kvisionVersion")
         implementation("io.kvision:kvision-bootstrap:$kvisionVersion")
-        implementation("io.kvision:kvision-bootstrap:$kvisionVersion")
         implementation("io.kvision:kvision-bootstrap-css:$kvisionVersion")
         implementation("io.kvision:kvision-bootstrap-datetime:$kvisionVersion")
         implementation("io.kvision:kvision-bootstrap-select:$kvisionVersion")
         implementation("io.kvision:kvision-bootstrap-spinner:$kvisionVersion")
         implementation("io.kvision:kvision-bootstrap-upload:$kvisionVersion")
         implementation("io.kvision:kvision-bootstrap-dialog:$kvisionVersion")
+        implementation("io.kvision:kvision-bootstrap-typeahead:$kvisionVersion")
         implementation("io.kvision:kvision-fontawesome:$kvisionVersion")
         implementation("io.kvision:kvision-i18n:$kvisionVersion")
         implementation("io.kvision:kvision-richtext:$kvisionVersion")
@@ -90,6 +90,11 @@ kotlin {
         implementation("io.kvision:kvision-chart:$kvisionVersion")
         implementation("io.kvision:kvision-tabulator:$kvisionVersion")
         implementation("io.kvision:kvision-pace:$kvisionVersion")
+        implementation("io.kvision:kvision-toast:$kvisionVersion")
+        implementation("io.kvision:kvision-react:$kvisionVersion")
+        implementation("io.kvision:kvision-routing-navigo:$kvisionVersion")
+        implementation("io.kvision:kvision-state:$kvisionVersion")
+        implementation("io.kvision:kvision-rest:$kvisionVersion")
         implementation("io.kvision:kvision-moment:$kvisionVersion")
         implementation("io.kvision:kvision-maps:$kvisionVersion")
         implementation(npm("xmltojson", "1.3.5", false))
@@ -101,63 +106,3 @@ kotlin {
     }
     sourceSets["main"].resources.srcDir(webDir)
 }
-
-fun getNodeJsBinaryExecutable(): String {
-    val nodeDir = NodeJsRootPlugin.apply(rootProject).nodeJsSetupTaskProvider.get().destination
-    val isWindows = System.getProperty("os.name").toLowerCase().contains("windows")
-    val nodeBinDir = if (isWindows) nodeDir else nodeDir.resolve("bin")
-    val command = NodeJsRootPlugin.apply(rootProject).nodeCommand
-    val finalCommand = if (isWindows && command == "node") "node.exe" else command
-    return nodeBinDir.resolve(finalCommand).absolutePath
-}
-
-tasks {
-    create("generatePotFile", Exec::class) {
-        dependsOn("compileKotlinJs")
-        executable = getNodeJsBinaryExecutable()
-        args("${rootProject.buildDir}/js/node_modules/gettext-extract/bin/gettext-extract")
-        inputs.files(kotlin.sourceSets["main"].kotlin.files)
-        outputs.file("$projectDir/src/main/resources/i18n/messages.pot")
-    }
-}
-afterEvaluate {
-    extensions.configure<NodeJsRootExtension> {
-        versions.webpackDevServer.version = "4.0.0"
-    }
-    tasks {
-        getByName("processResources", Copy::class) {
-            dependsOn("compileKotlinJs")
-            exclude("**/*.pot")
-            doLast("Convert PO to JSON") {
-                destinationDir.walkTopDown().filter {
-                    it.isFile && it.extension == "po"
-                }.forEach {
-                    exec {
-                        executable = getNodeJsBinaryExecutable()
-                        args(
-                            "${rootProject.buildDir}/js/node_modules/gettext.js/bin/po2json",
-                            it.absolutePath,
-                            "${it.parent}/${it.nameWithoutExtension}.json"
-                        )
-                        println("Converted ${it.name} to ${it.nameWithoutExtension}.json")
-                    }
-                    it.delete()
-                }
-            }
-        }
-        create("zip", Zip::class) {
-            dependsOn("browserProductionWebpack")
-            group = "package"
-            destinationDirectory.set(file("$buildDir/libs"))
-            val distribution =
-                project.tasks.getByName("browserProductionWebpack", KotlinWebpack::class).destinationDirectory!!
-            from(distribution) {
-                include("*.*")
-            }
-            from(webDir)
-            duplicatesStrategy = DuplicatesStrategy.EXCLUDE
-            inputs.files(distribution, webDir)
-            outputs.file(archiveFile)
-        }
-    }
-}
diff --git a/incubator/clients/kroviz/gradle.properties b/incubator/clients/kroviz/gradle.properties
index 1f665df..2ecbefd 100644
--- a/incubator/clients/kroviz/gradle.properties
+++ b/incubator/clients/kroviz/gradle.properties
@@ -18,8 +18,8 @@
 #
 javaVersion=1.8
 #Plugins
-systemProp.kotlinVersion=1.5.10
-serializationVersion=1.2.1
+systemProp.kotlinVersion=1.5.31
+serializationVersion=1.2.2
 #Dependencies
-systemProp.kvisionVersion=4.8.3
-kotlin.js.compiler=legacy
+systemProp.kvisionVersion=5.1.1
+kotlin.js.compiler=ir
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/App.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/App.kt
index 34a3976..316864b 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/App.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/App.kt
@@ -18,24 +18,28 @@
  */
 package org.apache.isis.client.kroviz
 
+import io.kvision.*
 import org.apache.isis.client.kroviz.ui.core.RoApp
-import io.kvision.Application
 import io.kvision.pace.Pace
+import io.kvision.panel.ContainerType
 import io.kvision.panel.root
 import io.kvision.panel.vPanel
-import io.kvision.startApplication
 import io.kvision.utils.px
-import io.kvision.require
+import kotlinx.browser.window
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.asCoroutineDispatcher
+
+val AppScope = CoroutineScope(window.asCoroutineDispatcher())
 
 class App : Application() {
 
     init {
+        Pace.init()
         require("css/kroviz.css")
     }
 
     override fun start() {
-        Pace.init()
-        root("kroviz") {
+        root("kroviz", containerType = ContainerType.FLUID, addRow = true) {
             vPanel(spacing = 0) {
                 padding = 0.px
                 add(RoApp)
@@ -49,5 +53,19 @@ class App : Application() {
 }
 
 fun main() {
-    startApplication(::App)
+    startApplication(
+        ::App,
+        module.hot,
+        BootstrapModule,
+        BootstrapCssModule,
+        FontAwesomeModule,
+        BootstrapSelectModule,
+        BootstrapDatetimeModule,
+        BootstrapSpinnerModule,
+//        BootstrapTypeaheadModule,
+        BootstrapUploadModule,
+        RichTextModule,
+        ChartModule,
+        TabulatorModule,
+        CoreModule)
 }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/MenuFactory.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/MenuFactory.kt
index f34cc30..ef99b62 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/MenuFactory.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/MenuFactory.kt
@@ -36,17 +36,20 @@ import io.kvision.html.Link as KvisionHtmlLink
 
 object MenuFactory {
 
-    fun buildForObject(tObject: TObject,
-                       withText: Boolean = true,
-                       iconName: String = "Actions")
+    fun buildForObject(
+        tObject: TObject,
+        withText: Boolean = true,
+        iconName: String = "Actions"
+    )
             : DropDown {
         val type = tObject.domainType
         val text = if (withText) "Actions for $type" else ""
         val icon = IconManager.find(iconName)
         val dd = DropDown(
-                text = text,
-                icon = icon,
-                style = ButtonStyle.LINK)
+            text = text,
+            icon = icon,
+            style = ButtonStyle.LINK
+        )
         val actions = tObject.getActions()
         actions.forEach {
             val link = buildActionLink(it.id, text)
@@ -59,18 +62,22 @@ object MenuFactory {
         return dd
     }
 
-    fun buildForMenu(menu: Menu,
-                     style: ButtonStyle = ButtonStyle.LIGHT,
-                     withText: Boolean = true,
-                     classes: Set<String> = setOf())
+    fun buildForMenu(
+        menu: Menu,
+        style: ButtonStyle = ButtonStyle.LIGHT,
+        withText: Boolean = true,
+        classes: Set<String> = setOf()
+    )
             : DropDown {
         val menuTitle = menu.named
         val dd = DropDown(
-                text = if (withText) menuTitle else "",
-                icon = IconManager.find(menuTitle),
-                style = style,
-                classes = classes,
-                forNavbar = false)
+            text = if (withText) menuTitle else "",
+            icon = IconManager.find(menuTitle),
+            style = style,
+//                classes = classes,
+            forNavbar = false
+        )
+        dd.addCssClass(classes.toString())
         //dd.setDragDropData(Constants.stdMimeType, menuTitle)
         // action.setDragDropData gets always overridden by dd.setDragDropData
         menu.section.forEachIndexed { index, section ->
@@ -93,8 +100,9 @@ object MenuFactory {
         val menu = findMenuByTitle(title)
         return if (menu == null) null else
             buildForMenu(
-                    menu = menu,
-                    withText = false)
+                menu = menu,
+                withText = false
+            )
     }
 
     private fun findMenuByTitle(menuTitle: String): Menu? {
@@ -110,8 +118,9 @@ object MenuFactory {
     }
 
     fun buildForAction(
-            menuTitle: String,
-            actionTitle: String): KvisionHtmlLink? {
+        menuTitle: String,
+        actionTitle: String
+    ): KvisionHtmlLink? {
         val menu = findMenuByTitle(menuTitle)!!
         menu.section.forEachIndexed { _, section ->
             section.serviceAction.forEach { sa ->
@@ -129,14 +138,16 @@ object MenuFactory {
         return null
     }
 
-     fun buildActionLink(
-            label: String,
-            menuTitle: String): KvisionHtmlLink {
+    fun buildActionLink(
+        label: String,
+        menuTitle: String
+    ): KvisionHtmlLink {
         val actionTitle = StringUtils.deCamel(label)
         val actionLink: KvisionHtmlLink = ddLink(
-                label = actionTitle,
-                icon = IconManager.find(label),
-                classes = IconManager.findStyleFor(label))
+            label = actionTitle,
+            icon = IconManager.find(label),
+            classes = IconManager.findStyleFor(label)
+        )
         val id = "$menuTitle${Constants.actionSeparator}$actionTitle"
         actionLink.setDragDropData(Constants.stdMimeType, id)
         actionLink.id = id
@@ -144,33 +155,37 @@ object MenuFactory {
     }
 
     private fun ddLink(
-            label: String,
-            icon: String? = null,
-            classes: Set<String>? = null,
-            init: (KvisionHtmlLink.() -> Unit)? = null
+        label: String,
+        icon: String? = null,
+        classes: Set<String>? = null,
+        init: (KvisionHtmlLink.() -> Unit)? = null
     ): KvisionHtmlLink {
-        return KvisionHtmlLink(
-                label = label,
-                url = null,
-                icon = icon,
-                image = null,
-                separator = null,
-                labelFirst = true,
-                classes = (classes ?: null.set) + "dropdown-item").apply {
+        val link = KvisionHtmlLink(
+            label = label,
+            url = null,
+            icon = icon,
+            image = null,
+            separator = null,
+            labelFirst = true
+        )
+        link.addCssClass("dropdown-item")
+        return link.apply {
             init?.invoke(this)
         }
     }
 
     // initially added items will be enabled
     fun amendWithSaveUndo(
-            dd: DropDown,
-            tObject: TObject) {
+        dd: DropDown,
+        tObject: TObject
+    ) {
         dd.separator()
 
         val saveLink = tObject.links.first()
         val saveAction = buildActionLink(
-                label = "save",
-                menuTitle = tObject.domainType)
+            label = "save",
+            menuTitle = tObject.domainType
+        )
         saveAction.onClick {
             ResourceProxy().fetch(saveLink)
         }
@@ -178,8 +193,9 @@ object MenuFactory {
 
         val undoLink = Link(href = "")
         val undoAction = buildActionLink(
-                label = "undo",
-                menuTitle = tObject.domainType)
+            label = "undo",
+            menuTitle = tObject.domainType
+        )
         undoAction.onClick {
             ResourceProxy().fetch(undoLink)
         }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoApp.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoApp.kt
index 067db26..eb6bba1 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoApp.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoApp.kt
@@ -27,9 +27,10 @@ object RoApp : SimplePanel() {
     init {
         this.add(RoMenuBar.navbar)
 
-        val view = HPanel(classes = setOf("main")) {
+        val view = HPanel() {
             width = CssSize(100, UNIT.perc)
         }
+        view.addCssClass("main")
         view.add(RoIconBar.panel)
         view.add(RoView.tabPanel)
         this.add(view)
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoDialog.kt
index b977975..ec579c1 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoDialog.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoDialog.kt
@@ -38,49 +38,54 @@ import org.apache.isis.client.kroviz.utils.Point
 import io.kvision.html.Link as KvisionHtmlLink
 
 class RoDialog(
-        caption: String,
-        val items: List<FormItem>,
-        val command: Command,
-        defaultAction: String = "OK",
-        widthPerc: Int = 30,
-        heightPerc: Int = 100,
-        menu: List<KvisionHtmlLink>? = null,
-        customButtons: List<FormItem> = emptyList() ) :
-        Displayable, RoWindow(caption = caption, closeButton = true, menu = menu) {
+    caption: String,
+    val items: List<FormItem>,
+    val command: Command,
+    defaultAction: String = "OK",
+    widthPerc: Int = 30,
+    heightPerc: Int = 100,
+    menu: List<KvisionHtmlLink>? = null,
+    customButtons: List<FormItem> = emptyList()
+) :
+    Displayable, RoWindow(caption = caption, closeButton = true, menu = menu) {
 
     private val okButton = Button(
-            text = defaultAction,
-            icon = IconManager.find(defaultAction),
-            style = ButtonStyle.SUCCESS)
-            .onClick {
-                execute()
-            }
+        text = defaultAction,
+        icon = IconManager.find(defaultAction),
+        style = ButtonStyle.SUCCESS
+    )
+        .onClick {
+            execute()
+        }
 
     private val cancelButton = Button(
-            "Cancel",
-            "fas fa-times",
-            ButtonStyle.OUTLINEINFO)
-            .onClick {
-                close()
-            }
+        "Cancel",
+        "fas fa-times",
+        ButtonStyle.OUTLINEINFO
+    )
+        .onClick {
+            close()
+        }
 
     @Deprecated("remove once leaflet/svg is fully operational")
     private val scaleUpButton = Button(
-            "",
-            "fas fa-plus",
-            ButtonStyle.OUTLINEINFO)
-            .onClick {
-                (command as DiagramDialog).scale(Direction.UP)
-            }
+        "",
+        "fas fa-plus",
+        ButtonStyle.OUTLINEINFO
+    )
+        .onClick {
+            (command as DiagramDialog).scale(Direction.UP)
+        }
 
     @Deprecated("remove once leaflet/svg is fully operational")
     private val scaleDownButton = Button(
-            "",
-            "fas fa-minus",
-            ButtonStyle.OUTLINEINFO)
-            .onClick {
-                (command as DiagramDialog).scale(Direction.DOWN)
-            }
+        "",
+        "fas fa-minus",
+        ButtonStyle.OUTLINEINFO
+    )
+        .onClick {
+            (command as DiagramDialog).scale(Direction.DOWN)
+        }
 
     var formPanel: FormPanel<String>? = null
 
@@ -98,9 +103,8 @@ class RoDialog(
 
             add(formPanel!!, grow = 2)
 
-            val buttonBar = HPanel(
-                    spacing = 10,
-                    classes = setOf("button-bar"))
+            val buttonBar = HPanel(spacing = 10)
+            buttonBar.addCssClass("button-bar")
             buttonBar.add(okButton)
             customButtons.forEach {
                 val b = createButton(it)
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoMenuBar.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoMenuBar.kt
index ec07964..2427639 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoMenuBar.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoMenuBar.kt
@@ -59,80 +59,90 @@ object RoMenuBar : SimplePanel() {
 
     private fun buildMainMenu(): DropDown {
         return dropDown(
-                "",
-                icon = IconManager.find("Burger"),
-                forNavbar = false,
-                style = ButtonStyle.LIGHT)
+            "",
+            icon = IconManager.find("Burger"),
+            forNavbar = false,
+            style = ButtonStyle.LIGHT
+        )
         {
             ddLink(
-                    "Connect ...",
-                    icon = IconManager.find("Connect")
+                "Connect ...",
+                icon = IconManager.find("Connect")
             ).onClick { e ->
                 val at = Point(e.pageX.toInt(), e.pageY.toInt())
                 LoginPrompt().open(at)
             }
 
             val toolTitle = "Toolbar"
-            ddLink(toolTitle,
-                    icon = IconManager.find(toolTitle)
+            ddLink(
+                toolTitle,
+                icon = IconManager.find(toolTitle)
             ).onClick {
                 RoIconBar.toggle()
             }
 
             val sampleTitle = "History"
-            ddLink(sampleTitle,
-                    icon = IconManager.find(sampleTitle)
+            ddLink(
+                sampleTitle,
+                icon = IconManager.find(sampleTitle)
             ).onClick {
                 val model = EventStore.log
                 UiManager.add("Log Entries", EventLogTable(model))
             }
 
             val chartTitle = "Sample Chart"
-            ddLink(chartTitle,
-                    icon = IconManager.find("Chart")
+            ddLink(
+                chartTitle,
+                icon = IconManager.find("Chart")
             ).onClick {
                 UiManager.add(chartTitle, EventChart(SampleChartModel()))
             }
 
             val geoMapTitle = "Sample Geo Map"
-            ddLink(geoMapTitle,
-                    icon = IconManager.find("Map")
+            ddLink(
+                geoMapTitle,
+                icon = IconManager.find("Map")
             ).onClick {
                 UiManager.add(geoMapTitle, GeoMap())
             }
 
             val svgMapTitle = "Sample SVG Map"
-            ddLink(svgMapTitle,
-                    icon = IconManager.find("Diagram")
+            ddLink(
+                svgMapTitle,
+                icon = IconManager.find("Diagram")
             ).onClick {
                 UiManager.add(svgMapTitle, SvgMap())
             }
 
             val svgInlineTitle = "Sample SVG Inline (interactive)"
-            ddLink(svgInlineTitle,
-                    icon = IconManager.find("Diagram")
+            ddLink(
+                svgInlineTitle,
+                icon = IconManager.find("Diagram")
             ).onClick {
                 SvgInline().open()
             }
 
             val imageTitle = "Sample Image"
-            ddLink(imageTitle,
-                    icon = IconManager.find("Image")
+            ddLink(
+                imageTitle,
+                icon = IconManager.find("Image")
             ).onClick {
                 val panel = ImageSample()
                 RoView.addTab(imageTitle, panel)
             }
 
             val searchTitle = "Dropdown search example"
-            ddLink(searchTitle,
-                    icon = IconManager.find("Find")
+            ddLink(
+                searchTitle,
+                icon = IconManager.find("Find")
             ).onClick {
                 UiManager.add(searchTitle, DropdownSearch())
             }
 
             val aboutTitle = "About"
-            ddLink(aboutTitle,
-                    icon = IconManager.find(aboutTitle)
+            ddLink(
+                aboutTitle,
+                icon = IconManager.find(aboutTitle)
             ).onClick {
                 About().open()
             }
@@ -151,10 +161,11 @@ object RoMenuBar : SimplePanel() {
 
     private fun logoButton() {
         val classes = setOf("isis-logo-button-image", "logo-button")
-        val logo = Button("", style = ButtonStyle.LINK, classes = classes)
-                .onClick {
-                    window.open("https://isis.apache.org")
-                }
+        val logo = Button("", style = ButtonStyle.LINK)
+        logo.addCssClass(classes.toString())
+        logo.onClick {
+            window.open("https://isis.apache.org")
+        }
         nav.add(logo)
     }
 
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoStatusBar.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoStatusBar.kt
index ee5bd00..5f68f86 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoStatusBar.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoStatusBar.kt
@@ -35,9 +35,9 @@ import org.apache.isis.client.kroviz.ui.dialog.NotificationDialog
 import org.apache.isis.client.kroviz.utils.IconManager
 
 object RoStatusBar {
-    val navbar = Navbar(
-            type = NavbarType.FIXEDBOTTOM,
-            classes = setOf("status-bar"))
+    val navbar = Navbar(type = NavbarType.FIXEDBOTTOM)
+
+    //FIXME    navbar.addCssClasses("status-bar")
     private val nav = Nav(rightAlign = true)
     private val userBtn: Button = buildButton("", "Me", ButtonStyle.OUTLINEWARNING)
     private val classDiagram = buildButton("", "Diagram", ButtonStyle.OUTLINEWARNING)
@@ -46,9 +46,10 @@ object RoStatusBar {
 
     private fun buildButton(text: String, iconName: String, style: ButtonStyle): Button {
         return Button(
-                text = text,
-                icon = IconManager.find(iconName),
-                style = style).apply {
+            text = text,
+            icon = IconManager.find(iconName),
+            style = style
+        ).apply {
             padding = CssSize(-16, UNIT.px)
             margin = CssSize(0, UNIT.px)
         }
@@ -117,18 +118,20 @@ object RoStatusBar {
 
     private fun isisButton(): Button {
         val classes = setOf("isis-logo-button-image", "logo-button")
-        return Button("", style = ButtonStyle.LINK, classes = classes)
-                .onClick {
-                    window.open("https://isis.apache.org")
-                }
+        val b = Button("", style = ButtonStyle.LINK)
+        b.addCssClass(classes.toString())
+        return b.onClick {
+            window.open("https://isis.apache.org")
+        }
     }
 
     private fun kvisionButton(): Button {
         val classes = setOf("kvision-logo-button-image", "logo-button")
-        return Button("", style = ButtonStyle.LINK, classes = classes)
-                .onClick {
-                    window.open("https://kvision.io")
-                }
+        val b = Button("", style = ButtonStyle.LINK)
+        b.addCssClass(classes.toString())
+        return b.onClick {
+            window.open("https://kvision.io")
+        }
     }
 
 }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoTable.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoTable.kt
index 1dbe848..531e64d 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoTable.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoTable.kt
@@ -22,7 +22,7 @@ import io.kvision.core.Container
 import io.kvision.core.CssSize
 import io.kvision.core.UNIT
 import io.kvision.panel.SimplePanel
-import io.kvision.table.TableType
+import io.kvision.tabulator.TableType
 import io.kvision.tabulator.Layout
 import io.kvision.tabulator.Tabulator
 import io.kvision.tabulator.TabulatorOptions
@@ -59,7 +59,7 @@ class RoTable(displayCollection: CollectionDM) : SimplePanel() {
 
         tabulator(model, options = options, types = tableTypes) {
             setEventListener<Tabulator<Exposer>> {
-                tabulatorCellClick = {
+                cellClickTabulator = {
                     // can't check cast to external interface
                     val cc = it.detail as CellComponent
                     val column = cc.getColumn().getField()
@@ -96,7 +96,8 @@ class RoTable(displayCollection: CollectionDM) : SimplePanel() {
             classes: Set<String> = setOf(),
             init: (Tabulator<T>.() -> Unit)? = null
     ): Tabulator<T> {
-        val tabulator = Tabulator(data, dataUpdateOnEdit, options, types, classes)
+        val tabulator = Tabulator(data, dataUpdateOnEdit, options, types)
+        tabulator.addCssClass(classes.toString())
         init?.invoke(tabulator)
         return tabulator
     }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/kv/override/RoTab.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/kv/override/RoTab.kt
index 4b017d2..a9dbc58 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/kv/override/RoTab.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/kv/override/RoTab.kt
@@ -32,9 +32,8 @@ import io.kvision.html.Icon
 import io.kvision.html.Link
 import io.kvision.html.TAG
 import io.kvision.html.Tag
+import io.kvision.panel.TabPanel
 import io.kvision.routing.RoutingManager
-import io.kvision.state.ObservableState
-import io.kvision.state.bind
 import io.kvision.utils.obj
 import org.apache.isis.client.kroviz.utils.DomUtil
 import org.apache.isis.client.kroviz.utils.ScalableVectorGraphic
@@ -51,22 +50,19 @@ import org.apache.isis.client.kroviz.utils.ScalableVectorGraphic
  * @param init an initializer extension function
  */
 open class RoTab(
+    label: String? = null, icon: String? = null,
+    image: ResString? = null, closable: Boolean = false, val route: String? = null,
+    init: (RoTab.() -> Unit)? = null
+) : Tag(TAG.LI, className = "nav-item") {
+
+    constructor(
         label: String? = null,
+        child: Component,
         icon: String? = null,
         image: ResString? = null,
         closable: Boolean = false,
-        val route: String? = null,
+        route: String? = null,
         init: (RoTab.() -> Unit)? = null
-) : Tag(TAG.LI, classes = setOf("nav-item")) {
-
-    constructor(
-            label: String? = null,
-            child: Component,
-            icon: String? = null,
-            image: ResString? = null,
-            closable: Boolean = false,
-            route: String? = null,
-            init: (RoTab.() -> Unit)? = null
     ) : this(label, icon, image, closable, route, init) {
         @Suppress("LeakingThis")
         add(child)
@@ -115,20 +111,20 @@ open class RoTab(
             closeIcon.visible = value
         }
 
-    internal val closeIcon = Icon("fas fa-times").apply {
+    protected val closeIcon = Icon("fas fa-times").apply {
         addCssClass("kv-tab-close")
         visible = closable
         setEventListener<Icon> {
             click = { e ->
-                val tabPanel = (this@RoTab.parent as? RoTabPanelNav)?.tabPanel
+                val tabPanel = (this@RoTab.parent as? RoTabPanel.TabPanelNav)?.tabPanel
                 val actIndex = tabPanel?.getTabIndex(this@RoTab) ?: -1
                 e.asDynamic().data = actIndex
                 @Suppress("UnsafeCastFromDynamic")
-                val event = org.w3c.dom.CustomEvent("tabClosing", obj { detail = e; cancelable = true })
+                val event = org.w3c.dom.CustomEvent("closingTab", obj { detail = e; cancelable = true })
                 if (tabPanel?.getElement()?.dispatchEvent(event) != false) {
                     tabPanel?.removeTab(actIndex)
                     @Suppress("UnsafeCastFromDynamic")
-                    val closed = org.w3c.dom.CustomEvent("tabClosed", obj { detail = e })
+                    val closed = org.w3c.dom.CustomEvent("closedTab", obj { detail = e })
                     tabPanel?.getElement()?.dispatchEvent(closed)
                 }
                 e.stopPropagation()
@@ -139,20 +135,20 @@ open class RoTab(
     /**
      * A link component within the tab.
      */
-    val link = Link(label ?: "", "#", icon, image, classes = setOf("nav-link")).apply {
+    val link = Link(label ?: "", "#", icon, image, className = "nav-link").apply {
         add(this@RoTab.closeIcon)
     }
 
     internal val tabId = counter++
 
     protected val routingHandler = { _: Any ->
-        (this@RoTab.parent as? RoTabPanelNav)?.tabPanel?.activeTab = this
+        (this@RoTab.parent as? RoTabPanel.TabPanelNav)?.tabPanel?.activeTab = this
     }
 
     init {
         addPrivate(link)
         onClick { e ->
-            (this@RoTab.parent as? RoTabPanelNav)?.tabPanel?.activeTab = this
+            (this@RoTab.parent as? RoTabPanel.TabPanelNav)?.tabPanel?.activeTab = this
             e.preventDefault()
             if (route != null) {
                 RoutingManager.getRouter().kvNavigate(route)
@@ -168,7 +164,7 @@ open class RoTab(
     }
 
     override fun childrenVNodes(): Array<VNode> {
-        return (privateChildren).filter { it.visible }.map { it.renderVNode() }.toTypedArray()
+        return (privateChildren!!).filter { it.visible }.map { it.renderVNode() }.toTypedArray()
     }
 
     override fun dispose() {
@@ -186,25 +182,12 @@ open class RoTab(
  *
  * It takes the same parameters as the constructor of the built component.
  */
-fun RoTabPanel.tab(
-        label: String? = null, icon: String? = null,
-        image: ResString? = null, closable: Boolean = false, route: String? = null,
-        init: (RoTab.() -> Unit)? = null
+fun TabPanel.tab(
+    label: String? = null, icon: String? = null,
+    image: ResString? = null, closable: Boolean = false, route: String? = null,
+    init: (RoTab.() -> Unit)? = null
 ): RoTab {
     val tab = RoTab(label, icon, image, closable, route, init)
     this.add(tab)
     return tab
 }
-
-/**
- * DSL builder extension function for observable state.
- *
- * It takes the same parameters as the constructor of the built component.
- */
-fun <S> RoTabPanel.tab(
-        state: ObservableState<S>,
-        label: String? = null, icon: String? = null,
-        image: ResString? = null, closable: Boolean = false, route: String? = null,
-        init: (RoTab.(S) -> Unit)
-) = tab(label, icon, image, closable, route).bind(state, true, init)
-
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/kv/override/RoTabPanel.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/kv/override/RoTabPanel.kt
index 9ca5846..bb1390f 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/kv/override/RoTabPanel.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/kv/override/RoTabPanel.kt
@@ -26,13 +26,11 @@ package org.apache.isis.client.kroviz.ui.kv.override
 import com.github.snabbdom.VNode
 import io.kvision.core.*
 import io.kvision.panel.SimplePanel
+import io.kvision.panel.Tab
 import io.kvision.panel.VPanel
 import io.kvision.routing.RoutingManager
-import io.kvision.state.ObservableState
-import io.kvision.state.bind
 import io.kvision.utils.auto
 import io.kvision.utils.obj
-import io.kvision.utils.set
 import org.apache.isis.client.kroviz.ui.core.RoView
 
 /**
@@ -66,29 +64,29 @@ enum class SideTabSize {
  * @param sideTabSize side tab size
  * @param scrollableTabs determines if tabs are scrollable (default: false)
  * @param draggableTabs determines if tabs are draggable (default: false)
- * @param classes a set of CSS class names
+ * @param className CSS class names
  * @param init an initializer extension function
  */
 @Suppress("LeakingThis")
 open class RoTabPanel(
-        private val tabPosition: TabPosition = TabPosition.TOP,
-        private val sideTabSize: SideTabSize = SideTabSize.SIZE_3,
-        val scrollableTabs: Boolean = false,
-        val draggableTabs: Boolean = false,
-        classes: Set<String> = setOf(),
-        init: (RoTabPanel.() -> Unit)? = null
-) : SimplePanel(classes) {
-
-    private val navClasses = when (tabPosition) {
-        TabPosition.TOP -> if (scrollableTabs) setOf("nav", "nav-tabs", "tabs-top") else setOf("nav", "nav-tabs")
-        TabPosition.LEFT -> setOf("nav", "nav-tabs", "tabs-left", "flex-column")
-        TabPosition.RIGHT -> setOf("nav", "nav-tabs", "tabs-right", "flex-column")
+    protected val tabPosition: TabPosition = TabPosition.TOP,
+    protected val sideTabSize: SideTabSize = SideTabSize.SIZE_3,
+    val scrollableTabs: Boolean = false,
+    val draggableTabs: Boolean = false,
+    className: String? = null,
+    init: (RoTabPanel.() -> Unit)? = null
+) : SimplePanel((className?.let { "$it " } ?: "") + "kv-tab-panel") {
+
+    protected val navClasses = when (tabPosition) {
+        TabPosition.TOP -> if (scrollableTabs) "nav nav-tabs tabs-top" else "nav nav-tabs"
+        TabPosition.LEFT -> "nav nav-tabs tabs-left flex-column"
+        TabPosition.RIGHT -> "nav nav-tabs tabs-right flex-column"
     }
 
     internal val tabs = mutableListOf<RoTab>()
 
-    private val nav = RoTabPanelNav(this, navClasses)
-    private val content = RoTabPanelContent(this)
+    private val nav = TabPanelNav(this, navClasses)
+    private val content = TabPanelContent(this)
 
     /**
      * The index of the active tab.
@@ -102,7 +100,7 @@ open class RoTabPanel(
                 }
                 tabs.getOrNull(value)?.link?.addCssClass("active")
                 @Suppress("UnsafeCastFromDynamic")
-                this.dispatchEvent("tabChange", obj { detail = obj { data = value } })
+                this.dispatchEvent("changeTab", obj { detail = obj { data = value } })
             }
         }
 
@@ -116,6 +114,7 @@ open class RoTabPanel(
         }
 
     init {
+        //TODO to be set by caller
         width = auto
         marginTop = CssSize(40, UNIT.px)
         when (tabPosition) {
@@ -127,25 +126,21 @@ open class RoTabPanel(
                 this.addSurroundingCssClass("container-fluid")
                 this.addCssClass("row")
                 val sizes = calculateSideClasses()
-                this.addPrivate(WidgetWrapper(nav, setOf(sizes.first, "pl-0", "pr-0")))
-                this.addPrivate(WidgetWrapper(content, setOf(sizes.second, "pl-0", "pr-0")))
+                this.addPrivate(WidgetWrapper(nav, "${sizes.first} ps-0 pe-0"))
+                this.addPrivate(WidgetWrapper(content, "${sizes.second} ps-0 pe-0"))
             }
             TabPosition.RIGHT -> {
                 this.addSurroundingCssClass("container-fluid")
                 this.addCssClass("row")
                 val sizes = calculateSideClasses()
-                this.addPrivate(WidgetWrapper(content, setOf(sizes.second, "pl-0", "pr-0")))
-                this.addPrivate(WidgetWrapper(nav, setOf(sizes.first, "pl-0", "pr-0")))
+                this.addPrivate(WidgetWrapper(content, "${sizes.second} ps-0 pe-0"))
+                this.addPrivate(WidgetWrapper(nav, "${sizes.first} ps-0 pe-0"))
             }
         }
         init?.invoke(this)
     }
 
-    override fun render(): VNode {
-        return render("div", childrenVNodes())
-    }
-
-    private fun calculateSideClasses(): Pair<String, String> {
+    protected fun calculateSideClasses(): Pair<String, String> {
         return when (sideTabSize) {
             SideTabSize.SIZE_1 -> Pair("col-sm-1", "col-sm-11")
             SideTabSize.SIZE_2 -> Pair("col-sm-2", "col-sm-10")
@@ -379,6 +374,33 @@ open class RoTabPanel(
         return null
     }
 
+    /**
+     * A helper component for rendering tabs.
+     */
+    class TabPanelNav(internal val tabPanel: RoTabPanel, className: String) : SimplePanel(className) {
+
+        override fun render(): VNode {
+            return render("ul", childrenVNodes())
+        }
+
+        override fun childrenVNodes(): Array<VNode> {
+            return tabPanel.tabs.filter { it.visible }.map { it.renderVNode() }.toTypedArray()
+        }
+
+    }
+
+    /**
+     * A helper component for rendering tab content.
+     */
+    class TabPanelContent(private val tabPanel: RoTabPanel) : SimplePanel() {
+
+        override fun childrenVNodes(): Array<VNode> {
+            return tabPanel.tabs.getOrNull(tabPanel.activeIndex)?.getChildren()?.map { it.renderVNode() }
+                ?.toTypedArray()
+                ?: emptyArray()
+        }
+
+    }
 }
 
 /**
@@ -387,54 +409,14 @@ open class RoTabPanel(
  * It takes the same parameters as the constructor of the built component.
  */
 fun Container.tabPanel(
-        tabPosition: TabPosition = TabPosition.TOP,
-        sideTabSize: SideTabSize = SideTabSize.SIZE_3,
-        scrollableTabs: Boolean = false,
-        draggableTabs: Boolean = false,
-        classes: Set<String>? = null,
-        className: String? = null,
-        init: (RoTabPanel.() -> Unit)? = null
+    tabPosition: TabPosition = TabPosition.TOP,
+    sideTabSize: SideTabSize = SideTabSize.SIZE_3,
+    scrollableTabs: Boolean = false,
+    draggableTabs: Boolean = false,
+    className: String? = null,
+    init: (RoTabPanel.() -> Unit)? = null
 ): RoTabPanel {
-    val tabPanel = RoTabPanel(tabPosition, sideTabSize, scrollableTabs, draggableTabs, classes ?: className.set, init)
+    val tabPanel = RoTabPanel(tabPosition, sideTabSize, scrollableTabs, draggableTabs, className, init)
     this.add(tabPanel)
     return tabPanel
 }
-
-/**
- * DSL builder extension function for observable state.
- *
- * It takes the same parameters as the constructor of the built component.
- */
-fun <S> Container.tabPanel(
-        state: ObservableState<S>,
-        tabPosition: TabPosition = TabPosition.TOP,
-        sideTabSize: SideTabSize = SideTabSize.SIZE_3,
-        scrollableTabs: Boolean = false,
-        draggableTabs: Boolean = false,
-        classes: Set<String>? = null,
-        className: String? = null,
-        init: (RoTabPanel.(S) -> Unit)
-) = tabPanel(tabPosition, sideTabSize, scrollableTabs, draggableTabs, classes, className).bind(state, true, init)
-
-
-internal class RoTabPanelNav(internal val tabPanel: RoTabPanel, classes: Set<String>) : SimplePanel(classes) {
-
-    override fun render(): VNode {
-        return render("ul", childrenVNodes())
-    }
-
-    override fun childrenVNodes(): Array<VNode> {
-        return tabPanel.tabs.filter { it.visible }.map { it.renderVNode() }.toTypedArray()
-    }
-
-}
-
-internal class RoTabPanelContent(private val tabPanel: RoTabPanel) : SimplePanel() {
-
-    override fun childrenVNodes(): Array<VNode> {
-        return tabPanel.tabs.getOrNull(tabPanel.activeIndex)?.getChildren()?.map { it.renderVNode() }?.toTypedArray()
-                ?: emptyArray()
-    }
-
-}
-
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/kv/override/RoWindow.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/kv/override/RoWindow.kt
index 5f5d1f1..146cbbf 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/kv/override/RoWindow.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/kv/override/RoWindow.kt
@@ -71,7 +71,7 @@ open class RoWindow(
         menu: List<KvisionHtmlLink>? = null,
         init: (RoWindow.() -> Unit)? = null
 ) :
-        SimplePanel(classes + setOf("modal-content", "kv-window")) {
+    SimplePanel(classes.toString() +" modal-content kv-window") {
 
     /**
      * Window caption text.
@@ -160,7 +160,7 @@ open class RoWindow(
             windowIcon.visible = (value != null && value != "")
         }
 
-    private val header = SimplePanel(setOf("modal-header"))
+    private val header = SimplePanel("modal-header")
 
     /**
      * @suppress
@@ -173,10 +173,11 @@ open class RoWindow(
     private val closeIcon = CloseIcon()
     private val maximizeIcon = MaximizeIcon()
     private val minimizeIcon = MinimizeIcon()
-    private val captionTag = Tag(TAG.H5, caption, classes = setOf("modal-title")).apply {
+    private val captionTag = Tag(TAG.H5, caption).apply {
+        addCssClass("modal-title")
         alignSelf = AlignItems.START
     }
-    private val iconsContainer = SimplePanel(setOf("kv-window-icons-container"))
+    private val iconsContainer = SimplePanel("kv-window-icons-container")
 
     private val windowIcon = Icon(icon ?: "").apply {
         addCssClass("window-icon")
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/EventLogTable.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/EventLogTable.kt
index 12885e8..db3505b 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/EventLogTable.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/EventLogTable.kt
@@ -154,8 +154,6 @@ class EventLogTable(val model: List<LogEntry>) : VPanel() {
 
         tabulator = tabulator(model, options = options) {
             setEventListener<Tabulator<LogEntry>> {
-                tabulatorRowClick = {
-                }
             }
         }
     }