aboutsummaryrefslogtreecommitdiff
path: root/kvision-modules
diff options
context:
space:
mode:
Diffstat (limited to 'kvision-modules')
-rw-r--r--kvision-modules/kvision-onsenui-css/build.gradle.kts48
-rw-r--r--kvision-modules/kvision-onsenui-css/src/main/kotlin/pl/treksoft/kvision/KVManagerOnsenuiCss.kt38
-rw-r--r--kvision-modules/kvision-onsenui-css/webpack.config.d/css.js2
-rw-r--r--kvision-modules/kvision-onsenui-css/webpack.config.d/file.js9
-rw-r--r--kvision-modules/kvision-onsenui-css/webpack.config.d/fonts.js3
-rw-r--r--kvision-modules/kvision-onsenui/build.gradle.kts48
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/KVManagerOnsenui.kt34
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/OnsenUi.kt359
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/core/BackButton.kt115
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/core/Navigator.kt421
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/core/Page.kt302
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/splitter/Splitter.kt141
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/splitter/SplitterContent.kt113
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/splitter/SplitterSide.kt298
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/tabbar/Tab.kt163
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/tabbar/Tabbar.kt268
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/toolbar/BottomToolbar.kt79
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/toolbar/Toolbar.kt172
-rw-r--r--kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/toolbar/ToolbarButton.kt130
-rw-r--r--kvision-modules/kvision-onsenui/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt100
-rw-r--r--kvision-modules/kvision-onsenui/webpack.config.d/css.js2
-rw-r--r--kvision-modules/kvision-onsenui/webpack.config.d/file.js9
-rw-r--r--kvision-modules/kvision-onsenui/webpack.config.d/fonts.js3
23 files changed, 2857 insertions, 0 deletions
diff --git a/kvision-modules/kvision-onsenui-css/build.gradle.kts b/kvision-modules/kvision-onsenui-css/build.gradle.kts
new file mode 100644
index 00000000..04ca8d8b
--- /dev/null
+++ b/kvision-modules/kvision-onsenui-css/build.gradle.kts
@@ -0,0 +1,48 @@
+buildscript {
+ extra.set("production", (findProperty("prod") ?: findProperty("production") ?: "false") == "true")
+}
+
+plugins {
+ kotlin("js")
+ id("maven-publish")
+}
+
+repositories()
+
+kotlin {
+ kotlinJsTargets()
+}
+
+dependencies {
+ implementation(kotlin("stdlib-js"))
+ api(rootProject)
+ implementation(npm("onsenui", "^2.10.10"))
+ testImplementation(kotlin("test-js"))
+}
+
+val sourcesJar by tasks.registering(Jar::class) {
+ archiveClassifier.set("sources")
+ from(kotlin.sourceSets.main.get().kotlin)
+}
+
+publishing {
+ publications {
+ create<MavenPublication>("kotlin") {
+ from(components["kotlin"])
+ artifact(tasks["sourcesJar"])
+ pom {
+ defaultPom()
+ }
+ }
+ }
+}
+
+setupPublication()
+
+tasks {
+ getByName("JsJar", Jar::class) {
+ from("${rootProject.buildDir}/js/packages/kvision-${project.name}/package.json") {
+ filter { it.replace("\"main\": \"kotlin/kvision-kvision", "\"main\": \"kvision-kvision") }
+ }
+ }
+}
diff --git a/kvision-modules/kvision-onsenui-css/src/main/kotlin/pl/treksoft/kvision/KVManagerOnsenuiCss.kt b/kvision-modules/kvision-onsenui-css/src/main/kotlin/pl/treksoft/kvision/KVManagerOnsenuiCss.kt
new file mode 100644
index 00000000..160b48e0
--- /dev/null
+++ b/kvision-modules/kvision-onsenui-css/src/main/kotlin/pl/treksoft/kvision/KVManagerOnsenuiCss.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision
+
+internal val kVManagerOnsenuiCssInit = KVManagerOnsenuiCss.init()
+
+/**
+ * Internal singleton object which initializes and configures KVision OnsenUI CSS module.
+ */
+internal object KVManagerOnsenuiCss {
+ init {
+ require("onsenui/css/ionicons/css/ionicons.min.css")
+ require("onsenui/css/material-design-iconic-font/css/material-design-iconic-font.min.css")
+ require("onsenui/css/onsenui-core.min.css")
+ require("onsenui/css/onsen-css-components.min.css")
+ }
+
+ internal fun init() {}
+}
diff --git a/kvision-modules/kvision-onsenui-css/webpack.config.d/css.js b/kvision-modules/kvision-onsenui-css/webpack.config.d/css.js
new file mode 100644
index 00000000..5d710d35
--- /dev/null
+++ b/kvision-modules/kvision-onsenui-css/webpack.config.d/css.js
@@ -0,0 +1,2 @@
+config.module.rules.push({ test: /\.css$/, loader: "style-loader!css-loader" });
+
diff --git a/kvision-modules/kvision-onsenui-css/webpack.config.d/file.js b/kvision-modules/kvision-onsenui-css/webpack.config.d/file.js
new file mode 100644
index 00000000..653ca21f
--- /dev/null
+++ b/kvision-modules/kvision-onsenui-css/webpack.config.d/file.js
@@ -0,0 +1,9 @@
+config.module.rules.push(
+ {
+ test: /\.(jpe?g|png|gif|svg)$/i,
+ loader: 'file-loader',
+ options: {
+ esModule: false,
+ },
+ }
+);
diff --git a/kvision-modules/kvision-onsenui-css/webpack.config.d/fonts.js b/kvision-modules/kvision-onsenui-css/webpack.config.d/fonts.js
new file mode 100644
index 00000000..35b28e6a
--- /dev/null
+++ b/kvision-modules/kvision-onsenui-css/webpack.config.d/fonts.js
@@ -0,0 +1,3 @@
+config.module.rules.push({test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'});
+config.module.rules.push({test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream'});
+config.module.rules.push({test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'});
diff --git a/kvision-modules/kvision-onsenui/build.gradle.kts b/kvision-modules/kvision-onsenui/build.gradle.kts
new file mode 100644
index 00000000..04ca8d8b
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/build.gradle.kts
@@ -0,0 +1,48 @@
+buildscript {
+ extra.set("production", (findProperty("prod") ?: findProperty("production") ?: "false") == "true")
+}
+
+plugins {
+ kotlin("js")
+ id("maven-publish")
+}
+
+repositories()
+
+kotlin {
+ kotlinJsTargets()
+}
+
+dependencies {
+ implementation(kotlin("stdlib-js"))
+ api(rootProject)
+ implementation(npm("onsenui", "^2.10.10"))
+ testImplementation(kotlin("test-js"))
+}
+
+val sourcesJar by tasks.registering(Jar::class) {
+ archiveClassifier.set("sources")
+ from(kotlin.sourceSets.main.get().kotlin)
+}
+
+publishing {
+ publications {
+ create<MavenPublication>("kotlin") {
+ from(components["kotlin"])
+ artifact(tasks["sourcesJar"])
+ pom {
+ defaultPom()
+ }
+ }
+ }
+}
+
+setupPublication()
+
+tasks {
+ getByName("JsJar", Jar::class) {
+ from("${rootProject.buildDir}/js/packages/kvision-${project.name}/package.json") {
+ filter { it.replace("\"main\": \"kotlin/kvision-kvision", "\"main\": \"kvision-kvision") }
+ }
+ }
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/KVManagerOnsenui.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/KVManagerOnsenui.kt
new file mode 100644
index 00000000..d0d184ab
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/KVManagerOnsenui.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision
+
+internal val kVManagerOnsenuiInit = KVManagerOnsenui.init()
+
+/**
+ * Internal singleton object which initializes and configures KVision OnsenUI module.
+ */
+internal object KVManagerOnsenui {
+
+ internal val ons = require("onsenui/js/onsenui.min.js")
+
+ internal fun init() {}
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/OnsenUi.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/OnsenUi.kt
new file mode 100644
index 00000000..9cbc960e
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/OnsenUi.kt
@@ -0,0 +1,359 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.onsenui
+
+import pl.treksoft.kvision.KVManagerOnsenui.ons
+
+/**
+ * Back button event type.
+ */
+external interface BackButtonEvent {
+ fun callParentHandler()
+}
+
+/**
+ * Change orientation event event type.
+ */
+external interface OrientationEvent {
+ val isPortrait: Boolean
+}
+
+/**
+ * Platform types.
+ */
+enum class Platform(internal val type: String) {
+ OPERA("opera"),
+ FIREFOX("firefox"),
+ SAFARI("safari"),
+ CHROME("chrome"),
+ IE("ie"),
+ EDGE("edge"),
+ ANDROID("android"),
+ BLACKBERRY("blackberry"),
+ IOS("ios"),
+ WP("wp")
+}
+
+/**
+ * Contains global Onsen UI functions.
+ */
+@Suppress("UnsafeCastFromDynamic")
+object OnsenUi {
+
+ /**
+ * Whether OnsenUI engine is loaded and ready.
+ */
+ fun isReady(): Boolean {
+ return ons.isReady()
+ }
+
+ /**
+ * Whether app is running inside Cordova.
+ */
+ fun isWebView(): Boolean {
+ return ons.isWebView()
+ }
+
+ /**
+ * A function called when engine is loaded and ready.
+ */
+ fun ready(callback: () -> Unit) {
+ ons.ready(callback)
+ }
+
+ /**
+ * Set default listener for the device back button event.
+ */
+ fun setDefaultDeviceBackButtonListener(listener: (event: dynamic) -> Unit) {
+ ons.setDefaultDeviceBackButtonListener(listener)
+ }
+
+ /**
+ * Disable default handler for the device back button.
+ */
+ fun disableDeviceBackButtonHandler() {
+ ons.disableDeviceBackButtonHandler()
+ }
+
+ /**
+ * Enable default handler for the device back button.
+ */
+ fun enableDeviceBackButtonHandler() {
+ ons.enableDeviceBackButtonHandler()
+ }
+
+ /**
+ * Enable status bar fill on IOS7 and above (except iPhone X)
+ */
+ fun enableAutoStatusBarFill() {
+ ons.enableAutoStatusBarFill()
+ }
+
+ /**
+ * Disable status bar fill on IOS7 and above (except iPhone X)
+ */
+ fun disableAutoStatusBarFill() {
+ ons.disableAutoStatusBarFill()
+ }
+
+ /**
+ * Creates a static element similar to iOS/Android status bar.
+ */
+ fun mockStatusBar() {
+ ons.mockStatusBar()
+ }
+
+ /**
+ * Disable all animations.
+ */
+ fun disableAnimations() {
+ ons.disableAnimations()
+ }
+
+ /**
+ * Enable all animations.
+ */
+ fun enableAnimations() {
+ ons.enableAnimations()
+ }
+
+ /**
+ * Disable automatic styling.
+ */
+ fun disableAutoStyling() {
+ ons.disableAutoStyling()
+ }
+
+ /**
+ * Enable automatic styling.
+ */
+ fun enableAutoStyling() {
+ ons.enableAutoStyling()
+ }
+
+ /**
+ * Disable adding fa- prefix for icons.
+ */
+ fun disableIconAutoPrefix() {
+ ons.disableIconAutoPrefix()
+ }
+
+ /**
+ * Applies a scroll fix for iOS UIWebView.
+ */
+ fun forceUIWebViewScrollFix(force: Boolean) {
+ ons.forceUIWebViewScrollFix(force)
+ }
+
+ /**
+ * Force styling for the given platform.
+ */
+ fun forcePlatformStyling(platform: Platform) {
+ ons.forcePlatformStyling(platform.type)
+ }
+
+ /**
+ * Set styling for the given platform (preferred)
+ */
+ fun platformSelect(platform: Platform) {
+ ons.platform.select(platform.type)
+ }
+
+ /**
+ * Whether device is iPhone.
+ * @param forceActualPlatform return the actual platform.
+ */
+ fun isIOS(forceActualPlatform: Boolean = false): Boolean {
+ return ons.platform.isIOS(forceActualPlatform)
+ }
+
+ /**
+ * Whether device is Android.
+ * @param forceActualPlatform return the actual platform.
+ */
+ fun isAndroid(forceActualPlatform: Boolean = false): Boolean {
+ return ons.platform.isAndroid(forceActualPlatform)
+ }
+
+ /**
+ * Whether device is Android phone.
+ */
+ fun isAndroidPhone(): Boolean {
+ return ons.platform.isAndroidPhone()
+ }
+
+ /**
+ * Whether device is Android tablet.
+ */
+ fun isAndroidTablet(): Boolean {
+ return ons.platform.isAndroidTablet()
+ }
+
+ /**
+ * Whether device is UIWebView.
+ */
+ fun isUIWebView(): Boolean {
+ return ons.platform.isUIWebView()
+ }
+
+ /**
+ * Whether device is iOS Safari.
+ */
+ fun isIOSSafari(): Boolean {
+ return ons.platform.isIOSSafari()
+ }
+
+ /**
+ * Whether device is WKWebView.
+ */
+ fun isWKWebView(): Boolean {
+ return ons.platform.isWKWebView()
+ }
+
+ /**
+ * Whether device is iPhone.
+ */
+ fun isIPhone(): Boolean {
+ return ons.platform.isIPhone()
+ }
+
+ /**
+ * Whether device is iPhone X, XS, XS Max, or XR.
+ */
+ fun isIPhoneX(): Boolean {
+ return ons.platform.isIPhoneX()
+ }
+
+ /**
+ * Whether device is iPad.
+ */
+ fun isIPad(): Boolean {
+ return ons.platform.isIPad()
+ }
+
+ /**
+ * Whether device is BlackBerry.
+ * @param forceActualPlatform return the actual platform.
+ */
+ fun isBlackBerry(forceActualPlatform: Boolean = false): Boolean {
+ return ons.platform.isBlackBerry(forceActualPlatform)
+ }
+
+ /**
+ * Whether browser is Opera.
+ * @param forceActualPlatform return the actual platform.
+ */
+ fun isOpera(forceActualPlatform: Boolean = false): Boolean {
+ return ons.platform.isOpera(forceActualPlatform)
+ }
+
+ /**
+ * Whether browser is Firefox.
+ * @param forceActualPlatform return the actual platform.
+ */
+ fun isFirefox(forceActualPlatform: Boolean = false): Boolean {
+ return ons.platform.isFirefox(forceActualPlatform)
+ }
+
+ /**
+ * Whether browser is Safari.
+ * @param forceActualPlatform return the actual platform.
+ */
+ fun isSafari(forceActualPlatform: Boolean = false): Boolean {
+ return ons.platform.isSafari(forceActualPlatform)
+ }
+
+ /**
+ * Whether browser is Chrome.
+ * @param forceActualPlatform return the actual platform.
+ */
+ fun isChrome(forceActualPlatform: Boolean = false): Boolean {
+ return ons.platform.isChrome(forceActualPlatform)
+ }
+
+ /**
+ * Whether browser is IE.
+ * @param forceActualPlatform return the actual platform.
+ */
+ fun isIE(forceActualPlatform: Boolean = false): Boolean {
+ return ons.platform.isIE(forceActualPlatform)
+ }
+
+ /**
+ * Whether device is iOS 7 or above.
+ */
+ fun isIOS7above(): Boolean {
+ return ons.platform.isIOS7above()
+ }
+
+ /**
+ * Whether browser is Edge.
+ * @param forceActualPlatform return the actual platform.
+ */
+ fun isEdge(forceActualPlatform: Boolean = false): Boolean {
+ return ons.platform.isEdge(forceActualPlatform)
+ }
+
+ /**
+ * Whether device is Windows Phone.
+ * @param forceActualPlatform return the actual platform.
+ */
+ fun isWP(forceActualPlatform: Boolean = false): Boolean {
+ return ons.platform.isWP(forceActualPlatform)
+ }
+
+ /**
+ * Function called on orientation change event.
+ */
+ fun onOrientationChange(callback: (OrientationEvent) -> Unit) {
+ ons.orientation.on("change", callback)
+ }
+
+ /**
+ * Function called on a single orientation change event.
+ */
+ fun onOrientationChangeOnce(callback: (OrientationEvent) -> Unit) {
+ ons.orientation.once("change", callback)
+ }
+
+ /**
+ * Remove orientation change event listener/all listeners.
+ */
+ fun offOrientationChange(callback: ((OrientationEvent) -> Unit)? = undefined) {
+ ons.orientation.off("change", callback)
+ }
+
+ /**
+ * Whether orientation is portrait.
+ */
+ fun isPortrait(): Boolean {
+ return ons.orientation.isPortrait()
+ }
+
+ /**
+ * Whether orientation is landscape.
+ */
+ fun isLandscape(): Boolean {
+ return ons.orientation.isLandscape()
+ }
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/core/BackButton.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/core/BackButton.kt
new file mode 100644
index 00000000..0832e74e
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/core/BackButton.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.onsenui.core
+
+import com.github.snabbdom.VNode
+import org.w3c.dom.events.MouseEvent
+import pl.treksoft.kvision.core.Component
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.html.Align
+import pl.treksoft.kvision.html.Div
+import pl.treksoft.kvision.utils.set
+
+/**
+ * A back button component designed to be placed inside the toolbar.
+ *
+ * @constructor Creates a back button component.
+ * @param content the content of the button.
+ * @param rich whether [content] can contain HTML code
+ * @param align text align
+ * @param classes a set of CSS class names
+ * @param init an initializer extension function
+ */
+@Suppress("LeakingThis")
+open class BackButton(
+ content: String? = null,
+ rich: Boolean = false,
+ align: Align? = null,
+ classes: Set<String> = setOf(),
+ init: (BackButton.() -> Unit)? = null
+) : Div(content, rich, align, classes) {
+
+ /**
+ * A modifier attribute to specify custom styles.
+ */
+ var modifier: String? by refreshOnUpdate()
+
+ init {
+ init?.invoke(this)
+ }
+
+ override fun render(elementName: String, children: Array<dynamic>): VNode {
+ return super.render("ons-back-button", children)
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ modifier?.let {
+ sn.add("modifier" to it)
+ }
+ return sn
+ }
+
+ override fun afterInsert(node: VNode) {
+ getElement()?.asDynamic()?.onClick = {
+ var component: Component? = this
+ while (component != null) {
+ component = component.parent
+ if (component is Navigator) {
+ component.popPage()
+ break
+ }
+ }
+ }
+ }
+
+ /**
+ * A convenient helper for easy setting onClick event handler.
+ */
+ open fun onClick(handler: BackButton.(MouseEvent) -> Unit): BackButton {
+ this.setEventListener<BackButton> {
+ click = { e ->
+ self.handler(e)
+ }
+ }
+ return this
+ }
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Div.backButton(
+ content: String? = null,
+ rich: Boolean = false,
+ align: Align? = null,
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (BackButton.() -> Unit)? = null
+): BackButton {
+ val backButton = BackButton(content, rich, align, classes ?: className.set, init)
+ this.add(backButton)
+ return backButton
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/core/Navigator.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/core/Navigator.kt
new file mode 100644
index 00000000..fdd7ad8c
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/core/Navigator.kt
@@ -0,0 +1,421 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.onsenui.core
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.KVManagerOnsenui.ons
+import pl.treksoft.kvision.core.Display
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.onsenui.BackButtonEvent
+import pl.treksoft.kvision.onsenui.splitter.SplitterContent
+import pl.treksoft.kvision.onsenui.tabbar.Tab
+import pl.treksoft.kvision.panel.Root
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.createInstance
+import pl.treksoft.kvision.utils.obj
+import pl.treksoft.kvision.utils.set
+import kotlin.js.Promise
+
+/**
+ * Navigator animation types.
+ */
+enum class NavAnimation(internal val type: String) {
+ NONE("none"),
+ FADE("fade"),
+ LIFT("lift"),
+ SLIDE("slide"),
+ FADEMD("fade-md"),
+ LIFTMD("lift-md"),
+ SLIDEMD("slide-md"),
+ FADEIOS("fade-ios"),
+ LIFTIOS("lift-ios"),
+ SLIDEIOS("slide-ios")
+}
+
+/**
+ * A navigator component.
+ *
+ * @constructor Creates a navigator component.
+ * @param animation an animation type.
+ * @param swipeable an iOS swipe to pop feature
+ * @param classes a set of CSS class names
+ * @param init an initializer extension function
+ */
+open class Navigator(
+ animation: NavAnimation? = null,
+ swipeable: Boolean? = null,
+ classes: Set<String> = setOf(),
+ init: (Navigator.() -> Unit)? = null
+) : SimplePanel(classes) {
+
+ /**
+ * An animation type.
+ */
+ var animation: NavAnimation? by refreshOnUpdate(animation)
+
+ /**
+ * An iOS swipe to pop feature.
+ */
+ var swipeable: Boolean? by refreshOnUpdate(swipeable)
+
+ /**
+ * The width of swipeable area calculated from the edge (in pixels).
+ */
+ var swipeTargetWidth: Number? by refreshOnUpdate()
+
+ /**
+ * Specify how much the page needs to be swiped before popping.
+ */
+ var swipeThreshold: Number? by refreshOnUpdate()
+
+ /**
+ * Device back button event listener function.
+ */
+ protected var onDeviceBackButtonCallback: ((BackButtonEvent) -> Unit)? = null
+
+ /**
+ * Swipe event listener function.
+ */
+ protected var onSwipeCallback: ((Number) -> Unit)? = null
+
+ internal val pagesMap = mutableMapOf<String, Page>()
+
+ init {
+ @Suppress("LeakingThis")
+ init?.invoke(this)
+ }
+
+ /**
+ * A dynamic property returning current top page.
+ */
+ val topPage: dynamic
+ get() = getElement()?.asDynamic()?.topPage
+
+ /**
+ * A dynamic property returning current pages stack.
+ */
+ val pages: dynamic
+ get() = getElement()?.asDynamic()?.pages
+
+ override fun render(): VNode {
+ return render("ons-navigator", childrenVNodes())
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ animation?.let {
+ sn.add("animation" to it.type)
+ }
+ if (swipeable == true) {
+ sn.add("swipeable" to "swipeable")
+ }
+ swipeTargetWidth?.let {
+ sn.add("swipe-target-width" to "${it}px")
+ }
+ swipeThreshold?.let {
+ sn.add("swipe-threshold" to it.toString())
+ }
+ return sn
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ override fun afterInsert(node: VNode) {
+ node.elm.asDynamic().pageLoader = (ons.PageLoader as Any).createInstance<Any>({ o: dynamic, done: dynamic ->
+ @Suppress("UnsafeCastFromDynamic")
+ val page: Page = o.page
+ done(page.getElement())
+ }, { })
+ if (onDeviceBackButtonCallback != null) {
+ getElement()?.asDynamic()?.onDeviceBackButton = onDeviceBackButtonCallback
+ }
+ if (onSwipeCallback != null) {
+ getElement()?.asDynamic()?.onSwipe = onSwipeCallback
+ }
+ this.getElementJQuery()?.on("prepush") { e, _ ->
+ this.dispatchEvent("onsPrepush", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("prepop") { e, _ ->
+ this.dispatchEvent("onsPrepop", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("postpush") { e, _ ->
+ this.dispatchEvent("onsPostpush", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("postpop") { e, _ ->
+ this.dispatchEvent("onsPostpop", obj { detail = e })
+ }
+ }
+
+ /**
+ * Pushes the specified page into the stack.
+ * @param pageId a given page id
+ * @param options a parameter object
+ */
+ open fun pushPage(pageId: String, options: dynamic = undefined): Promise<Unit>? {
+ return pagesMap[pageId]?.let { pushPage(it, options) }
+ }
+
+ /**
+ * Pushes the specified page into the stack.
+ * @param page a given page
+ * @param options a parameter object
+ */
+ open fun pushPage(page: Page, options: dynamic = undefined): Promise<Unit>? {
+ page.display = null
+ add(page)
+ @Suppress("UnsafeCastFromDynamic")
+ return getElement()?.asDynamic()?.pushPage(page, options).then {
+ refreshPageStack()
+ }
+ }
+
+ /**
+ * Pops the current page from the page stack.
+ * @param options a parameter object
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun popPage(options: dynamic = undefined): Promise<Unit>? {
+ return if (children.size > 1) {
+ getElement()?.asDynamic()?.popPage(options).then {
+ (children.last() as? Page)?.dispatchDestroyEvent()
+ children.removeAt(children.size - 1).clearParent()
+ refreshPageStack()
+ }
+ } else {
+ null
+ }
+ }
+
+ /**
+ * Replaces the current top page with the specified one.
+ * @param pageId a given page id
+ * @param options a parameter object
+ */
+ open fun replacePage(pageId: String, options: dynamic = undefined): Promise<Unit>? {
+ return pagesMap[pageId]?.let { replacePage(it, options) }
+ }
+
+ /**
+ * Replaces the current top page with the specified one.
+ * @param page a given page
+ * @param options a parameter object
+ */
+ open fun replacePage(page: Page, options: dynamic = undefined): Promise<Unit>? {
+ page.display = null
+ add(page)
+ @Suppress("UnsafeCastFromDynamic")
+ return getElement()?.asDynamic()?.replacePage(page, options).then {
+ if (children.size > 1) {
+ (children.elementAt(children.size - 2) as? Page)?.dispatchDestroyEvent()
+ children.removeAt(children.size - 2).clearParent()
+ }
+ refreshPageStack()
+ }
+ }
+
+ /**
+ * Insert the specified page into the stack with at a position defined by the index argument.
+ * @param index an insertion index
+ * @param pageId a given page id
+ * @param options a parameter object
+ */
+ open fun insertPage(index: Int, pageId: String, options: dynamic = undefined): Promise<Unit>? {
+ return pagesMap[pageId]?.let { insertPage(index, it, options) }
+ }
+
+ /**
+ * Insert the specified page into the stack with at a position defined by the index argument.
+ * @param index an insertion index
+ * @param page a given page
+ * @param options a parameter object
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun insertPage(index: Int, page: Page, options: dynamic = undefined): Promise<Unit>? {
+ return if (index >= 0 && index < children.size) {
+ children.add(index, page)
+ page.parent?.remove(page)
+ page.parent = this
+ refreshPageStack()
+ getElement()?.asDynamic()?.insertPage(index, page, options)
+ } else {
+ null
+ }
+ }
+
+ /**
+ * Remove the specified page at a position in the stack defined by the index argument.
+ * @param index index to delete
+ * @param options a parameter object
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun removePage(index: Int, options: dynamic = undefined): Promise<Unit>? {
+ return if (index >= 0 && index < children.size && children.size > 1) {
+ getElement()?.asDynamic()?.removePage(index, options).then {
+ (children[index] as? Page)?.dispatchDestroyEvent()
+ children.removeAt(index).clearParent()
+ refreshPageStack()
+ }
+ } else {
+ null
+ }
+ }
+
+ /**
+ * Clears page stack and adds the specified page to the stack.
+ * @param pageId a given page id
+ * @param options a parameter object
+ */
+ open fun resetToPage(pageId: String, options: dynamic = undefined): Promise<Unit>? {
+ return pagesMap[pageId]?.let { resetToPage(it, options) }
+ }
+
+ /**
+ * Clears page stack and adds the specified page to the stack.
+ * @param page a given page
+ * @param options a parameter object
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun resetToPage(page: Page, options: dynamic = undefined): Promise<Unit>? {
+ page.display = null
+ add(page)
+ return getElement()?.asDynamic()?.resetToPage(page, options).then {
+ children.take(children.size - 1).forEach {
+ (it as? Page)?.dispatchDestroyEvent()
+ it.clearParent()
+ }
+ children.clear()
+ children.add(page)
+ refresh()
+ }
+ }
+
+ /**
+ * Brings the given page to the top of the page stack.
+ * @param index index of a page to move
+ * @param options a parameter object
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun bringPageTop(index: Int, options: dynamic = undefined): Promise<Unit>? {
+ return if (index >= 0 && index < children.size) {
+ getElement()?.asDynamic()?.bringPageTop(index, options).then {
+ val page = children.removeAt(index).clearParent()
+ (page as? Page)?.display = null
+ add(page)
+ }
+ } else {
+ null
+ }
+ }
+
+ protected fun refreshPageStack() {
+ if (children.isNotEmpty()) {
+ children.take(children.size - 1).forEach { (it as? Page)?.display = Display.NONE }
+ (children.lastOrNull() as? Page)?.display = null
+ }
+ }
+
+ /**
+ * Sets device back button event listener.
+ * @param callback an event listener
+ */
+ open fun onDeviceBackButton(callback: (event: BackButtonEvent) -> Unit) {
+ onDeviceBackButtonCallback = callback
+ getElement()?.asDynamic()?.onDeviceBackButton = callback
+ }
+
+ /**
+ * Clears device back button event listener.
+ */
+ open fun onDeviceBackButtonClear() {
+ onDeviceBackButtonCallback = null
+ getElement()?.asDynamic()?.onDeviceBackButton = undefined
+ }
+
+ /**
+ * Sets swipe event listener.
+ * @param callback an event listener
+ */
+ open fun onSwipe(callback: (ratio: Number) -> Unit) {
+ onSwipeCallback = callback
+ getElement()?.asDynamic()?.onSwipe = callback
+ }
+
+ /**
+ * Clears swipe event listener.
+ */
+ open fun onSwipeClear() {
+ onSwipeCallback = null
+ getElement()?.asDynamic()?.onSwipe = undefined
+ }
+
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Root.navigator(
+ animation: NavAnimation? = null,
+ swipeable: Boolean? = null,
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Navigator.() -> Unit)? = null
+): Navigator {
+ val navigator = Navigator(animation, swipeable, classes ?: className.set, init)
+ this.add(navigator)
+ return navigator
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun SplitterContent.navigator(
+ animation: NavAnimation? = null,
+ swipeable: Boolean? = null,
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Navigator.() -> Unit)? = null
+): Navigator {
+ val navigator = Navigator(animation, swipeable, classes ?: className.set, init)
+ this.add(navigator)
+ return navigator
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Tab.navigator(
+ animation: NavAnimation? = null,
+ swipeable: Boolean? = null,
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Navigator.() -> Unit)? = null
+): Navigator {
+ val navigator = Navigator(animation, swipeable, classes ?: className.set, init)
+ this.add(navigator)
+ return navigator
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/core/Page.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/core/Page.kt
new file mode 100644
index 00000000..72ce513a
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/core/Page.kt
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.onsenui.core
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.core.Component
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.html.Span
+import pl.treksoft.kvision.onsenui.BackButtonEvent
+import pl.treksoft.kvision.onsenui.splitter.SplitterContent
+import pl.treksoft.kvision.onsenui.splitter.SplitterSide
+import pl.treksoft.kvision.onsenui.tabbar.Tab
+import pl.treksoft.kvision.onsenui.toolbar.Toolbar
+import pl.treksoft.kvision.panel.Root
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.obj
+import pl.treksoft.kvision.utils.set
+
+/**
+ * A page component.
+ *
+ * @constructor Creates a page component.
+ * @param classes a set of CSS class names
+ * @param init an initializer extension function
+ */
+@Suppress("LeakingThis")
+open class Page(classes: Set<String> = setOf(), init: (Page.() -> Unit)? = null) :
+ SimplePanel(setOf("page") + classes) {
+
+ /**
+ * The page toolbar.
+ */
+ var toolbar: Toolbar? = null
+ set(value) {
+ field = value
+ value?.parent = this
+ }
+
+ /**
+ * The page background.
+ */
+ val backgroundPanel = SimplePanel(setOf("page__background"))
+
+ /**
+ * The page content.
+ */
+ val contentPanel = SimplePanel(setOf("page__content"))
+
+ /**
+ * Fixed content.
+ */
+ val fixedPanel = Span()
+
+ /**
+ * A modifier attribute to specify custom styles.
+ */
+ var modifier: String? by refreshOnUpdate()
+
+ /**
+ * A dynamic parameter object passed to the page when pushed to the stack.
+ */
+ val data: dynamic
+ get() = getElement()?.asDynamic()?.data
+
+ /**
+ * Infinite scroll event listener function.
+ */
+ protected var onInfiniteScrollCallback: ((() -> Unit) -> Unit)? = null
+
+ /**
+ * Device back button event listener function.
+ */
+ protected var onDeviceBackButtonCallback: ((BackButtonEvent) -> Unit)? = null
+
+ init {
+ backgroundPanel.parent = this
+ contentPanel.parent = this
+ fixedPanel.parent = this
+ init?.invoke(this)
+ }
+
+ override fun render(): VNode {
+ val toolbarArr = toolbar?.let { arrayOf(it.renderVNode()) } ?: emptyArray()
+ return render(
+ "ons-page",
+ toolbarArr + arrayOf(backgroundPanel.renderVNode(), contentPanel.renderVNode(), fixedPanel.renderVNode())
+ )
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ modifier?.let {
+ sn.add("modifier" to it)
+ }
+ return sn
+ }
+
+ override fun afterInsert(node: VNode) {
+ if (onInfiniteScrollCallback != null) {
+ getElement()?.asDynamic()?.onInfiniteScroll = onInfiniteScrollCallback
+ }
+ if (onDeviceBackButtonCallback != null) {
+ getElement()?.asDynamic()?.onDeviceBackButton = onDeviceBackButtonCallback
+ }
+ this.getElementJQuery()?.on("init") { e, _ ->
+ this.dispatchEvent("onsInit", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("show") { e, _ ->
+ this.dispatchEvent("onsShow", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("hide") { e, _ ->
+ this.dispatchEvent("onsHide", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("destroy") { e, _ ->
+ this.dispatchEvent("onsDestroy", obj { detail = e })
+ }
+ }
+
+ override fun add(child: Component): SimplePanel {
+ contentPanel.add(child)
+ return this
+ }
+
+ override fun addAll(children: List<Component>): SimplePanel {
+ contentPanel.addAll(children)
+ return this
+ }
+
+ override fun remove(child: Component): SimplePanel {
+ contentPanel.remove(child)
+ return this
+ }
+
+ override fun removeAll(): SimplePanel {
+ contentPanel.removeAll()
+ return this
+ }
+
+ override fun getChildren(): List<Component> {
+ return contentPanel.getChildren()
+ }
+
+ /**
+ * DSL builder function to add fixed elements to the page.
+ * @param builder a builder extension function
+ */
+ open fun fixed(builder: Container.() -> Unit) {
+ fixedPanel.builder()
+ }
+
+ /**
+ * Sets infinite scroll event listener.
+ * @param callback an event listener
+ */
+ open fun onInfiniteScroll(callback: (done: () -> Unit) -> Unit) {
+ onInfiniteScrollCallback = callback
+ getElement()?.asDynamic()?.onInfiniteScroll = callback
+ }
+
+ /**
+ * Clears infinite scroll event listener.
+ */
+ open fun onInfiniteScrollClear() {
+ onInfiniteScrollCallback = null
+ getElement()?.asDynamic()?.onInfiniteScroll = undefined
+ }
+
+ /**
+ * Sets device back button event listener.
+ * @param callback an event listener
+ */
+ open fun onDeviceBackButton(callback: (event: BackButtonEvent) -> Unit) {
+ onDeviceBackButtonCallback = callback
+ getElement()?.asDynamic()?.onDeviceBackButton = callback
+ }
+
+ /**
+ * Clears device back button event listener.
+ */
+ open fun onDeviceBackButtonClear() {
+ onDeviceBackButtonCallback = null
+ getElement()?.asDynamic()?.onDeviceBackButton = undefined
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ internal fun dispatchHideEvent() {
+ this.dispatchEvent("onsHide", obj { })
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ internal fun dispatchDestroyEvent() {
+ this.dispatchEvent("onsDestroy", obj { })
+ }
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Root.page(
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Page.() -> Unit)? = null
+): Page {
+ val page = Page(classes ?: className.set, init)
+ this.add(page)
+ return page
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Navigator.page(
+ pageId: String? = null,
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Page.() -> Unit)? = null
+): Page {
+ val page = Page(classes ?: className.set, init)
+ if (pageId == null || this.getChildren().isEmpty()) {
+ this.add(page)
+ }
+ if (pageId != null) this.pagesMap[pageId] = page
+ return page
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun SplitterSide.page(
+ pageId: String? = null,
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Page.() -> Unit)? = null
+): Page {
+ val page = Page(classes ?: className.set, init)
+ if (pageId == null || this.getChildren().isEmpty()) {
+ this.add(page)
+ }
+ if (pageId != null) this.pagesMap[pageId] = page
+ return page
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun SplitterContent.page(
+ pageId: String? = null,
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Page.() -> Unit)? = null
+): Page {
+ val page = Page(classes ?: className.set, init)
+ if (pageId == null || this.getChildren().isEmpty()) {
+ this.add(page)
+ }
+ if (pageId != null) this.pagesMap[pageId] = page
+ return page
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Tab.page(
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Page.() -> Unit)? = null
+): Page {
+ val page = Page(classes ?: className.set, init)
+ this.add(page)
+ return page
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/splitter/Splitter.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/splitter/Splitter.kt
new file mode 100644
index 00000000..5968d285
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/splitter/Splitter.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.onsenui.splitter
+
+import com.github.snabbdom.VNode
+import org.w3c.dom.HTMLElement
+import pl.treksoft.kvision.onsenui.BackButtonEvent
+import pl.treksoft.kvision.onsenui.core.Page
+import pl.treksoft.kvision.panel.Root
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.set
+
+/**
+ * A splitter component.
+ *
+ * @constructor Creates a splitter component.
+ * @param classes a set of CSS class names
+ * @param init an initializer extension function
+ */
+open class Splitter(
+ classes: Set<String> = setOf(),
+ init: (Splitter.() -> Unit)? = null
+) : SimplePanel(classes) {
+
+ /**
+ * A dynamic property returning current left side element.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ val leftSide: HTMLElement?
+ get() = getElement()?.asDynamic()?.left
+
+ /**
+ * A dynamic property returning current right side element.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ val rightSide: HTMLElement?
+ get() = getElement()?.asDynamic()?.right
+
+ /**
+ * A dynamic property returning current first side element.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ val side: HTMLElement?
+ get() = getElement()?.asDynamic()?.right
+
+ /**
+ * A dynamic property returning current content element.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ val content: HTMLElement?
+ get() = getElement()?.asDynamic()?.content
+
+ /**
+ * Device back button event listener function.
+ */
+ protected var onDeviceBackButtonCallback: ((BackButtonEvent) -> Unit)? = null
+
+ init {
+ @Suppress("LeakingThis")
+ init?.invoke(this)
+ }
+
+ override fun render(): VNode {
+ return render("ons-splitter", childrenVNodes())
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ override fun afterInsert(node: VNode) {
+ if (onDeviceBackButtonCallback != null) {
+ getElement()?.asDynamic()?.onDeviceBackButton = onDeviceBackButtonCallback
+ }
+ }
+
+ /**
+ * Sets device back button event listener.
+ * @param callback an event listener
+ */
+ open fun onDeviceBackButton(callback: (event: BackButtonEvent) -> Unit) {
+ onDeviceBackButtonCallback = callback
+ getElement()?.asDynamic()?.onDeviceBackButton = callback
+ }
+
+ /**
+ * Clears device back button event listener.
+ */
+ open fun onDeviceBackButtonClear() {
+ onDeviceBackButtonCallback = null
+ getElement()?.asDynamic()?.onDeviceBackButton = undefined
+ }
+
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Root.splitter(
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Splitter.() -> Unit)? = null
+): Splitter {
+ val splitter = Splitter(classes ?: className.set, init)
+ this.add(splitter)
+ return splitter
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Page.splitter(
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Splitter.() -> Unit)? = null
+): Splitter {
+ val splitter = Splitter(classes ?: className.set, init)
+ this.add(splitter)
+ return splitter
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/splitter/SplitterContent.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/splitter/SplitterContent.kt
new file mode 100644
index 00000000..f7dcb373
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/splitter/SplitterContent.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.onsenui.splitter
+
+import com.github.snabbdom.VNode
+import org.w3c.dom.HTMLElement
+import pl.treksoft.kvision.KVManagerOnsenui.ons
+import pl.treksoft.kvision.onsenui.core.Page
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.createInstance
+import pl.treksoft.kvision.utils.set
+import kotlin.js.Promise
+
+/**
+ * A splitter content component.
+ *
+ * @constructor Creates a splitter content component.
+ * @param classes a set of CSS class names
+ * @param init an initializer extension function
+ */
+open class SplitterContent(
+ classes: Set<String> = setOf(),
+ init: (SplitterContent.() -> Unit)? = null
+) : SimplePanel(classes) {
+
+ /**
+ * A dynamic property returning current page.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ val page: HTMLElement?
+ get() = getElement()?.asDynamic()?.page
+
+ internal val pagesMap = mutableMapOf<String, Page>()
+
+ init {
+ @Suppress("LeakingThis")
+ init?.invoke(this)
+ }
+
+ override fun render(): VNode {
+ return render("ons-splitter-content", childrenVNodes())
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ override fun afterInsert(node: VNode) {
+ node.elm.asDynamic().pageLoader = (ons.PageLoader as Any).createInstance<Any>({ o: dynamic, done: dynamic ->
+ @Suppress("UnsafeCastFromDynamic")
+ val page: Page = o.page
+ done(page.getElement())
+ }, { })
+ }
+
+ /**
+ * Loads the specified page into the splitter content.
+ * @param pageId a given page id
+ * @param options a parameter object
+ */
+ open fun load(pageId: String, options: dynamic = undefined): Promise<Unit>? {
+ return pagesMap[pageId]?.let { load(it, options) }
+ }
+
+ /**
+ * Loads the specified page into the splitter content.
+ * @param page a given page
+ * @param options a parameter object
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun load(page: Page, options: dynamic = undefined): Promise<Unit>? {
+ (children.first() as? Page)?.let {
+ it.dispatchHideEvent()
+ it.dispatchDestroyEvent()
+ remove(it)
+ }
+ add(page)
+ return getElement()?.asDynamic()?.load(page, options)
+ }
+
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Splitter.splitterContent(
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (SplitterContent.() -> Unit)? = null
+): SplitterContent {
+ val splitterContent = SplitterContent(classes ?: className.set, init)
+ this.add(splitterContent)
+ return splitterContent
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/splitter/SplitterSide.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/splitter/SplitterSide.kt
new file mode 100644
index 00000000..66f34b89
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/splitter/SplitterSide.kt
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.onsenui.splitter
+
+import com.github.snabbdom.VNode
+import org.w3c.dom.HTMLElement
+import pl.treksoft.kvision.KVManagerOnsenui.ons
+import pl.treksoft.kvision.core.CssSize
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.onsenui.core.Page
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.asString
+import pl.treksoft.kvision.utils.createInstance
+import pl.treksoft.kvision.utils.obj
+import pl.treksoft.kvision.utils.set
+import kotlin.js.Promise
+
+/**
+ * Splitter side animation types.
+ */
+enum class SideAnimation(internal val type: String) {
+ OVERLAY("overlay"),
+ PUSH("push"),
+ REVEAL("reveal")
+}
+
+/**
+ * Splitter side collapse types.
+ */
+enum class Collapse(internal val type: String) {
+ COLLAPSE("collapse"),
+ PORTRAIT("portrait"),
+ LANDSCAPE("landscape")
+}
+
+/**
+ * Splitter side positions.
+ */
+enum class Side(internal val type: String) {
+ LEFT("left"),
+ RIGHT("right")
+}
+
+/**
+ * A splitter side component.
+ *
+ * @constructor Creates a splitter side component.
+ * @param animation an animation type
+ * @param swipeable whether to enable swipe interaction on collapse mode
+ * @param collapse specify the collapse behavior
+ * @param side specify which side of the screen the side menu is located
+ * @param classes a set of CSS class names
+ * @param init an initializer extension function
+ */
+open class SplitterSide(
+ animation: SideAnimation? = null,
+ swipeable: Boolean? = null,
+ collapse: Collapse? = null,
+ side: Side? = null,
+ classes: Set<String> = setOf(),
+ init: (SplitterSide.() -> Unit)? = null
+) : SimplePanel(classes) {
+
+ /**
+ * An animation type.
+ */
+ var animation: SideAnimation? by refreshOnUpdate(animation)
+
+ /**
+ * Whether to enable swipe interaction on collapse mode.
+ */
+ var swipeable: Boolean? by refreshOnUpdate(swipeable)
+
+ /**
+ * Specify the collapse behavior.
+ */
+ var collapse: Collapse? by refreshOnUpdate(collapse)
+
+ /**
+ * Specify which side of the screen the side menu is located.
+ */
+ var side: Side? by refreshOnUpdate(side)
+
+ /**
+ * Specify how much the menu needs to be swiped before opening.
+ */
+ var openThreshold: Number? by refreshOnUpdate()
+
+ /**
+ * The width of swipeable area calculated from the edge (in pixels).
+ */
+ var swipeTargetWidth: Number? by refreshOnUpdate()
+
+ /**
+ * The width of swipeable area calculated from the edge (in pixels).
+ */
+ var sideWidth: CssSize? by refreshOnUpdate()
+
+ /**
+ * A dynamic property returning current page.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ val page: HTMLElement?
+ get() = getElement()?.asDynamic()?.page
+
+ /**
+ * A dynamic property returning current collapse mode ("split", "collapse", "closed", "open" or "changing").
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ val mode: String?
+ get() = getElement()?.asDynamic()?.mode
+
+ /**
+ * A dynamic property returning if the side menu is open.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ val isOpen: Boolean
+ get() = getElement()?.asDynamic()?.isOpen ?: false
+
+ /**
+ * Swipe event listener function.
+ */
+ protected var onSwipeCallback: ((Number) -> Unit)? = null
+
+ internal val pagesMap = mutableMapOf<String, Page>()
+
+ init {
+ @Suppress("LeakingThis")
+ init?.invoke(this)
+ }
+
+ override fun render(): VNode {
+ return render("ons-splitter-side", childrenVNodes())
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ animation?.let {
+ sn.add("animation" to it.type)
+ }
+ if (swipeable == true) {
+ sn.add("swipeable" to "swipeable")
+ }
+ collapse?.let {
+ sn.add("collapse" to it.type)
+ }
+ side?.let {
+ sn.add("side" to it.type)
+ }
+ openThreshold?.let {
+ sn.add("open-threshold" to it.toString())
+ }
+ swipeTargetWidth?.let {
+ sn.add("swipe-target-width" to "${it}px")
+ }
+ sideWidth?.let {
+ sn.add("width" to it.asString())
+ }
+ return sn
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ override fun afterInsert(node: VNode) {
+ node.elm.asDynamic().pageLoader = (ons.PageLoader as Any).createInstance<Any>({ o: dynamic, done: dynamic ->
+ @Suppress("UnsafeCastFromDynamic")
+ val page: Page = o.page
+ done(page.getElement())
+ }, { })
+ if (onSwipeCallback != null) {
+ getElement()?.asDynamic()?.onSwipe = onSwipeCallback
+ }
+ this.getElementJQuery()?.on("preopen") { e, _ ->
+ this.dispatchEvent("onsPreopen", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("preclose") { e, _ ->
+ this.dispatchEvent("onsPreclose", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("postopen") { e, _ ->
+ this.dispatchEvent("onsPostopen", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("postclose") { e, _ ->
+ this.dispatchEvent("onsPostclose", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("modechange") { e, _ ->
+ this.dispatchEvent("onsModechange", obj { detail = e })
+ }
+ }
+
+ /**
+ * Loads the specified page into the splitter side menu.
+ * @param pageId a given page id
+ * @param options a parameter object
+ */
+ open fun load(pageId: String, options: dynamic = undefined): Promise<Unit>? {
+ return pagesMap[pageId]?.let { load(it, options) }
+ }
+
+ /**
+ * Loads the specified page into the splitter side menu.
+ * @param page a given page
+ * @param options a parameter object
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun load(page: Page, options: dynamic = undefined): Promise<Unit>? {
+ (children.first() as? Page)?.let {
+ it.dispatchHideEvent()
+ it.dispatchDestroyEvent()
+ remove(it)
+ }
+ add(page)
+ return getElement()?.asDynamic()?.load(page, options)
+ }
+
+ /**
+ * Opens side menu.
+ * @param options a parameter object
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun open(options: dynamic = undefined): Promise<Unit>? {
+ return getElement()?.asDynamic()?.open(options)
+ }
+
+ /**
+ * Closes side menu.
+ * @param options a parameter object
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun close(options: dynamic = undefined): Promise<Unit>? {
+ return getElement()?.asDynamic()?.close(options)
+ }
+
+ /**
+ * Toggles side menu visibility.
+ * @param options a parameter object
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun toggle(options: dynamic = undefined): Promise<Unit>? {
+ return getElement()?.asDynamic()?.toggle(options)
+ }
+
+ /**
+ * Sets swipe event listener.
+ * @param callback an event listener
+ */
+ open fun onSwipe(callback: (ratio: Number) -> Unit) {
+ onSwipeCallback = callback
+ getElement()?.asDynamic()?.onSwipe = callback
+ }
+
+ /**
+ * Clears swipe event listener.
+ */
+ open fun onSwipeClear() {
+ onSwipeCallback = null
+ getElement()?.asDynamic()?.onSwipe = undefined
+ }
+
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Splitter.splitterSide(
+ animation: SideAnimation? = null,
+ swipeable: Boolean? = null,
+ collapse: Collapse? = null,
+ side: Side? = null,
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (SplitterSide.() -> Unit)? = null
+): SplitterSide {
+ val splitterSide =
+ SplitterSide(animation, swipeable, collapse, side, classes ?: className.set, init)
+ this.add(splitterSide)
+ return splitterSide
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/tabbar/Tab.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/tabbar/Tab.kt
new file mode 100644
index 00000000..64889157
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/tabbar/Tab.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.onsenui.tabbar
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.KVManagerOnsenui.ons
+import pl.treksoft.kvision.core.Component
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.onsenui.core.Navigator
+import pl.treksoft.kvision.onsenui.core.Page
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.createInstance
+import pl.treksoft.kvision.utils.set
+
+/**
+ * A tab component.
+ *
+ * @constructor Creates a tab component.
+ * @param label the label of the tab item
+ * @param icon the name of the icon
+ * @param activeIcon the name of the icon when the tab is active
+ * @param badge display a notification badge on top of the tab
+ * @param active whether this tab is active on start
+ * @param classes a set of CSS class names
+ * @param init an initializer extension function
+ */
+open class Tab(
+ label: String? = null,
+ icon: String? = null,
+ activeIcon: String? = null,
+ badge: String? = null,
+ active: Boolean? = null,
+ classes: Set<String> = setOf(),
+ init: (Tab.() -> Unit)? = null
+) : SimplePanel(classes) {
+
+ /**
+ * The label of the tab item.
+ */
+ var label: String? by refreshOnUpdate(label)
+
+ /**
+ * The name of the icon.
+ */
+ var icon: String? by refreshOnUpdate(icon)
+
+ /**
+ * The name of the icon when the tab is active.
+ */
+ var activeIcon: String? by refreshOnUpdate(activeIcon)
+
+ /**
+ * Display a notification badge on top of the tab.
+ */
+ var badge: String? by refreshOnUpdate(badge)
+
+ /**
+ * Whether this tab is active on start.
+ */
+ var active: Boolean? by refreshOnUpdate(active)
+
+ internal var content: Widget? = null
+
+ private val idc = "kv_ons_tab_${counter}"
+
+ init {
+ @Suppress("LeakingThis")
+ init?.invoke(this)
+ counter++
+ }
+
+ override fun render(): VNode {
+ return render("ons-tab", childrenVNodes())
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ sn.add("page" to idc)
+ label?.let {
+ sn.add("label" to it)
+ }
+ icon?.let {
+ sn.add("icon" to it)
+ }
+ activeIcon?.let {
+ sn.add("active-icon" to it)
+ }
+ badge?.let {
+ sn.add("badge" to it)
+ }
+ if (active == true) {
+ sn.add("active" to "active")
+ }
+ return sn
+ }
+
+ override fun afterInsert(node: VNode) {
+ node.elm.asDynamic().pageLoader = (ons.PageLoader as Any).createInstance<Any>({ _: dynamic, done: dynamic ->
+ if (content != null && content?.parent == null) this.parent?.add(content!!)
+ if (content?.getElement() != null) {
+ done(content?.getElement())
+ } else {
+ content?.afterInsertHook = {
+ done(it.elm)
+ }
+ }
+ }, { })
+ }
+
+ override fun add(child: Component): SimplePanel {
+ return if (child is Page || child is Navigator) {
+ content = child as Widget
+ this
+ } else {
+ super.add(child)
+ }
+ }
+
+ companion object {
+ internal var counter = 0
+ }
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Tabbar.tab(
+ label: String? = null,
+ icon: String? = null,
+ activeIcon: String? = null,
+ badge: String? = null,
+ active: Boolean? = null,
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Tab.() -> Unit)? = null
+): Tab {
+ val tab = Tab(label, icon, activeIcon, badge, active, classes ?: className.set, init)
+ this.add(tab)
+ return tab
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/tabbar/Tabbar.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/tabbar/Tabbar.kt
new file mode 100644
index 00000000..f526676c
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/tabbar/Tabbar.kt
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.onsenui.tabbar
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.onsenui.core.Page
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.obj
+import pl.treksoft.kvision.utils.set
+import kotlin.js.Promise
+
+/**
+ * Tab bar position.
+ */
+enum class TabsPosition(internal val type: String) {
+ AUTO("auto"),
+ TOP("top"),
+ BOTTOM("bottom")
+}
+
+/**
+ * A tab bar component.
+ *
+ * @constructor Creates a tab bar component.
+ * @param tabPosition the tab bar position
+ * @param animation determines if the transitions are animated
+ * @param swipeable determines if the tab bar can be scrolled by drag or swipe
+ * @param classes a set of CSS class names
+ * @param init an initializer extension function
+ */
+open class Tabbar(
+ tabPosition: TabsPosition? = null,
+ animation: Boolean = true,
+ swipeable: Boolean? = null,
+ classes: Set<String> = setOf(),
+ init: (Tabbar.() -> Unit)? = null
+) : SimplePanel(classes) {
+
+ /**
+ * The tab bar position.
+ */
+ var tabPosition: TabsPosition? by refreshOnUpdate(tabPosition)
+
+ /**
+ * Determines if the transitions are animated.
+ */
+ var animation: Boolean by refreshOnUpdate(animation)
+
+ /**
+ * Determines if the tab bar can be scrolled by drag or swipe.
+ */
+ var swipeable: Boolean? by refreshOnUpdate(swipeable)
+
+ /**
+ * Distance in pixels from both edges. Swiping on these areas will prioritize parent components.
+ */
+ var ignoreEdgeWidth: Number? by refreshOnUpdate()
+
+ /**
+ * Whether to hide the tabs.
+ */
+ var hideTabs: Boolean? by refreshOnUpdate()
+
+ /**
+ * Whether the tabs show a dynamic bottom border. Only works for iOS flat design since the border is always visible in Material Design.
+ */
+ var tabBorder: Boolean? by refreshOnUpdate()
+
+ /**
+ * A modifier attribute to specify custom styles.
+ */
+ var modifier: String? by refreshOnUpdate()
+
+ /**
+ * A dynamic property returning visibility of the tab bar.
+ */
+ val isVisible: dynamic
+ get() = getElement()?.asDynamic()?.visible
+
+ /**
+ * Swipe event listener function.
+ */
+ protected var onSwipeCallback: ((Number) -> Unit)? = null
+
+ /**
+ * The tab bar panel styling callback.
+ */
+ protected var tabbarStyleCallback: (Widget.(Int) -> Unit)? = null
+
+ init {
+ @Suppress("LeakingThis")
+ init?.invoke(this)
+ }
+
+ override fun render(): VNode {
+ return render("ons-tabbar", childrenVNodes())
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ tabPosition?.let {
+ sn.add("position" to it.type)
+ }
+ if (!animation) {
+ sn.add("animation" to "none")
+ }
+ if (swipeable == true) {
+ sn.add("swipeable" to "swipeable")
+ }
+ ignoreEdgeWidth?.let {
+ sn.add("ignore-edge-width" to "${it}px")
+ }
+ if (hideTabs == true) {
+ sn.add("hide-tabs" to "hide-tabs")
+ }
+ if (tabBorder == true) {
+ sn.add("tab-border" to "tab-border")
+ }
+ modifier?.let {
+ sn.add("modifier" to it)
+ }
+ return sn
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ override fun afterInsert(node: VNode) {
+ if (onSwipeCallback != null) {
+ getElement()?.asDynamic()?.onSwipe = onSwipeCallback
+ }
+ this.getElementJQuery()?.on("prechange") { e, _ ->
+ this.dispatchEvent("onsPrechange", obj { detail = e })
+ if (tabbarStyleCallback != null) {
+ val widget = Widget()
+ tabbarStyleCallback?.let { widget.it(e.asDynamic().detail.index) }
+ val style = widget.getSnStyle().joinToString(";") { (key, value) -> "$key: $value" }
+ getElementJQuery()?.find(".tabbar")?.attr("style", style)
+ }
+ }
+ this.getElementJQuery()?.on("postchange") { e, _ ->
+ this.dispatchEvent("onsPostchange", obj { detail = e })
+ }
+ this.getElementJQuery()?.on("reactive") { e, _ ->
+ this.dispatchEvent("onsReactive", obj { detail = e })
+ }
+ if (tabbarStyleCallback != null) {
+ val activeIndex = if (getActiveTabIndex().toInt() >= 0) {
+ getActiveTabIndex().toInt()
+ } else {
+ val childIndex = getChildren().indexOfFirst { it is Tab && it.active == true }
+ if (childIndex >= 0) {
+ childIndex
+ } else {
+ 0
+ }
+ }
+ val widget = Widget()
+ tabbarStyleCallback?.let { widget.it(activeIndex) }
+ val style = widget.getSnStyle().joinToString(";") { (key, value) -> "$key: $value" }
+ getElementJQuery()?.find(".tabbar")?.attr("style", style)
+ }
+ }
+
+ override fun afterDestroy() {
+ children.forEach {
+ if (it is Page) remove(it)
+ }
+ }
+
+ /**
+ * Shows specified tab page.
+ * @param index the tab index
+ * @param options a parameter object
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun setActiveTab(index: Int, options: dynamic = undefined): Promise<Unit>? {
+ return getElement()?.asDynamic()?.setActiveTab(index, options)
+ }
+
+ /**
+ * Shows or hides the tab bar.
+ * @param visible whether the tab bar is visible
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun setTabbarVisibility(visible: Boolean): Unit {
+ return getElement()?.asDynamic()?.setTabbarVisibility(visible)
+ }
+
+ /**
+ * Get the active tab index.
+ * @return active tab index
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun getActiveTabIndex(): Number {
+ return getElement()?.asDynamic()?.getActiveTabIndex() ?: -1
+ }
+
+ /**
+ * Sets swipe event listener.
+ * @param callback an event listener
+ */
+ open fun onSwipe(callback: (ratio: Number) -> Unit) {
+ onSwipeCallback = callback
+ getElement()?.asDynamic()?.onSwipe = callback
+ }
+
+ /**
+ * Clears swipe event listener.
+ */
+ open fun onSwipeClear() {
+ onSwipeCallback = null
+ getElement()?.asDynamic()?.onSwipe = undefined
+ }
+
+ /**
+ * Sets the tab bar panel styling callback.
+ * @param callback a styling callback
+ */
+ open fun tabbarStyle(callback: Widget.(index: Int) -> Unit) {
+ tabbarStyleCallback = callback
+ }
+
+ /**
+ * Clears the tab bar panel styling callback.
+ */
+ open fun tabbarStyleClear() {
+ tabbarStyleCallback = null
+ }
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Page.tabbar(
+ tabPosition: TabsPosition? = null,
+ animation: Boolean = true,
+ swipeable: Boolean? = null,
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Tabbar.() -> Unit)? = null
+): Tabbar {
+ val tabbar = Tabbar(tabPosition, animation, swipeable, classes ?: className.set, init)
+ this.add(tabbar)
+ return tabbar
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/toolbar/BottomToolbar.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/toolbar/BottomToolbar.kt
new file mode 100644
index 00000000..ddd70a6b
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/toolbar/BottomToolbar.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.onsenui.toolbar
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.onsenui.core.Page
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.set
+
+/**
+ * An toolbar component located at the bottom of the page.
+ *
+ * @constructor Creates a bottom toolbar component.
+ * @param classes a set of CSS class names
+ * @param init an initializer extension function
+ */
+@Suppress("LeakingThis")
+open class BottomToolbar(
+ classes: Set<String> = setOf(),
+ init: (BottomToolbar.() -> Unit)? = null
+) : SimplePanel(classes) {
+
+ /**
+ * A modifier attribute to specify custom styles.
+ */
+ var modifier: String? by refreshOnUpdate()
+
+ init {
+ init?.invoke(this)
+ }
+
+ override fun render(): VNode {
+ return render("ons-bottom-toolbar", childrenVNodes())
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ modifier?.let {
+ sn.add("modifier" to it)
+ }
+ return sn
+ }
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Page.bottomToolbar(
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (BottomToolbar.() -> Unit)? = null
+): BottomToolbar {
+ val bottomToolbar = BottomToolbar(classes ?: className.set, init)
+ this.add(bottomToolbar)
+ return bottomToolbar
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/toolbar/Toolbar.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/toolbar/Toolbar.kt
new file mode 100644
index 00000000..f52e110d
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/toolbar/Toolbar.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.onsenui.toolbar
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.html.Div
+import pl.treksoft.kvision.onsenui.core.Page
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.set
+
+/**
+ * A toolbar component.
+ *
+ * @constructor Creates a toolbar component.
+ * @param label a label placed automatically in the center section of the toolbar
+ * @param inline display the toolbar as an inline element
+ * @param static static toolbars are not animated by ons-navigator when pushing or popping pages
+ * @param classes a set of CSS class names
+ * @param init an initializer extension function
+ */
+@Suppress("LeakingThis")
+open class Toolbar(
+ label: String? = null,
+ inline: Boolean? = null,
+ static: Boolean? = null,
+ classes: Set<String> = setOf(),
+ init: (Toolbar.() -> Unit)? = null
+) : SimplePanel(classes) {
+
+ /**
+ * The left section of the toolbar.
+ */
+ val leftPanel = Div(classes = setOf("left", "toolbar__left"))
+
+ /**
+ * The center section of the toolbar.
+ */
+ val centerPanel = Div(label, classes = setOf("center", "toolbar__center"))
+
+ /**
+ * The right section of the toolbar.
+ */
+ val rightPanel = Div(classes = setOf("right", "toolbar__right"))
+
+ /**
+ * Display the toolbar as an inline element.
+ */
+ var inline: Boolean? by refreshOnUpdate(inline)
+
+ /**
+ * Static toolbars are not animated by ons-navigator when pushing or popping pages.
+ */
+ var static: Boolean? by refreshOnUpdate(static)
+
+ /**
+ * A modifier attribute to specify custom styles.
+ */
+ var modifier: String? by refreshOnUpdate()
+
+ init {
+ leftPanel.parent = this
+ centerPanel.parent = this
+ rightPanel.parent = this
+ init?.invoke(this)
+ }
+
+ override fun render(): VNode {
+ return render(
+ "ons-toolbar",
+ arrayOf(leftPanel.renderVNode(), centerPanel.renderVNode(), rightPanel.renderVNode())
+ )
+ }
+
+ /**
+ * A DSL builder for the left section of the toolbar.
+ */
+ open fun left(builder: Div.() -> Unit) {
+ leftPanel.builder()
+ }
+
+ /**
+ * A DSL builder for the center section of the toolbar.
+ */
+ open fun center(builder: Div.() -> Unit) {
+ centerPanel.builder()
+ }
+
+ /**
+ * A DSL builder for the right section of the toolbar.
+ */
+ open fun right(builder: Div.() -> Unit) {
+ rightPanel.builder()
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ if (inline == true) {
+ sn.add("inline" to "inline")
+ }
+ if (static == true) {
+ sn.add("static" to "static")
+ }
+ modifier?.let {
+ sn.add("modifier" to it)
+ }
+ return sn
+ }
+
+ /**
+ * Shows or hides the toolbar.
+ * @param visible whether the toolbar is visible
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun setVisibility(visible: Boolean) {
+ return getElement()?.asDynamic()?.setVisibility(visible)
+ }
+
+ /**
+ * Shows the toolbar.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun showToolbar() {
+ return getElement()?.asDynamic()?.show()
+ }
+
+ /**
+ * Hides the toolbar.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ open fun hideToolbar() {
+ return getElement()?.asDynamic()?.hide()
+ }
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Page.toolbar(
+ label: String? = null,
+ inline: Boolean? = null,
+ static: Boolean? = null,
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (Toolbar.() -> Unit)? = null
+): Toolbar {
+ val toolbar = Toolbar(label, inline, static, classes ?: className.set, init)
+ this.toolbar = toolbar
+ return toolbar
+}
diff --git a/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/toolbar/ToolbarButton.kt b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/toolbar/ToolbarButton.kt
new file mode 100644
index 00000000..157ba6a9
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/main/kotlin/pl/treksoft/kvision/onsenui/toolbar/ToolbarButton.kt
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package pl.treksoft.kvision.onsenui.toolbar
+
+import com.github.snabbdom.VNode
+import org.w3c.dom.events.MouseEvent
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.html.Align
+import pl.treksoft.kvision.html.Div
+import pl.treksoft.kvision.utils.set
+
+/**
+ * A button component designed to be placed inside the toolbar.
+ *
+ * @constructor Creates a toolbar button component.
+ * @param content the content of the button.
+ * @param rich whether [content] can contain HTML code
+ * @param align text align
+ * @param icon an icon placed on the toolbar button
+ * @param disabled specify if the button should be disabled
+ * @param classes a set of CSS class names
+ * @param init an initializer extension function
+ */
+@Suppress("LeakingThis")
+open class ToolbarButton(
+ content: String? = null,
+ rich: Boolean = false,
+ align: Align? = null,
+ icon: String? = null,
+ disabled: Boolean? = null,
+ classes: Set<String> = setOf(),
+ init: (ToolbarButton.() -> Unit)? = null
+) : Div(content, rich, align, classes) {
+
+ /**
+ * The icon placed on the toolbar button.
+ */
+ var icon: String? by refreshOnUpdate(icon)
+
+ /**
+ * Specify if the button should be disabled.
+ */
+ var disabled: Boolean? by refreshOnUpdate(disabled)
+
+ /**
+ * A modifier attribute to specify custom styles.
+ */
+ var modifier: String? by refreshOnUpdate()
+
+ init {
+ init?.invoke(this)
+ }
+
+ /**
+ * A dynamic property returning current state of the component.
+ */
+ val isDisabled: Boolean?
+ @Suppress("UnsafeCastFromDynamic")
+ get() = getElement()?.asDynamic()?.disabled
+
+ override fun render(elementName: String, children: Array<dynamic>): VNode {
+ return super.render("ons-toolbar-button", children)
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ icon?.let {
+ sn.add("icon" to it)
+ }
+ if (disabled == true) {
+ sn.add("disabled" to "disabled")
+ }
+ modifier?.let {
+ sn.add("modifier" to it)
+ }
+ return sn
+ }
+
+ /**
+ * A convenient helper for easy setting onClick event handler.
+ */
+ open fun onClick(handler: ToolbarButton.(MouseEvent) -> Unit): ToolbarButton {
+ this.setEventListener<ToolbarButton> {
+ click = { e ->
+ self.handler(e)
+ }
+ }
+ return this
+ }
+}
+
+/**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+fun Div.toolbarButton(
+ content: String? = null,
+ rich: Boolean = false,
+ align: Align? = null,
+ icon: String? = null,
+ disabled: Boolean? = null,
+ classes: Set<String>? = null,
+ className: String? = null,
+ init: (ToolbarButton.() -> Unit)? = null
+): ToolbarButton {
+ val toolbarButton = ToolbarButton(content, rich, align, icon, disabled, classes ?: className.set, init)
+ this.add(toolbarButton)
+ return toolbarButton
+}
diff --git a/kvision-modules/kvision-onsenui/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-onsenui/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
new file mode 100644
index 00000000..e0e3df54
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package test.pl.treksoft.kvision
+
+import org.w3c.dom.Element
+import pl.treksoft.jquery.jQuery
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.panel.Root
+import kotlin.browser.document
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+interface TestSpec {
+ fun beforeTest()
+
+ fun afterTest()
+
+ fun run(code: () -> Unit) {
+ beforeTest()
+ code()
+ afterTest()
+ }
+}
+
+interface SimpleSpec : TestSpec {
+
+ override fun beforeTest() {
+ }
+
+ override fun afterTest() {
+ }
+
+}
+
+interface DomSpec : TestSpec {
+
+ override fun beforeTest() {
+ val fixture = "<div style=\"display: none\" id=\"pretest\">" +
+ "<div id=\"test\"></div></div>"
+ document.body?.insertAdjacentHTML("afterbegin", fixture)
+ }
+
+ override fun afterTest() {
+ val div = document.getElementById("pretest")
+ div?.let { jQuery(it).remove() }
+ jQuery(".modal-backdrop").remove()
+ Root.disposeAllRoots()
+ }
+
+ fun assertEqualsHtml(expected: String?, actual: String?, message: String?) {
+ if (expected != null && actual != null) {
+ val exp = jQuery(expected.replace("position: ;","position: absolute;"))
+ val act = jQuery(actual.replace("position: ;","position: absolute;"))
+ val result = exp[0]?.isEqualNode(act[0])
+ if (result == true) {
+ assertTrue(result == true, message)
+ } else {
+ assertEquals(expected, actual, message)
+ }
+ } else {
+ assertEquals(expected, actual, message)
+ }
+ }
+}
+
+interface WSpec : DomSpec {
+
+ fun runW(code: (widget: Widget, element: Element?) -> Unit) {
+ run {
+ val root = Root("test", containerType = pl.treksoft.kvision.panel.ContainerType.FIXED)
+ val widget = Widget()
+ widget.id = "test_id"
+ root.add(widget)
+ val element = document.getElementById("test_id")
+ code(widget, element)
+ }
+ }
+
+}
+
+external fun require(name: String): dynamic
diff --git a/kvision-modules/kvision-onsenui/webpack.config.d/css.js b/kvision-modules/kvision-onsenui/webpack.config.d/css.js
new file mode 100644
index 00000000..5d710d35
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/webpack.config.d/css.js
@@ -0,0 +1,2 @@
+config.module.rules.push({ test: /\.css$/, loader: "style-loader!css-loader" });
+
diff --git a/kvision-modules/kvision-onsenui/webpack.config.d/file.js b/kvision-modules/kvision-onsenui/webpack.config.d/file.js
new file mode 100644
index 00000000..653ca21f
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/webpack.config.d/file.js
@@ -0,0 +1,9 @@
+config.module.rules.push(
+ {
+ test: /\.(jpe?g|png|gif|svg)$/i,
+ loader: 'file-loader',
+ options: {
+ esModule: false,
+ },
+ }
+);
diff --git a/kvision-modules/kvision-onsenui/webpack.config.d/fonts.js b/kvision-modules/kvision-onsenui/webpack.config.d/fonts.js
new file mode 100644
index 00000000..35b28e6a
--- /dev/null
+++ b/kvision-modules/kvision-onsenui/webpack.config.d/fonts.js
@@ -0,0 +1,3 @@
+config.module.rules.push({test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'});
+config.module.rules.push({test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream'});
+config.module.rules.push({test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'});