diff options
Diffstat (limited to 'kvision-modules')
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'}); |