aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--build.gradle2
-rw-r--r--gradle.properties6
-rw-r--r--kvision-modules/kvision-bootstrap/build.gradle1
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/resources/css/style.css52
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt2
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt12
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt2
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt2
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt2
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt4
-rw-r--r--kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt2
-rw-r--r--kvision-modules/kvision-chart/build.gradle1
-rw-r--r--kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt2
-rw-r--r--kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt4
-rw-r--r--kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt4
-rw-r--r--kvision-modules/kvision-cordova/build.gradle5
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt73
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt270
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt139
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt542
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt221
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt47
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt264
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt74
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt105
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt197
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt89
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt106
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt154
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt76
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt50
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt154
-rw-r--r--kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt43
-rw-r--r--kvision-modules/kvision-datacontainer/build.gradle1
-rw-r--r--kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt2
-rw-r--r--kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt2
-rw-r--r--kvision-modules/kvision-datetime/build.gradle1
-rw-r--r--kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt4
-rw-r--r--kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt2
-rw-r--r--kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt2
-rw-r--r--kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt2
-rw-r--r--kvision-modules/kvision-dialog/build.gradle1
-rw-r--r--kvision-modules/kvision-handlebars/build.gradle1
-rw-r--r--kvision-modules/kvision-i18n/build.gradle1
-rw-r--r--kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt6
-rw-r--r--kvision-modules/kvision-redux/build.gradle1
-rw-r--r--kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt2
-rw-r--r--kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt4
-rw-r--r--kvision-modules/kvision-richtext/build.gradle1
-rw-r--r--kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt2
-rw-r--r--kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt2
-rw-r--r--kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt2
-rw-r--r--kvision-modules/kvision-select/build.gradle1
-rw-r--r--kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt4
-rw-r--r--kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt10
-rw-r--r--kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt2
-rw-r--r--kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt2
-rw-r--r--kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt2
-rw-r--r--kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt2
-rw-r--r--kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt2
-rw-r--r--kvision-modules/kvision-spinner/build.gradle1
-rw-r--r--kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt4
-rw-r--r--kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt2
-rw-r--r--kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt6
-rw-r--r--kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt6
-rw-r--r--kvision-modules/kvision-tabulator/build.gradle1
-rw-r--r--kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt152
-rw-r--r--kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt38
-rw-r--r--kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt2
-rw-r--r--kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt2
-rw-r--r--kvision-modules/kvision-upload/build.gradle1
-rw-r--r--kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt4
-rw-r--r--kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt2
-rw-r--r--kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt2
-rw-r--r--kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt2
-rw-r--r--settings.gradle3
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/KVManager.kt5
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Component.kt20
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Style.kt78
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt2
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/core/Widget.kt22
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt10
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt4
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt4
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt209
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt213
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt4
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/html/Tag.kt16
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt6
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/Root.kt27
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt15
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt76
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt2
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt10
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt6
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt4
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectInputSpec.kt51
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectSpec.kt53
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt4
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt8
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt8
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt31
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt2
-rw-r--r--src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt2
159 files changed, 3717 insertions, 289 deletions
diff --git a/README.md b/README.md
index 6390ac90..3721118d 100644
--- a/README.md
+++ b/README.md
@@ -49,13 +49,14 @@ allows to build full-stack, multiplatform applications with shared common code.
- Innovative integration interface for [Ktor](https://ktor.io), [Jooby](https://jooby.org) and [Spring Boot](https://spring.io/projects/spring-boot) frameworks on the server side,
including support for type-safe websockets connections.
- Support for building cross-platform, desktop applications with [Electron](https://electronjs.org).
-- Ready to explore [KVision examples](https://github.com/rjaros/kvision-examples) are available,
-built with [Gradle](https://gradle.org/) and supporting Webpack's [Hot Module Replacement (HMR)](https://webpack.js.org/concepts/hot-module-replacement/) and
+- KVision applications are built with [Gradle](https://gradle.org/) with support for Webpack's [Hot Module Replacement (HMR)](https://webpack.js.org/concepts/hot-module-replacement/) and
[Kotlin JavaScript DCE (dead code elimination)](https://kotlinlang.org/docs/reference/javascript-dce.html).
- [Karma](https://karma-runner.github.io/) testing framework support.
- IDE support (IntelliJ IDEA Community Edition).
-## Documentation
+## Examples and documentation
+
+Ready to explore, rich set of [KVision examples](https://github.com/rjaros/kvision-examples) is available in the separate project.
The comprehensive [KVision guide](https://kvision.gitbook.io/kvision-guide/) is published on GitBook.
diff --git a/build.gradle b/build.gradle
index cb32f60f..1c77ff6e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -138,6 +138,7 @@ if (!project.gradle.startParameter.taskNames.contains("dokka")) {
dependency("navigo", "7.0.0")
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
@@ -178,6 +179,7 @@ dokka {
'kvision-modules/kvision-server-jooby/src/main/kotlin',
'kvision-modules/kvision-server-ktor/src/main/kotlin',
'kvision-modules/kvision-server-spring-boot/src/main/kotlin',
+ 'kvision-modules/kvision-cordova/src/main/kotlin',
'kvision-modules/kvision-electron/src/main/kotlin')
classpath = [new File("dokka/kvision-dokka-helper.jar")]
outputFormat = 'html'
diff --git a/gradle.properties b/gradle.properties
index 3d8b64ac..e51a8eb4 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,8 +1,8 @@
group=pl.treksoft
-version=0.0.35
-kotlinVersion=1.3.30
+version=0.0.36
+kotlinVersion=1.3.31
javaVersion=1.8
-coroutinesVersion=1.2.0
+coroutinesVersion=1.2.1
serializationVersion=0.11.0
frontendPluginVersion=0.0.45
dokkaVersion=0.9.18
diff --git a/kvision-modules/kvision-bootstrap/build.gradle b/kvision-modules/kvision-bootstrap/build.gradle
index 5f5d0c49..6c504c17 100644
--- a/kvision-modules/kvision-bootstrap/build.gradle
+++ b/kvision-modules/kvision-bootstrap/build.gradle
@@ -11,6 +11,7 @@ kotlinFrontend {
dependency("bootstrap-vertical-tabs", "1.2.2")
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
index a0314ced..7633ed6c 100644
--- a/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
+++ b/kvision-modules/kvision-bootstrap/src/main/resources/css/style.css
@@ -163,3 +163,55 @@ ul.tabs-top > li {
float:none;
flex-shrink: 0;
}
+
+.kv-tab-close {
+ margin-left: 10px;
+ color: #000;
+ text-shadow: 0 1px 0 #fff;
+ filter: alpha(opacity=20);
+ opacity: 0.2;
+}
+
+.kv-tab-close:hover, .kv-tab-close:focus {
+ cursor: pointer;
+ filter: alpha(opacity=50);
+ opacity: 0.5;
+}
+
+select.form-control, .tabulator-row .tabulator-cell.tabulator-editing select {
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ background: transparent none no-repeat;
+ background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAKCAYAAABblxXYAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wUKFyIn4IjqJgAAAENJREFUKM/l0LERACEQQlGsiTa2px1aokGugNNAx8wfMy8AeLoBALYjaTqoKkga2+gKPgF/2Q7JkEx359oftu+C7/UBCUIcVQz0PvcAAAAASUVORK5CYII=');
+ background-position: right center;
+ cursor: pointer;
+}
+
+select.form-control:hover {
+ background-color: #e6e6e6;
+}
+
+select.form-control option {
+ background-color: white;
+}
+
+select.input-sm {
+ line-height: inherit;
+}
+
+.tabulator-row .tabulator-cell.tabulator-editing input, .tabulator-row .tabulator-cell.tabulator-editing select {
+ border: 1px solid #ccc;
+ border-radius: 4px;
+}
+
+.tabulator-row .tabulator-cell.tabulator-editing input:focus, .tabulator-row .tabulator-cell.tabulator-editing select:focus {
+ border-color: #66afe9;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
+}
+
+.tabulator-row .tabulator-cell.tabulator-editing {
+ border-right: 1px solid #1d68cd !important;
+ padding: 2px !important;
+}
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 2fc0320a..1ad97acc 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -88,7 +88,7 @@ interface WSpec : DomSpec {
fun runW(code: (widget: Widget, element: Element?) -> Unit) {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
widget.id = "test_id"
root.add(widget)
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt
index ff4daea7..af35fa51 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/dropdown/DropDownSpec.kt
@@ -34,7 +34,7 @@ class DropDownSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag")
root.add(dd)
dd.toggle()
@@ -51,7 +51,7 @@ class DropDownSpec : DomSpec {
@Test
fun renderDropUp() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag").apply { dropup = true }
root.add(dd)
dd.toggle()
@@ -68,7 +68,7 @@ class DropDownSpec : DomSpec {
@Test
fun renderHeaderElement() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val dd = DropDown("Dropdown", listOf("abc" to DD.HEADER.option), "flag")
root.add(dd)
dd.toggle()
@@ -85,7 +85,7 @@ class DropDownSpec : DomSpec {
@Test
fun renderSeparatorElement() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val dd = DropDown("Dropdown", listOf("abc" to DD.SEPARATOR.option), "flag")
root.add(dd)
dd.toggle()
@@ -102,7 +102,7 @@ class DropDownSpec : DomSpec {
@Test
fun renderDisabledElement() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val dd = DropDown("Dropdown", listOf("abc" to DD.DISABLED.option), "flag")
root.add(dd)
dd.toggle()
@@ -119,7 +119,7 @@ class DropDownSpec : DomSpec {
@Test
fun toggle() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val dd = DropDown("Dropdown", listOf("abc" to "#!/x", "def" to "#!/y"), "flag")
root.add(dd)
val visible = dd.getElementJQuery()?.hasClass("open") ?: false
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt
index 807f837a..c3d17de9 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/AlertSpec.kt
@@ -36,7 +36,7 @@ class AlertSpec : DomSpec {
@Test
fun render() {
run {
- Root("test", true)
+ Root("test", fixed = true)
Alert.show("Alert caption", "Alert content")
val alert = document.getElementById("test")?.let { jQuery(it).find(".modal")[0] }
assertNotNull(alert, "Should show alert window")
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt
index 2e3ea3ef..1893ce81 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/CloseIconSpec.kt
@@ -32,7 +32,7 @@ class CloseIconSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ci = CloseIcon()
root.add(ci)
val element = document.getElementById("test")
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt
index dc734e10..875bf9e2 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ConfirmSpec.kt
@@ -36,7 +36,7 @@ class ConfirmSpec : DomSpec {
@Test
fun render() {
run {
- Root("test", true)
+ Root("test", fixed = true)
Confirm.show("Confirm caption", "Confirm content")
val confirm = document.getElementById("test")?.let { jQuery(it).find(".modal")[0] }
assertNotNull(confirm, "Should show confirm window")
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt
index 523abfd5..7149b163 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/modal/ModalSpec.kt
@@ -35,7 +35,7 @@ class ModalSpec : DomSpec {
@Test
fun render() {
run {
- Root("test", true)
+ Root("test", fixed = true)
val modal = Modal("Modal")
modal.show()
val content = document.getElementById("test")?.let { jQuery(it).find(".modal-title").html() }
@@ -47,7 +47,7 @@ class ModalSpec : DomSpec {
@Test
fun toggle() {
run {
- Root("test", true)
+ Root("test", fixed = true)
val modal = Modal("Modal")
modal.toggle()
val content = document.getElementById("test")?.let { jQuery(it).find(".modal-title").html() }
diff --git a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt
index e87626ca..c79b9d32 100644
--- a/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt
+++ b/kvision-modules/kvision-bootstrap/src/test/kotlin/test/pl/treksoft/kvision/window/WindowSpec.kt
@@ -32,7 +32,7 @@ class WindowSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val window = Window("Window title", isResizable = false)
root.add(window)
val id = window.id
diff --git a/kvision-modules/kvision-chart/build.gradle b/kvision-modules/kvision-chart/build.gradle
index 4d4ba4df..d90de6ce 100644
--- a/kvision-modules/kvision-chart/build.gradle
+++ b/kvision-modules/kvision-chart/build.gradle
@@ -6,6 +6,7 @@ kotlinFrontend {
dependency("chart.js", "2.7.3")
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
fun runW(code: (widget: Widget, element: Element?) -> Unit) {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
widget.id = "test_id"
root.add(widget)
diff --git a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt
index 6fae54ad..da83b989 100644
--- a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt
+++ b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartCanvasSpec.kt
@@ -36,7 +36,7 @@ class ChartCanvasSpec : DomSpec {
@Test
fun renderResponsive() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val chart = ChartCanvas(
configuration = Configuration(
ChartType.SCATTER,
@@ -56,7 +56,7 @@ class ChartCanvasSpec : DomSpec {
@Test
fun renderNotResponsive() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val chart = ChartCanvas(
300, 600,
configuration = Configuration(
diff --git a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt
index 7974ea78..1fe73608 100644
--- a/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt
+++ b/kvision-modules/kvision-chart/src/test/kotlin/test/pl/treksoft/kvision/chart/ChartSpec.kt
@@ -37,7 +37,7 @@ class ChartSpec : DomSpec {
@Test
fun renderResponsive() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val chart = Chart(
Configuration(
ChartType.SCATTER,
@@ -60,7 +60,7 @@ class ChartSpec : DomSpec {
@Test
fun renderNotResponsive() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val chart = Chart(
Configuration(
ChartType.SCATTER,
diff --git a/kvision-modules/kvision-cordova/build.gradle b/kvision-modules/kvision-cordova/build.gradle
new file mode 100644
index 00000000..4aaef76d
--- /dev/null
+++ b/kvision-modules/kvision-cordova/build.gradle
@@ -0,0 +1,5 @@
+apply from: "../shared.gradle"
+
+dependencies {
+ compile "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutinesVersion"
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
new file mode 100644
index 00000000..656a519a
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Battery.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.cordova
+
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Battery status.
+ */
+external class BatteryStatus {
+ val level: Int
+ val isPlugged: Boolean
+}
+
+/**
+ * Main object for Cordova battery.
+ */
+object Battery {
+
+ /**
+ * Battery status event types.
+ */
+ enum class BatteryEvent(internal val type: String) {
+ BATTERY_STATUS("batterystatus"),
+ BATTERY_LOW("batterylow"),
+ BATTERY_CRITICAL("batterycritical")
+ }
+
+ /**
+ * Add listeners for battery status Cordova events.
+ */
+ fun addStatusListener(event: BatteryEvent, listener: (BatteryStatus) -> Unit) {
+ addDeviceReadyListener {
+ window.addEventListener(event.type, { status ->
+ @Suppress("UnsafeCastFromDynamic")
+ listener(status.asDynamic())
+ }, false)
+ }
+ }
+
+ /**
+ * Get battery status.
+ */
+ suspend fun getStatus(): BatteryStatus {
+ return suspendCoroutine { continuation ->
+ addStatusListener(Battery.BatteryEvent.BATTERY_STATUS) { status ->
+ continuation.resume(status)
+ }
+ }
+ }
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt
new file mode 100644
index 00000000..40c731c7
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Camera.kt
@@ -0,0 +1,270 @@
+/*
+ * 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.cordova
+
+import pl.treksoft.kvision.utils.obj
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Exception class for camera errors.
+ */
+class CameraException(message: String) : Exception(message)
+
+/**
+ * Main object for Cordova camera.
+ */
+object Camera {
+
+ private const val CAMERA_ACTIVE_STORAGE_KEY = "kv_camera_active_storage_key"
+ private const val CAMERA_STATUS_OK = "OK"
+
+ /**
+ * Camera destination types.
+ */
+ enum class DestinationType {
+ DATA_URL,
+ FILE_URI,
+ NATIVE_URI
+ }
+
+ /**
+ * Picture encoding types.
+ */
+ enum class EncodingType {
+ JPEG,
+ PNG
+ }
+
+ /**
+ * Picture/video media types.
+ */
+ enum class MediaType {
+ PICTURE,
+ VIDEO,
+ ALLMEDIA
+ }
+
+ /**
+ * Camera picture/video sources.
+ */
+ enum class PictureSourceType {
+ PHOTOLIBRARY,
+ CAMERA,
+ SAVEDPHOTOALBUM
+ }
+
+ /**
+ * Camera facing types.
+ */
+ enum class Direction {
+ BACK,
+ FRONT
+ }
+
+ /**
+ * iOS popover arrow directions.
+ */
+ enum class PopoverArrowDirection {
+ ARROW_UP,
+ ARROW_DOWN,
+ ARROW_LEFT,
+ ARROW_RIGHT,
+ ARROW_ANY
+ }
+
+ /**
+ * iOS popover options.
+ */
+ data class CameraPopoverOptions(
+ val x: Int,
+ val y: Int,
+ val width: Int,
+ val height: Int,
+ val arrowDir: PopoverArrowDirection,
+ val popoverWidth: Int,
+ val popoverHeight: Int
+ )
+
+ /**
+ * Suspending function to get picture from the camera.
+ *
+ * Note: On Android platform you must also use [addCameraResultCallback] listener.
+ *
+ * @param options camera options
+ * @return a [Result] class containing the picture or the exception
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun getPicture(options: CameraOptions): Result<String, CameraException> {
+ return suspendCoroutine { continuation ->
+ getPicture(options) {
+ continuation.resume(it)
+ }
+ }
+ }
+
+ /**
+ * A function to get picture from the camera.
+ *
+ * Note: On Android platform you must also use [addCameraResultCallback] listener.
+ *
+ * @param options camera options
+ * @param resultCallback a callback function to get the [Result], containing the picture or the exception
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ fun getPicture(options: CameraOptions, resultCallback: (Result<String, CameraException>) -> Unit) {
+ window.localStorage.setItem(CAMERA_ACTIVE_STORAGE_KEY, "true")
+ addDeviceReadyListener {
+ window.navigator.asDynamic().camera.getPicture({ image ->
+ window.localStorage.removeItem(CAMERA_ACTIVE_STORAGE_KEY)
+ resultCallback(Result.success(image))
+ }, { message ->
+ window.localStorage.removeItem(CAMERA_ACTIVE_STORAGE_KEY)
+ resultCallback(Result.error(CameraException(message)))
+ }, options.toJs())
+ }
+ }
+
+ /**
+ * An Android specific function to get picture from the camera after resume when the application
+ * webview intent is killed.
+ *
+ * @param resultCallback a callback function to get the [Result], containing the picture or the exception
+ */
+ fun addCameraResultCallback(resultCallback: (Result<String, CameraException>) -> Unit) {
+ addResumeListener { resumeEvent ->
+ val isCameraActive = window.localStorage.getItem(CAMERA_ACTIVE_STORAGE_KEY) == "true"
+ if (isCameraActive && resumeEvent.pendingResult != null) {
+ window.localStorage.removeItem(CAMERA_ACTIVE_STORAGE_KEY)
+ if (resumeEvent.pendingResult.pluginStatus == CAMERA_STATUS_OK) {
+ resultCallback(Result.success(resumeEvent.pendingResult.result))
+ } else {
+ resultCallback(Result.error(CameraException(resumeEvent.pendingResult.result)))
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes intermediate image files that are kept in the temporary storage after calling [getPicture].
+ *
+ * @param resultCallback an optional callback function to get the [Result] of the cleanup operation.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ fun cleanup(resultCallback: ((Result<String, CameraException>) -> Unit)? = null) {
+ addDeviceReadyListener {
+ window.navigator.asDynamic().camera.cleanup({
+ resultCallback?.invoke(Result.success(CAMERA_STATUS_OK))
+ }, { message ->
+ resultCallback?.invoke(Result.error(CameraException(message)))
+ })
+ }
+ }
+}
+
+internal fun Camera.DestinationType.toJs(): dynamic = when (this) {
+ Camera.DestinationType.DATA_URL -> js("Camera.DestinationType.DATA_URL")
+ Camera.DestinationType.FILE_URI -> js("Camera.DestinationType.FILE_URI")
+ Camera.DestinationType.NATIVE_URI -> js("Camera.DestinationType.NATIVE_URI")
+}
+
+internal fun Camera.EncodingType.toJs(): dynamic = when (this) {
+ Camera.EncodingType.JPEG -> js("Camera.EncodingType.JPEG")
+ Camera.EncodingType.PNG -> js("Camera.EncodingType.PNG")
+}
+
+internal fun Camera.MediaType.toJs(): dynamic = when (this) {
+ Camera.MediaType.PICTURE -> js("Camera.MediaType.PICTURE")
+ Camera.MediaType.VIDEO -> js("Camera.MediaType.VIDEO")
+ Camera.MediaType.ALLMEDIA -> js("Camera.MediaType.ALLMEDIA")
+}
+
+internal fun Camera.PictureSourceType.toJs(): dynamic = when (this) {
+ Camera.PictureSourceType.PHOTOLIBRARY -> js("Camera.PictureSourceType.PHOTOLIBRARY")
+ Camera.PictureSourceType.CAMERA -> js("Camera.PictureSourceType.CAMERA")
+ Camera.PictureSourceType.SAVEDPHOTOALBUM -> js("Camera.PictureSourceType.SAVEDPHOTOALBUM")
+}
+
+internal fun Camera.PopoverArrowDirection.toJs(): dynamic = when (this) {
+ Camera.PopoverArrowDirection.ARROW_UP -> js("Camera.PopoverArrowDirection.ARROW_UP")
+ Camera.PopoverArrowDirection.ARROW_DOWN -> js("Camera.PopoverArrowDirection.ARROW_DOWN")
+ Camera.PopoverArrowDirection.ARROW_LEFT -> js("Camera.PopoverArrowDirection.ARROW_LEFT")
+ Camera.PopoverArrowDirection.ARROW_RIGHT -> js("Camera.PopoverArrowDirection.ARROW_RIGHT")
+ Camera.PopoverArrowDirection.ARROW_ANY -> js("Camera.PopoverArrowDirection.ARROW_ANY")
+}
+
+internal fun Camera.Direction.toJs(): dynamic = when (this) {
+ Camera.Direction.BACK -> js("Camera.Direction.BACK")
+ Camera.Direction.FRONT -> js("Camera.Direction.FRONT")
+}
+
+internal external class CameraPopoverOptions(
+ x: Int,
+ y: Int,
+ width: Int,
+ height: Int,
+ arrowDir: dynamic,
+ popoverWidth: Int,
+ popoverHeight: Int
+)
+
+internal fun Camera.CameraPopoverOptions.toJs(): dynamic {
+ return CameraPopoverOptions(x, y, width, height, arrowDir.toJs(), popoverWidth, popoverHeight)
+}
+
+/**
+ * Camera options.
+ */
+data class CameraOptions(
+ val quality: Int? = null,
+ val destinationType: Camera.DestinationType? = null,
+ val sourceType: Camera.PictureSourceType? = null,
+ val allowEdit: Boolean? = null,
+ val encodingType: Camera.EncodingType? = null,
+ val targetWidth: Int? = null,
+ val targetHeight: Int? = null,
+ val mediaType: Camera.MediaType? = null,
+ val correctOrientation: Boolean? = null,
+ val saveToPhotoAlbum: Boolean? = null,
+ val popoverOptions: Camera.CameraPopoverOptions? = null,
+ val cameraDirection: Camera.Direction? = null
+)
+
+@Suppress("ComplexMethod")
+internal fun CameraOptions.toJs(): dynamic {
+ return obj {
+ if (quality != null) this.quality = quality
+ if (destinationType != null) this.destinationType = destinationType.toJs()
+ if (sourceType != null) this.sourceType = sourceType.toJs()
+ if (allowEdit != null) this.allowEdit = allowEdit
+ if (encodingType != null) this.encodingType = encodingType.toJs()
+ if (targetWidth != null) this.targetWidth = targetWidth
+ if (targetHeight != null) this.targetHeight = targetHeight
+ if (mediaType != null) this.mediaType = mediaType.toJs()
+ if (correctOrientation != null) this.correctOrientation = correctOrientation
+ if (saveToPhotoAlbum != null) this.saveToPhotoAlbum = saveToPhotoAlbum
+ if (popoverOptions != null) this.popoverOptions = popoverOptions.toJs()
+ if (cameraDirection != null) this.cameraDirection = cameraDirection.toJs()
+ }
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt
new file mode 100644
index 00000000..d83b79d4
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Device.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.cordova
+
+import org.w3c.dom.events.Event
+import kotlin.browser.document
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Device information class.
+ */
+external class Device {
+ val cordova: String
+ val model: String
+ val platform: String
+ val uuid: String
+ val version: String
+ val manufacturer: String
+ val isVirtual: Boolean
+ val serial: String
+}
+
+/**
+ * Pending result class.
+ */
+external class PendingResult {
+ val pluginServiceName: String
+ val pluginStatus: String
+ val result: dynamic
+}
+
+/**
+ * Resume event class.
+ */
+external class ResumeEvent {
+ val action: String?
+ val pendingResult: PendingResult?
+}
+
+/**
+ * Cordova event types.
+ */
+enum class CordovaEvent(internal val type: String) {
+ DEVICEREADY("deviceready"),
+ PAUSE("pause"),
+ RESUME("resume"),
+ BACKBUTTON("backbutton"),
+ MENUBUTTON("menubutton"),
+ SEARCHBUTTON("searchbutton"),
+ STARTCALLBUTTON("startcallbutton"),
+ ENDCALLBUTTON("endcallbutton"),
+ VOLUMEDOWNBUTTON("volumedownbutton"),
+ VOLUMEUPBUTTON("volumeupbutton"),
+ ACTIVATED("activated")
+}
+
+private external val device: Device
+
+/**
+ * Cordova device information object.
+ */
+var cordovaDevice: Device? = null
+ private set
+
+/**
+ * Add listeners for 'deviceready' Cordova event.
+ */
+fun addDeviceReadyListener(listener: (Device) -> Unit) {
+ document.addEventListener(CordovaEvent.DEVICEREADY.type, {
+ listener(device)
+ }, false)
+}
+
+/**
+ * Add listeners for 'pause' Cordova event.
+ */
+fun addPauseListener(listener: () -> Unit) {
+ addDeviceReadyListener {
+ document.addEventListener(CordovaEvent.PAUSE.type, {
+ listener()
+ }, false)
+ }
+}
+
+/**
+ * Add listeners for 'resume' Cordova event.
+ */
+fun addResumeListener(listener: (ResumeEvent) -> Unit) {
+ addDeviceReadyListener {
+ document.addEventListener(CordovaEvent.RESUME.type, { e ->
+ @Suppress("UnsafeCastFromDynamic")
+ listener(e.asDynamic())
+ }, false)
+ }
+}
+
+/**
+ * Add listeners for a Cordova events.
+ */
+fun addCordovaEventListener(event: CordovaEvent, listener: (Event) -> Unit) {
+ addDeviceReadyListener {
+ document.addEventListener(event.type, { e ->
+ listener(e)
+ }, false)
+ }
+}
+
+/**
+ * Suspending function to return device information object.
+ */
+suspend fun getDevice(): Device {
+ return cordovaDevice ?: suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ cordovaDevice = device
+ continuation.resume(device)
+ }
+ }
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt
new file mode 100644
index 00000000..6e7ba732
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/File.kt
@@ -0,0 +1,542 @@
+@file:Suppress(
+ "TooManyFunctions", "TooGenericExceptionCaught"
+)
+
+/*
+ * 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.cordova
+
+import org.khronos.webgl.ArrayBuffer
+import org.khronos.webgl.Uint8Array
+import org.khronos.webgl.set
+import org.w3c.files.Blob
+import org.w3c.files.BlobPropertyBag
+import org.w3c.files.FileReader
+import pl.treksoft.kvision.utils.obj
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Exception class for file errors.
+ */
+class FileException(code: Int) : Exception("Error: $code")
+
+/**
+ * System directories class.
+ */
+external class SystemDirs {
+ val applicationDirectory: String
+ val applicationStorageDirectory: String
+ val dataDirectory: String
+ val cacheDirectory: String
+ val externalApplicationStorageDirectory: String?
+ val externalDataDirectory: String?
+ val externalCacheDirectory: String?
+ val externalRootDirectory: String?
+ val tempDirectory: String?
+ val syncedDataDirectory: String?
+ val documentsDirectory: String?
+ val sharedDirectory: String?
+}
+
+/**
+ * Main object for Cordova file.
+ */
+object File {
+
+ const val NOT_FOUND_ERR = 1
+ const val SECURITY_ERR = 2
+ const val ABORT_ERR = 3
+ const val NOT_READABLE_ERR = 4
+ const val ENCODING_ERR = 5
+ const val NO_MODIFICATION_ALLOWED_ERR = 6
+ const val INVALID_STATE_ERR = 7
+ const val SYNTAX_ERR = 8
+ const val INVALID_MODIFICATION_ERR = 9
+ const val QUOTA_EXCEEDED_ERR = 10
+ const val TYPE_MISMATCH_ERR = 11
+ const val PATH_EXISTS_ERR = 12
+
+ /**
+ * File system types.
+ */
+ enum class FileSystemType {
+ TEMPORARY,
+ PERSISTENT
+ }
+
+ /**
+ * Get system directories.
+ */
+ suspend fun getSystemDirectories(): SystemDirs {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ continuation.resume(window.asDynamic().cordova.file)
+ }
+ }
+ }
+
+ /**
+ * Resolve given path to a file or directory entry.
+ * @param url directory path
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun resolveLocalFileSystemURL(url: String): Result<Entry, FileException> {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ window.asDynamic().resolveLocalFileSystemURL(url, { entry ->
+ continuation.resume(Result.success(entry))
+ }, { error ->
+ continuation.resume(Result.error(FileException(error.code)))
+ })
+ }
+ }
+ }
+
+ /**
+ * Resolve given path to a directory entry.
+ * @param url directory path
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun resolveLocalFileSystemURLForDir(url: String): Result<DirectoryEntry, FileException> {
+ return when (val result = resolveLocalFileSystemURL(url)) {
+ is Result.Success -> {
+ if (result.value.isDirectory) {
+ result.map {
+ @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
+ it as DirectoryEntry
+ }
+ } else {
+ Result.error(FileException(TYPE_MISMATCH_ERR))
+ }
+ }
+ is Result.Failure -> result
+ }
+ }
+
+ /**
+ * Resolve given path to a file entry.
+ * @param url file path
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun resolveLocalFileSystemURLForFile(url: String): Result<FileEntry, FileException> {
+ return when (val result = resolveLocalFileSystemURL(url)) {
+ is Result.Success -> {
+ if (result.value.isFile) {
+ result.map {
+ @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
+ it as FileEntry
+ }
+ } else {
+ Result.error(FileException(TYPE_MISMATCH_ERR))
+ }
+ }
+ is Result.Failure -> result
+ }
+ }
+
+ /**
+ * Request a file system of a given type.
+ * @param fileSystemType file system type (temporary or persistent)
+ * @param size file system size (quota)
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun requestFileSystem(
+ fileSystemType: File.FileSystemType,
+ size: Long = 0
+ ): Result<FileSystem, FileException> {
+ return suspendCoroutine { continuation ->
+ val type = when (fileSystemType) {
+ File.FileSystemType.TEMPORARY -> LocalFileSystem.TEMPORARY
+ File.FileSystemType.PERSISTENT -> LocalFileSystem.PERSISTENT
+ }
+ addDeviceReadyListener {
+ window.asDynamic().requestFileSystem(type, size, { fs ->
+ continuation.resume(Result.success(fs))
+ }, { error ->
+ continuation.resume(Result.error(FileException(error.code)))
+ })
+ }
+ }
+ }
+
+ internal fun dataURLtoBlob(dataURI: String): Blob {
+ val byteString = window.atob(dataURI.split(',')[1])
+ val mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
+ val ab = ArrayBuffer(byteString.length)
+ val ia = Uint8Array(ab)
+ for (i in 0 until byteString.length) {
+ ia[i] = byteString[i].toByte()
+ }
+ return Blob(arrayOf(ab), BlobPropertyBag(type = mimeString))
+ }
+
+}
+
+/**
+ * Extension function to convert String to a directory entry.
+ */
+suspend fun String.toDirectoryEntry(): Result<DirectoryEntry, FileException> {
+ return File.resolveLocalFileSystemURLForDir(this)
+}
+
+/**
+ * Extension function to convert String to a file entry.
+ */
+suspend fun String.toFileEntry(): Result<FileEntry, FileException> {
+ return File.resolveLocalFileSystemURLForFile(this)
+}
+
+/**
+ * Get file or directory metadata.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun Entry.getMetadata(): Result<Metadata, FileException> {
+ return suspendCoroutine { continuation ->
+ this.getMetadata({ metadata: Metadata ->
+ continuation.resume(Result.success(metadata))
+ } as MetadataCallback, { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ } as ErrorCallback)
+ }
+}
+
+/**
+ * Get file or directory parent directory entry.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun Entry.getParent(): Result<DirectoryEntry, FileException> {
+ return suspendCoroutine { continuation ->
+ this.getParent({ directoryEntry: DirectoryEntry ->
+ continuation.resume(Result.success(directoryEntry))
+ } as DirectoryEntryCallback, { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ } as ErrorCallback)
+ }
+}
+
+/**
+ * Remove given file or directory.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun Entry.remove(): Result<Entry, FileException> {
+ return suspendCoroutine { continuation ->
+ this.remove({
+ continuation.resume(Result.success(this))
+ } as VoidCallback, { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ } as ErrorCallback)
+ }
+}
+
+/**
+ * Move given file or directory to a new location.
+ * @param parent new location parent directory entry
+ * @param newName new location name
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun Entry.moveTo(parent: DirectoryEntry, newName: String? = null): Result<Entry, FileException> {
+ return suspendCoroutine { continuation ->
+ this.moveTo(parent, newName, { entry: Entry ->
+ continuation.resume(Result.success(entry))
+ } as EntryCallback, { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ } as ErrorCallback)
+ }
+}
+
+/**
+ * Copy given file or directory to a new location.
+ * @param parent new location parent directory entry
+ * @param newName new location name
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun Entry.copyTo(parent: DirectoryEntry, newName: String? = null): Result<Entry, FileException> {
+ return suspendCoroutine { continuation ->
+ this.copyTo(parent, newName, { entry: Entry ->
+ continuation.resume(Result.success(entry))
+ } as EntryCallback, { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ } as ErrorCallback)
+ }
+}
+
+/**
+ * Create a FileWriter object for a given file entry.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun FileEntry.createWriter(): Result<FileWriter, FileException> {
+ return suspendCoroutine { continuation ->
+ this.createWriter({ fileWriter: FileWriter ->
+ continuation.resume(Result.success(fileWriter))
+ } as FileWriterCallback, { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ } as ErrorCallback)
+ }
+}
+
+/**
+ * Get a File object for a given file entry.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun FileEntry.file(): Result<org.w3c.files.File, FileException> {
+ return suspendCoroutine { continuation ->
+ this.file({ file: org.w3c.files.File ->
+ continuation.resume(Result.success(file))
+ } as FileCallback, { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ } as ErrorCallback)
+ }
+}
+
+/**
+ * Get or create a file in a given parent directory.
+ * @param path target file path
+ * @param create Used to indicate that the user wants to create a file if it was not previously there.
+ * @param exclusive Fail if the target path file already exists.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun DirectoryEntry.getFile(
+ path: String,
+ create: Boolean = true,
+ exclusive: Boolean = false
+): Result<FileEntry, FileException> {
+ return suspendCoroutine { continuation ->
+ this.getFile(path, obj {
+ this.create = create
+ this.exclusive = exclusive
+ }, { fileEntry: FileEntry ->
+ continuation.resume(Result.success(fileEntry))
+ } as FileEntryCallback, { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ } as ErrorCallback)
+ }
+}
+
+/**
+ * Get or create a directory in a given parent directory.
+ * @param path target directory path
+ * @param create Used to indicate that the user wants to create a directory if it was not previously there.
+ * @param exclusive Fail if the target path directory already exists.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun DirectoryEntry.getDirectory(
+ path: String,
+ create: Boolean = true,
+ exclusive: Boolean = false
+): Result<DirectoryEntry, FileException> {
+ return suspendCoroutine { continuation ->
+ this.getDirectory(path, obj {
+ this.create = create
+ this.exclusive = exclusive
+ }, { directoryEntry: DirectoryEntry ->
+ continuation.resume(Result.success(directoryEntry))
+ } as DirectoryEntryCallback, { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ } as ErrorCallback)
+ }
+}
+
+/**
+ * Remove given directory recursively.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun DirectoryEntry.removeRecursively(): Result<DirectoryEntry, FileException> {
+ return suspendCoroutine { continuation ->
+ this.removeRecursively({
+ continuation.resume(Result.success(this))
+ } as VoidCallback, { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ } as ErrorCallback)
+ }
+}
+
+/**
+ * List directory entries for a given DirectoryReader.
+ */
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "UnsafeCastFromDynamic")
+suspend fun DirectoryReader.readEntries(): Result<List<Entry>, FileException> {
+ return suspendCoroutine { continuation ->
+ this.readEntries({ entries: Array<Entry> ->
+ continuation.resume(Result.success(entries.toList()))
+ } as EntriesCallback, { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ } as ErrorCallback)
+ }
+}
+
+/**
+ * List directory entries for a given parent directory entry.
+ */
+suspend fun DirectoryEntry.readEntries(): Result<List<Entry>, FileException> {
+ return this.createReader().readEntries()
+}
+
+/**
+ * Read file content as a plain string.
+ */
+@Suppress("UnsafeCastFromDynamic")
+suspend fun FileEntry.readAsText(): Result<String, FileException> {
+ return this.file().flatMap { file ->
+ suspendCoroutine<Result<String, FileException>> { continuation ->
+ val reader = FileReader()
+ reader.onloadend = { e ->
+ continuation.resume(Result.success(e.target.asDynamic().result))
+ }
+ reader.onerror = { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ }
+ reader.readAsText(file)
+ }
+ }
+}
+
+/**
+ * Read file content as a data url.
+ */
+@Suppress("UnsafeCastFromDynamic")
+suspend fun FileEntry.readAsDataURL(): Result<String, FileException> {
+ return this.file().flatMap { file ->
+ suspendCoroutine<Result<String, FileException>> { continuation ->
+ val reader = FileReader()
+ reader.onloadend = { e ->
+ continuation.resume(Result.success(e.target.asDynamic().result))
+ }
+ reader.onerror = { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ }
+ reader.readAsDataURL(file)
+ }
+ }
+}
+
+/**
+ * Read file content as an array buffer.
+ */
+@Suppress("UnsafeCastFromDynamic")
+suspend fun FileEntry.readAsArrayBuffer(): Result<ArrayBuffer, FileException> {
+ return this.file().flatMap { file ->
+ suspendCoroutine<Result<ArrayBuffer, FileException>> { continuation ->
+ val reader = FileReader()
+ reader.onloadend = { e ->
+ continuation.resume(Result.success(e.target.asDynamic().result))
+ }
+ reader.onerror = { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ }
+ reader.onabort = { _ ->
+ continuation.resume(Result.error(FileException(File.ABORT_ERR)))
+ }
+ reader.readAsArrayBuffer(file)
+ }
+ }
+}
+
+/**
+ * Write file content from a Blob.
+ * @param data a data Blob to be written.
+ */
+@Suppress("UnsafeCastFromDynamic")
+suspend fun FileEntry.write(data: Blob): Result<FileEntry, FileException> {
+ return this.createWriter().flatMap { writer ->
+ suspendCoroutine<Result<FileEntry, FileException>> { continuation ->
+ writer.onwriteend = {
+ continuation.resume(Result.success(this))
+ }
+ writer.onerror = { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ }
+ writer.onabort = { _ ->
+ continuation.resume(Result.error(FileException(File.ABORT_ERR)))
+ }
+ writer.write(data)
+ }
+ }
+}
+
+/**
+ * Write file content from a plain string.
+ * @param data a data string to be written.
+ */
+suspend fun FileEntry.write(data: String): Result<FileEntry, FileException> {
+ return this.write(Blob(arrayOf(data)))
+}
+
+/**
+ * Write file content from a data url.
+ * @param dataUrl a data url to be written.
+ */
+suspend fun FileEntry.writeDataUrL(dataUrl: String): Result<FileEntry, FileException> {
+ return this.write(File.dataURLtoBlob(dataUrl))
+}
+
+/**
+ * Write file content from an array buffer.
+ * @param data an array buffer to be written.
+ */
+suspend fun FileEntry.write(data: ArrayBuffer): Result<FileEntry, FileException> {
+ return this.write(data.toBlob())
+}
+
+/**
+ * Append file content from a Blob.
+ * @param data a data Blob to be appended.
+ */
+@Suppress("UnsafeCastFromDynamic")
+suspend fun FileEntry.append(data: Blob): Result<FileEntry, FileException> {
+ return this.createWriter().flatMap { writer ->
+ try {
+ writer.seek(writer.length)
+ } catch (e: Exception) {
+ console.log("File doesn't exist!")
+ }
+ suspendCoroutine<Result<FileEntry, FileException>> { continuation ->
+ writer.onwriteend = {
+ continuation.resume(Result.success(this))
+ }
+ writer.onerror = { error: dynamic ->
+ continuation.resume(Result.error(FileException(error.code)))
+ }
+ writer.onabort = { _ ->
+ continuation.resume(Result.error(FileException(File.ABORT_ERR)))
+ }
+ writer.write(data)
+ }
+ }
+}
+
+/**
+ * Append file content from a plain string.
+ * @param data a string to be appended.
+ */
+suspend fun FileEntry.append(data: String): Result<FileEntry, FileException> {
+ return this.append(Blob(arrayOf(data)))
+}
+
+/**
+ * Convert array buffer to a blob.
+ */
+fun ArrayBuffer.toBlob(): Blob {
+ return Blob(arrayOf(Uint8Array(this)))
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt
new file mode 100644
index 00000000..48f4f3d2
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileSystem.kt
@@ -0,0 +1,221 @@
+@file:Suppress(
+ "INTERFACE_WITH_SUPERCLASS",
+ "OVERRIDING_FINAL_MEMBER",
+ "RETURN_TYPE_MISMATCH_ON_OVERRIDE",
+ "CONFLICTING_OVERLOADS",
+ "EXTERNAL_DELEGATION",
+ "NESTED_CLASS_IN_EXTERNAL_INTERFACE",
+ "unused", "PropertyName", "TooManyFunctions", "VariableNaming", "MaxLineLength"
+)
+
+package pl.treksoft.kvision.cordova
+
+import org.w3c.files.File
+import kotlin.js.Date
+
+external object LocalFileSystem {
+ var TEMPORARY: Number
+ var PERSISTENT: Number
+ fun requestFileSystem(
+ type: Number,
+ size: Number,
+ successCallback: FileSystemCallback,
+ errorCallback: ErrorCallback? = definedExternally /* null */
+ )
+
+ fun resolveLocalFileSystemURL(
+ url: String,
+ successCallback: EntryCallback,
+ errorCallback: ErrorCallback? = definedExternally /* null */
+ )
+
+ fun webkitRequestFileSystem(
+ type: Number,
+ size: Number,
+ successCallback: FileSystemCallback,
+ errorCallback: ErrorCallback? = definedExternally /* null */
+ )
+}
+
+external object LocalFileSystemSync {
+ var TEMPORARY: Number
+ var PERSISTENT: Number
+ fun requestFileSystemSync(type: Number, size: Number): FileSystemSync
+ fun resolveLocalFileSystemSyncURL(url: String): EntrySync
+ fun webkitRequestFileSystemSync(type: Number, size: Number): FileSystemSync
+}
+
+external interface Metadata {
+ var modificationTime: Date
+ var size: Number
+}
+
+external interface Flags {
+ var create: Boolean? get() = definedExternally; set(value) = definedExternally
+ var exclusive: Boolean? get() = definedExternally; set(value) = definedExternally
+}
+
+external interface FileSystem {
+ var name: String
+ var root: DirectoryEntry
+}
+
+external interface Entry {
+ var isFile: Boolean
+ var isDirectory: Boolean
+ fun getMetadata(successCallback: MetadataCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+ var name: String
+ var fullPath: String
+ var filesystem: FileSystem
+ fun moveTo(
+ parent: DirectoryEntry,
+ newName: String? = definedExternally /* null */,
+ successCallback: EntryCallback? = definedExternally /* null */,
+ errorCallback: ErrorCallback? = definedExternally /* null */
+ )
+
+ fun copyTo(
+ parent: DirectoryEntry,
+ newName: String? = definedExternally /* null */,
+ successCallback: EntryCallback? = definedExternally /* null */,
+ errorCallback: ErrorCallback? = definedExternally /* null */
+ )
+
+ fun toURL(): String
+ fun toInternalURL(): String
+ fun remove(successCallback: VoidCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+ fun getParent(successCallback: DirectoryEntryCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+}
+
+external interface DirectoryEntry : Entry {
+ fun createReader(): DirectoryReader
+ fun getFile(
+ path: String,
+ options: Flags? = definedExternally /* null */,
+ successCallback: FileEntryCallback?,
+ errorCallback: ErrorCallback? = definedExternally /* null */
+ )
+
+ fun getDirectory(
+ path: String,
+ options: Flags? = definedExternally /* null */,
+ successCallback: DirectoryEntryCallback?,
+ errorCallback: ErrorCallback? = definedExternally /* null */
+ )
+
+ fun removeRecursively(successCallback: VoidCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+}
+
+external interface DirectoryReader {
+ fun readEntries(successCallback: EntriesCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+}
+
+external interface FileEntry : Entry {
+ fun createWriter(successCallback: FileWriterCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+ fun file(successCallback: FileCallback, errorCallback: ErrorCallback? = definedExternally /* null */)
+}
+
+external interface FileSystemCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun FileSystemCallback.invoke(filesystem: FileSystem) {
+ asDynamic()(filesystem)
+}
+
+external interface EntryCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun EntryCallback.invoke(entry: Entry) {
+ asDynamic()(entry)
+}
+
+external interface FileEntryCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun FileEntryCallback.invoke(entry: FileEntry) {
+ asDynamic()(entry)
+}
+
+external interface DirectoryEntryCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun DirectoryEntryCallback.invoke(entry: DirectoryEntry) {
+ asDynamic()(entry)
+}
+
+external interface EntriesCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun EntriesCallback.invoke(entries: Array<Entry>) {
+ asDynamic()(entries)
+}
+
+external interface MetadataCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun MetadataCallback.invoke(metadata: Metadata) {
+ asDynamic()(metadata)
+}
+
+external interface FileWriterCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun FileWriterCallback.invoke(fileWriter: FileWriter) {
+ asDynamic()(fileWriter)
+}
+
+external interface FileCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun FileCallback.invoke(file: File) {
+ asDynamic()(file)
+}
+
+external interface VoidCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun VoidCallback.invoke() {
+ asDynamic()()
+}
+
+external interface ErrorCallback
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun ErrorCallback.invoke(err: Error) {
+ asDynamic()(err)
+}
+
+external interface FileSystemSync {
+ var name: String
+ var root: DirectoryEntrySync
+}
+
+external interface EntrySync {
+ var isFile: Boolean
+ var isDirectory: Boolean
+ fun getMetadata(): Metadata
+ var name: String
+ var fullPath: String
+ var filesystem: FileSystemSync
+ fun moveTo(parent: DirectoryEntrySync, newName: String? = definedExternally /* null */): EntrySync
+ fun copyTo(parent: DirectoryEntrySync, newName: String? = definedExternally /* null */): EntrySync
+ fun toURL(): String
+ fun remove()
+ fun getParent(): DirectoryEntrySync
+}
+
+external interface DirectoryEntrySync : EntrySync {
+ fun createReader(): DirectoryReaderSync
+ fun getFile(path: String, options: Flags? = definedExternally /* null */): FileEntrySync
+ fun getDirectory(path: String, options: Flags? = definedExternally /* null */): DirectoryEntrySync
+ fun removeRecursively()
+}
+
+external interface DirectoryReaderSync {
+ fun readEntries(): Array<EntrySync>
+}
+
+external interface FileEntrySync : EntrySync {
+ fun createWriter(): FileWriterSync
+ fun file(): File
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt
new file mode 100644
index 00000000..26215aa8
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/FileWriter.kt
@@ -0,0 +1,47 @@
+@file:Suppress(
+ "INTERFACE_WITH_SUPERCLASS",
+ "OVERRIDING_FINAL_MEMBER",
+ "RETURN_TYPE_MISMATCH_ON_OVERRIDE",
+ "CONFLICTING_OVERLOADS",
+ "EXTERNAL_DELEGATION",
+ "NESTED_CLASS_IN_EXTERNAL_INTERFACE",
+ "unused", "PropertyName", "TooManyFunctions", "VariableNaming", "MaxLineLength"
+)
+
+package pl.treksoft.kvision.cordova
+
+import org.w3c.dom.events.EventTarget
+import org.w3c.files.Blob
+import org.w3c.xhr.ProgressEvent
+
+external interface FileSaver : EventTarget {
+ fun abort()
+ var INIT: Number
+ var WRITING: Number
+ var DONE: Number
+ var readyState: Number
+ var error: Error
+ var onwritestart: (event: ProgressEvent) -> Unit
+ var onprogress: (event: ProgressEvent) -> Unit
+ var onwrite: (event: ProgressEvent) -> Unit
+ var onabort: (event: ProgressEvent) -> Unit
+ var onerror: (event: ProgressEvent) -> Unit
+ var onwriteend: (event: ProgressEvent) -> Unit
+
+}
+
+external interface FileWriter : FileSaver {
+ var position: Number
+ var length: Number
+ fun write(data: Blob)
+ fun seek(offset: Number)
+ fun truncate(size: Number)
+}
+
+external interface FileWriterSync {
+ var position: Number
+ var length: Number
+ fun write(data: Blob)
+ fun seek(offset: Number)
+ fun truncate(size: Number)
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt
new file mode 100644
index 00000000..c4ccd354
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Geolocation.kt
@@ -0,0 +1,264 @@
+/*
+ * 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.cordova
+
+import pl.treksoft.kvision.utils.obj
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Geolocation coordinates values.
+ */
+external class Coordinates {
+ val latitude: Number
+ val longitude: Number
+ val altitude: Number
+ val accuracy: Number
+ val altitudeAccuracy: Number?
+ val heading: Number?
+ val speed: Number
+}
+
+/**
+ * Geolocation position value.
+ */
+external class Position {
+ val coords: Coordinates
+ val timestamp: Number
+}
+
+/**
+ * Geolocaton error codes.
+ */
+enum class PositionError {
+ PERMISSION_DENIED,
+ POSITION_UNAVAILABLE,
+ TIMEOUT
+}
+
+private fun codeToEnum(code: Int): PositionError {
+ return when (code) {
+ 1 -> PositionError.PERMISSION_DENIED
+ 2 -> PositionError.POSITION_UNAVAILABLE
+ else -> PositionError.TIMEOUT
+ }
+}
+
+/**
+ * Exception class for geolocation errors.
+ */
+class GeolocationException(val code: PositionError, message: String) : Exception(message)
+
+/**
+ * Main geolocation object based on webview api.
+ */
+object Geolocation {
+
+ /**
+ * Get current location.
+ * @param enableHighAccuracy provides a hint that the application needs the best possible results
+ * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result
+ * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun getCurrentPosition(
+ enableHighAccuracy: Boolean = true,
+ timeout: Number? = null,
+ maximumAge: Number? = null
+ ): Result<Position, GeolocationException> {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ window.navigator.asDynamic().geolocation.getCurrentPosition({ position: Position ->
+ continuation.resume(Result.success(position))
+ }, { error ->
+ continuation.resume(Result.error(GeolocationException(codeToEnum(error.code), error.message)))
+ }, obj {
+ this.enableHighAccuracy = enableHighAccuracy
+ if (timeout != null) this.timeout = timeout
+ if (maximumAge != null) this.maximumAge = maximumAge
+ })
+ }
+ }
+ }
+
+ /**
+ * Watch location changes.
+ * @param enableHighAccuracy provides a hint that the application needs the best possible results
+ * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result
+ * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds
+ * @param resultCallback a callback function receiving the result
+ * @return watch identifier (can be removed with a [clearWatch] function)
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun watchPosition(
+ enableHighAccuracy: Boolean = true,
+ timeout: Number? = null,
+ maximumAge: Number? = null,
+ resultCallback: (Result<Position, GeolocationException>) -> Unit
+ ): String? {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ val watchId = window.navigator.asDynamic().geolocation.watchPosition({ position: Position ->
+ resultCallback(Result.success(position))
+ }, { error ->
+ resultCallback(Result.error(GeolocationException(codeToEnum(error.code), error.message)))
+ }, obj {
+ this.enableHighAccuracy = enableHighAccuracy
+ if (timeout != null) this.timeout = timeout
+ if (maximumAge != null) this.maximumAge = maximumAge
+ })
+ continuation.resume(watchId)
+ }
+ }
+ }
+
+ /**
+ * Clear the given watch.
+ * @param watchId watch identifier returned from [watchPosition] function
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ fun clearWatch(watchId: String) {
+ if (window.navigator.asDynamic().geolocation != null) {
+ window.navigator.asDynamic().geolocation.clearWatch(watchId)
+ }
+ }
+
+}
+
+/**
+ * Main geolocation object based on Google location services api.
+ */
+object Locationservices {
+
+ /**
+ * Location services priority.
+ */
+ enum class Priority {
+ PRIORITY_HIGH_ACCURACY,
+ PRIORITY_BALANCED_POWER_ACCURACY,
+ PRIORITY_LOW_POWER,
+ PRIORITY_NO_POWER
+ }
+
+ /**
+ * Get current location.
+ * @param enableHighAccuracy provides a hint that the application needs the best possible results
+ * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result
+ * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds
+ * @param priority the priority of the request is a strong hint for which location sources to use
+ * @param interval set the desired interval for active location updates, in milliseconds
+ * @param fastInterval explicitly set the fastest interval for location updates, in milliseconds
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun getCurrentPosition(
+ enableHighAccuracy: Boolean = true,
+ timeout: Number? = null,
+ maximumAge: Number? = null,
+ priority: Priority? = null,
+ interval: Number? = null,
+ fastInterval: Number? = null
+ ): Result<Position, GeolocationException> {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ window.asDynamic()
+ .cordova.plugins.locationServices.geolocation.getCurrentPosition({ position: Position ->
+ continuation.resume(Result.success(position))
+ }, { error ->
+ continuation.resume(Result.error(GeolocationException(codeToEnum(error.code), error.message)))
+ }, obj {
+ this.enableHighAccuracy = enableHighAccuracy
+ if (timeout != null) this.timeout = timeout
+ if (maximumAge != null) this.maximumAge = maximumAge
+ if (priority != null) this.priority = getJsPriority(priority)
+ if (interval != null) this.interval = interval
+ if (fastInterval != null) this.fastInterval = fastInterval
+ })
+ }
+ }
+ }
+
+ /**
+ * Watch location changes.
+ * @param enableHighAccuracy provides a hint that the application needs the best possible results
+ * @param timeout the maximum length of time (milliseconds) that is allowed to pass before getting the result
+ * @param maximumAge accept a cached position whose age is no greater than the specified time in milliseconds
+ * @param priority the priority of the request is a strong hint for which location sources to use
+ * @param interval set the desired interval for active location updates, in milliseconds
+ * @param fastInterval explicitly set the fastest interval for location updates, in milliseconds
+ * @param resultCallback a callback function receiving the result
+ * @return watch identifier (can be removed with a [clearWatch] function)
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun watchPosition(
+ enableHighAccuracy: Boolean = true,
+ timeout: Number? = null,
+ maximumAge: Number? = null,
+ priority: Priority? = null,
+ interval: Number? = null,
+ fastInterval: Number? = null,
+ resultCallback: (Result<Position, GeolocationException>) -> Unit
+ ): String? {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ val watchId =
+ window.asDynamic()
+ .cordova.plugins.locationServices.geolocation.watchPosition({ position: Position ->
+ resultCallback(Result.success(position))
+ }, { error ->
+ resultCallback(Result.error(GeolocationException(codeToEnum(error.code), error.message)))
+ }, obj {
+ this.enableHighAccuracy = enableHighAccuracy
+ if (timeout != null) this.timeout = timeout
+ if (maximumAge != null) this.maximumAge = maximumAge
+ if (priority != null) this.priority = getJsPriority(priority)
+ if (interval != null) this.interval = interval
+ if (fastInterval != null) this.fastInterval = fastInterval
+ })
+ continuation.resume(watchId)
+ }
+ }
+ }
+
+ /**
+ * Clear the given watch.
+ * @param watchId watch identifier returned from [watchPosition] function
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ fun clearWatch(watchId: String) {
+ if (window.asDynamic().cordova.plugins.locationServices.geolocation != null) {
+ window.asDynamic().cordova.plugins.locationServices.geolocation.clearWatch(watchId)
+ }
+ }
+
+ @Suppress("MagicNumber")
+ private fun getJsPriority(priority: Priority): dynamic {
+ return when (priority) {
+ Priority.PRIORITY_HIGH_ACCURACY -> 100
+ Priority.PRIORITY_BALANCED_POWER_ACCURACY -> 102
+ Priority.PRIORITY_LOW_POWER -> 104
+ Priority.PRIORITY_NO_POWER -> 105
+ }
+ }
+
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt
new file mode 100644
index 00000000..c0c8f0b4
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/InAppBrowser.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.cordova
+
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * InAppBrowser event type.
+ */
+external class InAppBrowserEvent {
+ val type: String
+ val url: String
+ val code: Number?
+ val message: String?
+ val data: String?
+}
+
+/**
+ * InAppBrowser reference object.
+ */
+external class Browser {
+ fun addEventListener(eventname: String, callback: (InAppBrowserEvent) -> Unit)
+ fun removeEventListener(eventname: String, callback: (InAppBrowserEvent) -> Unit)
+ fun close()
+ fun show()
+ fun hide()
+ fun executeScript(details: dynamic, callback: ((dynamic) -> Unit) = definedExternally)
+ fun insertCSS(details: dynamic, callback: (() -> Unit) = definedExternally)
+}
+
+/**
+ * Main object for Cordova InAppBrowser api.
+ */
+object InAppBrowser {
+
+ /**
+ * Open new browser window.
+ * @param url an URL address
+ * @param target a target window
+ * @param options a string with window options
+ * @return a browser window reference
+ */
+ suspend fun open(url: String, target: String = "_blank", options: String? = null): Browser {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ val ref = window.asDynamic().cordova.InAppBrowser.open(url, target, options)
+ continuation.resume(ref)
+ }
+ }
+ }
+
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt
new file mode 100644
index 00000000..a126941b
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Media.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.cordova
+
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Media error class.
+ */
+external class MediaError {
+ val code: Int
+ val message: String
+
+ companion object {
+ val MEDIA_ERR_ABORTED: Int
+ val MEDIA_ERR_NETWORK: Int
+ val MEDIA_ERR_DECODE: Int
+ val MEDIA_ERR_NONE_SUPPORTED: Int
+ }
+}
+
+/**
+ * Cordova media class.
+ */
+@Suppress("TooManyFunctions")
+external class Media(
+ src: String,
+ mediaSuccess: (() -> Unit)? = definedExternally,
+ mediaError: ((MediaError) -> Unit)? = definedExternally,
+ mediaStatus: ((Int) -> Unit)? = definedExternally
+) {
+
+ fun play()
+ fun pause()
+ fun stop()
+ fun startRecord()
+ fun pauseRecord()
+ fun resumeRecord()
+ fun stopRecord()
+ fun seekTo(millis: Number)
+ fun setRate(rate: Number)
+ fun setVolume(volume: Number)
+ fun getDuration(): Number
+ fun getCurrentAmplitude(mediaSuccess: (Number) -> Unit, mediaError: ((Int) -> Unit)? = definedExternally)
+ fun getCurrentPosition(mediaSuccess: (Number) -> Unit, mediaError: ((Int) -> Unit)? = definedExternally)
+ fun release()
+
+ val position: Number
+ val duration: Number
+
+ companion object {
+ val MEDIA_NONE: Int
+ val MEDIA_STARTING: Int
+ val MEDIA_RUNNING: Int
+ val MEDIA_PAUSED: Int
+ val MEDIA_STOPPED: Int
+ }
+}
+
+/**
+ * Returns the current amplitude within an audio file.
+ */
+suspend fun Media.getCurrentAmplitude(): Number {
+ return suspendCoroutine { continuation ->
+ this.getCurrentAmplitude({ amp ->
+ continuation.resume(amp)
+ }, {
+ continuation.resume(-1)
+ })
+ }
+}
+
+/**
+ * Returns the current position within an audio file.
+ */
+suspend fun Media.getCurrentPosition(): Number {
+ return suspendCoroutine { continuation ->
+ this.getCurrentPosition({ pos ->
+ continuation.resume(pos)
+ }, {
+ continuation.resume(-1)
+ })
+ }
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt
new file mode 100644
index 00000000..fda81da7
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/MediaCapture.kt
@@ -0,0 +1,197 @@
+/*
+ * 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.cordova
+
+import pl.treksoft.kvision.utils.obj
+import kotlin.browser.document
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Media error class.
+ */
+internal external class CaptureError {
+ val code: Int
+
+ companion object {
+ val CAPTURE_INTERNAL_ERR: Int
+ val CAPTURE_APPLICATION_BUSY: Int
+ val CAPTURE_INVALID_ARGUMENT: Int
+ val CAPTURE_NO_MEDIA_FILES: Int
+ val CAPTURE_PERMISSION_DENIED: Int
+ val CAPTURE_NOT_SUPPORTED: Int
+ }
+}
+
+/**
+ * Media file details class.
+ */
+external class MediaFileData {
+ val codecs: String?
+ val bitrate: Number
+ val height: Number
+ val width: Number
+ val duration: Number
+}
+
+/**
+ * Media file information class.
+ */
+external class MediaFile {
+ val name: String
+ val fullPath: String
+ val type: String
+ val lastModifiedDate: Number
+ val size: Number
+
+ fun getFormatData(successCallback: (MediaFileData) -> Unit, errorCallback: (() -> Unit)? = definedExternally)
+}
+
+/**
+ * Get details for the given file.
+ */
+suspend fun MediaFile.getFormatData(): MediaFileData? {
+ return suspendCoroutine { continuation ->
+ this.getFormatData({
+ continuation.resume(it)
+ }, {
+ continuation.resume(null)
+ })
+ }
+}
+
+/**
+ * Exception class for media capture errors.
+ */
+class CaptureException(val code: MediaCapture.CaptureErrorCode) : Exception("Capture exception: $code")
+
+/**
+ * Main media capture object.
+ */
+object MediaCapture {
+
+ enum class CaptureErrorCode {
+ CAPTURE_INTERNAL_ERR,
+ CAPTURE_APPLICATION_BUSY,
+ CAPTURE_INVALID_ARGUMENT,
+ CAPTURE_NO_MEDIA_FILES,
+ CAPTURE_PERMISSION_DENIED,
+ CAPTURE_NOT_SUPPORTED
+ }
+
+ @Suppress("MagicNumber")
+ private fun codeToEnum(code: Int): CaptureErrorCode {
+ return when (code) {
+ 0 -> CaptureErrorCode.CAPTURE_INTERNAL_ERR
+ 1 -> CaptureErrorCode.CAPTURE_APPLICATION_BUSY
+ 2 -> CaptureErrorCode.CAPTURE_INVALID_ARGUMENT
+ 3 -> CaptureErrorCode.CAPTURE_NO_MEDIA_FILES
+ 4 -> CaptureErrorCode.CAPTURE_PERMISSION_DENIED
+ else -> CaptureErrorCode.CAPTURE_NOT_SUPPORTED
+ }
+ }
+
+ /**
+ * Capture audio file(s).
+ * @param limit The maximum number of audio clips the user can capture in a single capture operation.
+ * @param duration The maximum duration of an audio clip, in seconds.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun captureAudio(limit: Number = 1, duration: Number? = null): Result<List<MediaFile>, CaptureException> {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ window.navigator.asDynamic().device.capture.captureAudio({ result: Array<MediaFile> ->
+ continuation.resume(Result.success(result.asList()))
+ }, { err: CaptureError ->
+ continuation.resume(Result.error(CaptureException(codeToEnum(err.code))))
+ }, obj {
+ this.limit = limit
+ if (duration != null) this.duration = duration
+ })
+ }
+ }
+ }
+
+ /**
+ * Capture image file(s).
+ * @param limit The maximum number of image files the user can capture in a single capture operation.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun captureImage(limit: Number = 1): Result<List<MediaFile>, CaptureException> {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ window.navigator.asDynamic().device.capture.captureImage({ result: Array<MediaFile> ->
+ continuation.resume(Result.success(result.asList()))
+ }, { err: CaptureError ->
+ continuation.resume(Result.error(CaptureException(codeToEnum(err.code))))
+ }, obj {
+ this.limit = limit
+ })
+ }
+ }
+ }
+
+ /**
+ * Capture video file(s).
+ * @param limit The maximum number of video clips the user can capture in a single capture operation.
+ * @param duration The maximum duration of an video clip, in seconds.
+ * @param lowQuality Capture video with a lower quality (Android only)
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun captureVideo(
+ limit: Number = 1,
+ duration: Number? = null,
+ lowQuality: Boolean = false
+ ): Result<List<MediaFile>, CaptureException> {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ window.navigator.asDynamic().device.capture.captureVideo({ result: Array<MediaFile> ->
+ continuation.resume(Result.success(result.asList()))
+ }, { err: CaptureError ->
+ continuation.resume(Result.error(CaptureException(codeToEnum(err.code))))
+ }, obj {
+ this.limit = limit
+ if (duration != null) this.duration = duration
+ if (lowQuality) this.quality = 0
+ })
+ }
+ }
+ }
+
+ /**
+ * Add listeners for pending capture Cordova events.
+ */
+ fun addPendingCaptureListener(listener: (Result<List<MediaFile>, CaptureException>) -> Unit) {
+ addDeviceReadyListener {
+ document.addEventListener("pendingcaptureresult", { result ->
+ @Suppress("CAST_NEVER_SUCCEEDS")
+ listener(Result.success((result as? Array<MediaFile>)?.asList() ?: listOf()))
+ }, false)
+ document.addEventListener("pendingcaptureerror", { error ->
+ @Suppress("CAST_NEVER_SUCCEEDS")
+ listener(Result.error(CaptureException(codeToEnum((error as? CaptureError)?.code ?: 0))))
+ }, false)
+ }
+ }
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt
new file mode 100644
index 00000000..eb98f8a4
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Network.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.cordova
+
+import kotlin.browser.document
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * Main object for Cordova network.
+ */
+object Network {
+
+ /**
+ * Connection types
+ */
+ enum class ConnectionType {
+ UNKNOWN,
+ ETHERNET,
+ WIFI,
+ CELL_2G,
+ CELL_3G,
+ CELL_4G,
+ CELL,
+ NONE
+ }
+
+ /**
+ * Network status events
+ */
+ enum class NetworkEvent(internal val type: String) {
+ OFFLINE("offline"),
+ ONLINE("online")
+ }
+
+ /**
+ * Get network connection type.
+ */
+ suspend fun getConnectionType(): ConnectionType {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ val connectionType = when (window.navigator.asDynamic().connection.type) {
+ js("Connection.ETHERNET") -> ConnectionType.ETHERNET
+ js("Connection.WIFI") -> ConnectionType.WIFI
+ js("Connection.CELL_2G") -> ConnectionType.CELL_2G
+ js("Connection.CELL_3G") -> ConnectionType.CELL_3G
+ js("Connection.CELL_4G") -> ConnectionType.CELL_4G
+ js("Connection.CELL") -> ConnectionType.CELL
+ js("Connection.NONE") -> ConnectionType.NONE
+ else -> ConnectionType.UNKNOWN
+ }
+ continuation.resume(connectionType)
+ }
+ }
+ }
+
+ /**
+ * Add listeners for network status Cordova events.
+ */
+ fun addStatusListener(event: NetworkEvent, listener: () -> Unit) {
+ addDeviceReadyListener {
+ document.addEventListener(event.type, {
+ listener()
+ }, false)
+ }
+ }
+
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt
new file mode 100644
index 00000000..5afddb41
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Notification.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.cordova
+
+import kotlin.browser.window
+
+/**
+ * A response object for prompt function callback.
+ */
+external class PromptResponse {
+ val buttonIndex: Int
+ val input1: String
+}
+
+/**
+ * Main object for Cordova notifications.
+ */
+object Notification {
+
+ /**
+ * Show alert dialog.
+ * @param message a message
+ * @param title an optional dialog title (defaults to Alert)
+ * @param buttonLabel an optional button label (defaults to OK)
+ * @param callback a callback function
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ fun alert(message: String, title: String? = null, buttonLabel: String? = null, callback: () -> Unit) {
+ addDeviceReadyListener {
+ window.navigator.asDynamic().notification.alert(message, callback, title, buttonLabel)
+ }
+ }
+
+ /**
+ * Show confirm dialog.
+ * @param message a message
+ * @param title an optional dialog title (defaults to Confirm)
+ * @param buttonLabels an optional list of button labels (defaults to [OK, Cancel])
+ * @param callback a callback function
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ fun confirm(message: String, title: String? = null, buttonLabels: List<String>? = null, callback: (Int) -> Unit) {
+ addDeviceReadyListener {
+ window.navigator.asDynamic().notification.confirm(message, callback, title, buttonLabels?.toTypedArray())
+ }
+ }
+
+ /**
+ * Show prompt dialog.
+ * @param message a message
+ * @param title an optional dialog title (defaults to Prompt)
+ * @param buttonLabels an optional list of button labels (defaults to [OK, Cancel])
+ * @param defaultText default input value (default to empty string)
+ * @param callback a callback function
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ fun prompt(
+ message: String,
+ title: String? = null,
+ buttonLabels: List<String>? = null,
+ defaultText: String? = null,
+ callback: (PromptResponse) -> Unit
+ ) {
+ addDeviceReadyListener {
+ window.navigator.asDynamic().notification.prompt(
+ message,
+ callback,
+ title,
+ buttonLabels?.toTypedArray(),
+ defaultText
+ )
+ }
+ }
+
+ /**
+ * Play a beep sound.
+ * @param times the number of times to repeat the beep (defaults to 1)
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ fun beep(times: Int = 1) {
+ addDeviceReadyListener {
+ window.navigator.asDynamic().notification.beep(times)
+ }
+ }
+
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt
new file mode 100644
index 00000000..83a1987e
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Result.kt
@@ -0,0 +1,154 @@
+@file:Suppress(
+ "TooManyFunctions", "TooGenericExceptionCaught"
+)
+/*
+ * Source: https://github.com/kittinunf/Result
+ *
+ * MIT License
+ *
+ * Copyright (c) 2017 Kittinun Vantasin
+ *
+ * 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.cordova
+
+inline fun <reified X> Result<*, *>.getAs() = when (this) {
+ is Result.Success -> value as? X
+ is Result.Failure -> error as? X
+}
+
+inline fun <V : Any> Result<V, *>.success(f: (V) -> Unit) = fold(f, {})
+
+inline fun <E : Exception> Result<*, E>.failure(f: (E) -> Unit) = fold({}, f)
+
+infix fun <V : Any, E : Exception> Result<V, E>.or(fallback: V) = when (this) {
+ is Result.Success -> this
+ else -> Result.Success(fallback)
+}
+
+infix fun <V : Any, E : Exception> Result<V, E>.getOrElse(fallback: V) = when (this) {
+ is Result.Success -> value
+ else -> fallback
+}
+
+inline fun <V : Any, U : Any, E : Exception> Result<V, E>.map(transform: (V) -> U): Result<U, E> = try {
+ when (this) {
+ is Result.Success -> Result.Success(transform(value))
+ is Result.Failure -> Result.Failure(error)
+ }
+} catch (ex: Exception) {
+ @Suppress("UNCHECKED_CAST")
+ Result.error(ex as E)
+}
+
+inline fun <V : Any, U : Any, E : Exception> Result<V, E>.flatMap(transform: (V) -> Result<U, E>): Result<U, E> = try {
+ when (this) {
+ is Result.Success -> transform(value)
+ is Result.Failure -> Result.Failure(error)
+ }
+} catch (ex: Exception) {
+ @Suppress("UNCHECKED_CAST")
+ Result.error(ex as E)
+}
+
+fun <V : Any, E : Exception, E2 : Exception> Result<V, E>.mapError(transform: (E) -> E2) = when (this) {
+ is Result.Success -> Result.Success(value)
+ is Result.Failure -> Result.Failure(transform(error))
+}
+
+fun <V : Any, E : Exception, E2 : Exception> Result<V, E>.flatMapError(transform: (E) -> Result<V, E2>) = when (this) {
+ is Result.Success -> Result.Success(value)
+ is Result.Failure -> transform(error)
+}
+
+fun <V : Any, E : Exception> Result<V, E>.any(predicate: (V) -> Boolean): Boolean = try {
+ when (this) {
+ is Result.Success -> predicate(value)
+ is Result.Failure -> false
+ }
+} catch (ex: Exception) {
+ false
+}
+
+fun <V : Any, U : Any> Result<V, *>.fanout(other: () -> Result<U, *>): Result<Pair<V, U>, *> =
+ flatMap { outer -> other().map { outer to it } }
+
+sealed class Result<out V : Any, out E : Exception> {
+
+ open operator fun component1(): V? = null
+ open operator fun component2(): E? = null
+
+ inline fun <X> fold(success: (V) -> X, failure: (E) -> X): X = when (this) {
+ is Success -> success(this.value)
+ is Failure -> failure(this.error)
+ }
+
+ abstract fun get(): V
+
+ class Success<out V : Any>(val value: V) : Result<V, Nothing>() {
+ override fun component1(): V? = value
+
+ override fun get(): V = value
+
+ override fun toString() = "[Success: $value]"
+
+ override fun hashCode(): Int = value.hashCode()
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ return other is Success<*> && value == other.value
+ }
+ }
+
+ class Failure<out E : Exception>(val error: E) : Result<Nothing, E>() {
+ override fun component2(): E? = error
+
+ override fun get() = throw error
+
+ fun getException(): E = error
+
+ override fun toString() = "[Failure: $error]"
+
+ override fun hashCode(): Int = error.hashCode()
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ return other is Failure<*> && error == other.error
+ }
+ }
+
+ companion object {
+ // Factory methods
+ fun <E : Exception> error(ex: E) = Failure(ex)
+
+ fun <V : Any> success(v: V) = Success(v)
+
+ fun <V : Any> of(value: V?, fail: (() -> Exception) = { Exception() }): Result<V, Exception> =
+ value?.let { success(it) } ?: error(fail())
+
+ fun <V : Any, E : Exception> of(f: () -> V): Result<V, E> = try {
+ success(f())
+ } catch (ex: Exception) {
+ @Suppress("UNCHECKED_CAST")
+ error(ex as E)
+ }
+ }
+
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt
new file mode 100644
index 00000000..6765b44e
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Screen.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.cordova
+
+import kotlin.browser.window
+
+/**
+ * Main object for Cordova screen.
+ */
+object Screen {
+
+ /**
+ * Screen orientations
+ */
+ enum class Orientation(internal val type: String) {
+ PORTRAIT_PRIMARY("portrait-primary"),
+ PORTRAIT_SECONDARY("portrait-secondary"),
+ LANDSCAPE_PRIMARY("landscape-primary"),
+ LANDSCAPE_SECONDARY("landscape-secondary"),
+ PORTRAIT("portrait"),
+ LANDSCAPE("landscape"),
+ ANY("any")
+ }
+
+ /**
+ * Lock screen orientation.
+ * @param orientation selected orientation
+ */
+ fun lock(orientation: Orientation) {
+ window.screen.asDynamic().orientation.lock(orientation.type)
+ }
+
+ /**
+ * Unlock screen orientation.
+ */
+ fun unlock() {
+ window.screen.asDynamic().orientation.unlock()
+ }
+
+ /**
+ * Get screen orientation.
+ */
+ fun getOrientation(): Orientation {
+ val type = window.screen.asDynamic().orientation.type
+ return Orientation.values().find { it.type == type } ?: Screen.Orientation.ANY
+ }
+
+ /**
+ * Add listeners for screen orientation change Cordova events.
+ */
+ fun addOrientationChangeListener(listener: (Orientation) -> Unit) {
+ window.addEventListener("orientationchange", {
+ listener(getOrientation())
+ }, false)
+ }
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt
new file mode 100644
index 00000000..03157534
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Splashscreen.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.cordova
+
+import kotlin.browser.window
+
+/**
+ * Main object for Cordova splashscreen.
+ */
+object Splashscreen {
+
+ /**
+ * Show splashscreen.
+ */
+ fun show() {
+ addDeviceReadyListener {
+ window.navigator.asDynamic().splashscreen.show()
+ }
+ }
+
+ /**
+ * Hide splashscreen.
+ */
+ fun hide() {
+ addDeviceReadyListener {
+ window.navigator.asDynamic().splashscreen.hide()
+ }
+ }
+
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt
new file mode 100644
index 00000000..e3de6af2
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/StatusBar.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.cordova
+
+import pl.treksoft.kvision.utils.toHexString
+import kotlin.browser.window
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+@Suppress("TopLevelPropertyNaming")
+@JsName("StatusBar")
+internal external val StatusBarJs: dynamic
+
+/**
+ * Main object for Cordova status bar.
+ */
+@Suppress("TooManyFunctions")
+object StatusBar {
+
+ /**
+ * Show status bar.
+ */
+ fun show() {
+ addDeviceReadyListener {
+ @Suppress("UnsafeCastFromDynamic")
+ StatusBarJs.show()
+ }
+ }
+
+ /**
+ * Hide status bar.
+ */
+ fun hide() {
+ addDeviceReadyListener {
+ @Suppress("UnsafeCastFromDynamic")
+ StatusBarJs.hide()
+ }
+ }
+
+ /**
+ * Set overlay.
+ * @param overlays determines if the status bar overlays the web view
+ */
+ fun overlaysWebView(overlays: Boolean) {
+ addDeviceReadyListener {
+ @Suppress("UnsafeCastFromDynamic")
+ StatusBarJs.overlaysWebView(overlays)
+ }
+ }
+
+ /**
+ * Use the default status bar (dark text, for light backgrounds).
+ */
+ fun styleDefault() {
+ addDeviceReadyListener {
+ @Suppress("UnsafeCastFromDynamic")
+ StatusBarJs.styleDefault()
+ }
+ }
+
+ /**
+ * Use the lightContent status bar (light text, for dark backgrounds).
+ */
+ fun styleLightContent() {
+ addDeviceReadyListener {
+ @Suppress("UnsafeCastFromDynamic")
+ StatusBarJs.styleLightContent()
+ }
+ }
+
+ /**
+ * Use the blackTranslucent status bar (light text, for dark backgrounds).
+ */
+ fun styleBlackTranslucent() {
+ addDeviceReadyListener {
+ @Suppress("UnsafeCastFromDynamic")
+ StatusBarJs.styleBlackTranslucent()
+ }
+ }
+
+ /**
+ * Use the blackOpaque status bar (light text, for dark backgrounds).
+ */
+ fun styleBlackOpaque() {
+ addDeviceReadyListener {
+ @Suppress("UnsafeCastFromDynamic")
+ StatusBarJs.styleBlackOpaque()
+ }
+ }
+
+ /**
+ * Sets the background color of the status bar by a hexadecimal Int.
+ */
+ fun setBackgroundColor(color: Int) {
+ addDeviceReadyListener {
+ @Suppress("UnsafeCastFromDynamic")
+ StatusBarJs.backgroundColorByHexString("#" + color.toHexString())
+ }
+ }
+
+ /**
+ * Sets the background color of the status bar by a hex string.
+ */
+ fun setBackgroundColorByHexString(color: String) {
+ addDeviceReadyListener {
+ @Suppress("UnsafeCastFromDynamic")
+ StatusBarJs.backgroundColorByHexString(color)
+ }
+ }
+
+ /**
+ * Returns if the status bar is visible.
+ */
+ @Suppress("UnsafeCastFromDynamic")
+ suspend fun isVisible(): Boolean {
+ return suspendCoroutine { continuation ->
+ addDeviceReadyListener {
+ continuation.resume(StatusBarJs.isVisible)
+ }
+ }
+ }
+
+ /**
+ * Add listeners for the status bar tap event.
+ */
+ fun addTapListener(listener: () -> Unit) {
+ addDeviceReadyListener {
+ window.addEventListener("statusTap", {
+ listener()
+ })
+ }
+ }
+
+}
diff --git a/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt
new file mode 100644
index 00000000..6d973bbc
--- /dev/null
+++ b/kvision-modules/kvision-cordova/src/main/kotlin/pl/treksoft/kvision/cordova/Vibration.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.cordova
+
+import kotlin.browser.window
+
+/**
+ * Main object for Cordova vibration.
+ */
+object Vibration {
+
+ /**
+ * Generate a vibration with a given pattern.
+ * @param pattern a pattern - sequence of durations (in milliseconds) for which to turn on or off the vibrator.
+ */
+ fun vibrate(vararg pattern: Int) {
+ val patternArray = if (pattern.isEmpty()) arrayOf(0) else pattern.toTypedArray()
+ addDeviceReadyListener {
+ window.navigator.vibrate(patternArray)
+ }
+ }
+
+}
diff --git a/kvision-modules/kvision-datacontainer/build.gradle b/kvision-modules/kvision-datacontainer/build.gradle
index 7ad09e67..9337698c 100644
--- a/kvision-modules/kvision-datacontainer/build.gradle
+++ b/kvision-modules/kvision-datacontainer/build.gradle
@@ -9,6 +9,7 @@ kotlinFrontend {
npm {
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
fun runW(code: (widget: Widget, element: Element?) -> Unit) {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
widget.id = "test_id"
root.add(widget)
diff --git a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt
index 931294d5..556d3991 100644
--- a/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt
+++ b/kvision-modules/kvision-datacontainer/src/test/kotlin/test/pl/treksoft/kvision/data/DataContainerSpec.kt
@@ -36,7 +36,7 @@ class DataContainerSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
class Model(value: String) : BaseDataComponent() {
var value: String by obs(value)
diff --git a/kvision-modules/kvision-datetime/build.gradle b/kvision-modules/kvision-datetime/build.gradle
index 610b1d9c..bbd4bb79 100644
--- a/kvision-modules/kvision-datetime/build.gradle
+++ b/kvision-modules/kvision-datetime/build.gradle
@@ -6,6 +6,7 @@ kotlinFrontend {
dependency("bootstrap-datetime-picker", "2.4.4")
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt b/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt
index 7c1c8d71..5858d04b 100644
--- a/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt
+++ b/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt
@@ -282,14 +282,14 @@ open class DateTimeInput(
/**
* Makes the input element focused.
*/
- open fun focus() {
+ override fun focus() {
getElementJQuery()?.focus()
}
/**
* Makes the input element blur.
*/
- open fun blur() {
+ override fun blur() {
getElementJQuery()?.blur()
}
diff --git a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
fun runW(code: (widget: Widget, element: Element?) -> Unit) {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
widget.id = "test_id"
root.add(widget)
diff --git a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt
index 69ab46ec..877cf650 100644
--- a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt
+++ b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt
@@ -34,7 +34,7 @@ class DateTimeInputSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val data = Date()
val dti = DateTimeInput(value = data).apply {
placeholder = "place"
diff --git a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt
index 19cefd86..b5e393bb 100644
--- a/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt
+++ b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt
@@ -34,7 +34,7 @@ class DateTimeSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val data = Date()
val ti = DateTime(value = data, label = "Label").apply {
placeholder = "place"
diff --git a/kvision-modules/kvision-dialog/build.gradle b/kvision-modules/kvision-dialog/build.gradle
index 16c2720a..292a228f 100644
--- a/kvision-modules/kvision-dialog/build.gradle
+++ b/kvision-modules/kvision-dialog/build.gradle
@@ -9,6 +9,7 @@ kotlinFrontend {
npm {
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-handlebars/build.gradle b/kvision-modules/kvision-handlebars/build.gradle
index de48816a..a971427e 100644
--- a/kvision-modules/kvision-handlebars/build.gradle
+++ b/kvision-modules/kvision-handlebars/build.gradle
@@ -7,6 +7,7 @@ kotlinFrontend {
dependency("handlebars-loader", "1.7.1")
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-i18n/build.gradle b/kvision-modules/kvision-i18n/build.gradle
index 88147bbe..72870169 100644
--- a/kvision-modules/kvision-i18n/build.gradle
+++ b/kvision-modules/kvision-i18n/build.gradle
@@ -6,6 +6,7 @@ kotlinFrontend {
dependency("jed", "1.1.1")
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt b/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt
index 9eb9d9a7..39e7c2dc 100644
--- a/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt
+++ b/kvision-modules/kvision-moment/src/main/kotlin/pl/treksoft/kvision/moment/Moment.kt
@@ -23,7 +23,7 @@ package pl.treksoft.kvision.moment
import kotlin.js.Date
-@Suppress("unused")
+@Suppress("unused", "TooManyFunctions")
@JsNonModule
@JsModule("moment")
open external class Moment {
@@ -79,7 +79,9 @@ open external class Moment {
open fun isSameOrAfter(moment: dynamic): Boolean = definedExternally
open fun isSameOrAfter(moment: dynamic, key: String): Boolean = definedExternally
open fun isBetween(firstMoment: dynamic, secondMoment: dynamic): Boolean = definedExternally
- open fun isBetween(firstMoment: dynamic, secondMoment: dynamic, key: String, inclusivity: String): Boolean = definedExternally
+ open fun isBetween(firstMoment: dynamic, secondMoment: dynamic, key: String, inclusivity: String): Boolean =
+ definedExternally
+
open fun isBetween(firstMoment: dynamic, secondMoment: dynamic, key: String): Boolean = definedExternally
open fun isDST(): Boolean = definedExternally
open fun isDSTShifted(): Boolean = definedExternally
diff --git a/kvision-modules/kvision-redux/build.gradle b/kvision-modules/kvision-redux/build.gradle
index db0610bd..3394095f 100644
--- a/kvision-modules/kvision-redux/build.gradle
+++ b/kvision-modules/kvision-redux/build.gradle
@@ -14,6 +14,7 @@ kotlinFrontend {
dependency("core-js", "3.0.0")
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
fun runW(code: (widget: Widget, element: Element?) -> Unit) {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
widget.id = "test_id"
root.add(widget)
diff --git a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt
index 0271a956..5204402d 100644
--- a/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt
+++ b/kvision-modules/kvision-redux/src/test/kotlin/test/pl/treksoft/kvision/redux/StateBindingSpec.kt
@@ -55,7 +55,7 @@ class StateBindingSpec : DomSpec {
@Test
fun stateBinding() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val store = createReduxStore(::stateReducer, State(10))
val container = SimplePanel()
@@ -81,7 +81,7 @@ class StateBindingSpec : DomSpec {
@Test
fun stateUpdate() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val store = createReduxStore(::stateReducer, State(10))
val container = SimplePanel()
diff --git a/kvision-modules/kvision-richtext/build.gradle b/kvision-modules/kvision-richtext/build.gradle
index 86be8e7f..fde9ff17 100644
--- a/kvision-modules/kvision-richtext/build.gradle
+++ b/kvision-modules/kvision-richtext/build.gradle
@@ -6,6 +6,7 @@ kotlinFrontend {
dependency("trix", "1.1.0")
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
fun runW(code: (widget: Widget, element: Element?) -> Unit) {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
widget.id = "test_id"
root.add(widget)
diff --git a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt
index 21b7dc39..4ebf7b45 100644
--- a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt
+++ b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt
@@ -34,7 +34,7 @@ class RichTextInputSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val hai = RichTextInput(value = "abc").apply {
placeholder = "place"
id = "idti"
diff --git a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt
index 844b7e94..1ea79790 100644
--- a/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt
+++ b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt
@@ -34,7 +34,7 @@ class RichTextSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val hai = RichText(value = "abc", label = "Field").apply {
placeholder = "place"
id = "idti"
diff --git a/kvision-modules/kvision-select/build.gradle b/kvision-modules/kvision-select/build.gradle
index 65b8a83c..7525f037 100644
--- a/kvision-modules/kvision-select/build.gradle
+++ b/kvision-modules/kvision-select/build.gradle
@@ -7,6 +7,7 @@ kotlinFrontend {
dependency("ajax-bootstrap-select", "1.4.3")
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt
index f19081e1..db8a5b3b 100644
--- a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt
+++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt
@@ -39,7 +39,7 @@ import pl.treksoft.kvision.utils.SnOn
* [SelectOption] or [SelectOptGroup] components to the container.
*
* @constructor
- * @param options an optional list of options (label to value pairs) for the select control
+ * @param options an optional list of options (value to label pairs) for the select control
* @param value selected value
* @param name the name attribute of the generated HTML input element
* @param multiple allows multiple value selection (multiple values are comma delimited)
@@ -55,7 +55,7 @@ open class Select(
) : SimplePanel(setOf("form-group")), StringFormControl {
/**
- * A list of options (label to value pairs) for the select control.
+ * A list of options (value to label pairs) for the select control.
*/
var options
get() = input.options
diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt
index 7ffdf2c2..9ae1a4e9 100644
--- a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt
+++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt
@@ -50,7 +50,7 @@ enum class SelectWidthType(internal val value: String) {
* [SelectOption] or [SelectOptGroup] components to the container.
*
* @constructor
- * @param options an optional list of options (label to value pairs) for the select control
+ * @param options an optional list of options (value to label pairs) for the select control
* @param value selected value
* @param multiple allows multiple value selection (multiple values are comma delimited)
* @param ajaxOptions additional options for remote (AJAX) data source
@@ -64,9 +64,9 @@ open class SelectInput(
) : SimplePanel(classes), FormInput {
/**
- * A list of options (label to value pairs) for the select control.
+ * A list of options (value to label pairs) for the select control.
*/
- internal var options by refreshOnUpdate(options) { setChildrenFromOptions() }
+ var options by refreshOnUpdate(options) { setChildrenFromOptions() }
/**
* A value of the selected option.
*/
@@ -337,14 +337,14 @@ open class SelectInput(
/**
* Makes the input element focused.
*/
- open fun focus() {
+ override fun focus() {
getElementJQuery()?.focus()
}
/**
* Makes the input element blur.
*/
- open fun blur() {
+ override fun blur() {
getElementJQuery()?.blur()
}
diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
fun runW(code: (widget: Widget, element: Element?) -> Unit) {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
widget.id = "test_id"
root.add(widget)
diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt
index 30f42e9c..bfd93900 100644
--- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt
@@ -34,7 +34,7 @@ class SelectInputSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val selectInput = SelectInput(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", true).apply {
liveSearch = true
placeholder = "Choose ..."
diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt
index bd88f560..33ccc843 100644
--- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt
@@ -33,7 +33,7 @@ class SelectOptGroupSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val selectOptGroup = SelectOptGroup("Group", listOf("test1" to "Test 1", "test2" to "Test 2"), 2)
root.add(selectOptGroup)
val element = document.getElementById("test")
diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt
index f7e07d42..33c36576 100644
--- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt
@@ -32,7 +32,7 @@ class SelectOptionSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val selectOption = SelectOption("testValue", "testLabel")
root.add(selectOption)
val element = document.getElementById("test")
diff --git a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt
index eaccd551..9eddff81 100644
--- a/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt
@@ -34,7 +34,7 @@ class SelectSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val select = Select(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", null, true, null, "Label").apply {
liveSearch = true
placeholder = "Choose ..."
diff --git a/kvision-modules/kvision-spinner/build.gradle b/kvision-modules/kvision-spinner/build.gradle
index e7600e5a..4569b8bc 100644
--- a/kvision-modules/kvision-spinner/build.gradle
+++ b/kvision-modules/kvision-spinner/build.gradle
@@ -6,6 +6,7 @@ kotlinFrontend {
dependency("bootstrap-touchspin", "4.2.5")
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt b/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt
index c60aab2f..66e98ab5 100644
--- a/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt
+++ b/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt
@@ -293,14 +293,14 @@ open class SpinnerInput(
/**
* Makes the input element focused.
*/
- open fun focus() {
+ override fun focus() {
getElementJQuery()?.focus()
}
/**
* Makes the input element blur.
*/
- open fun blur() {
+ override fun blur() {
getElementJQuery()?.blur()
}
diff --git a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
fun runW(code: (widget: Widget, element: Element?) -> Unit) {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
widget.id = "test_id"
root.add(widget)
diff --git a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt
index a240bfd8..467e48db 100644
--- a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt
+++ b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt
@@ -32,7 +32,7 @@ class SpinnerInputSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val si = SpinnerInput(value = 13).apply {
placeholder = "place"
id = "idti"
@@ -46,7 +46,7 @@ class SpinnerInputSpec : DomSpec {
@Test
fun spinUp() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val si = SpinnerInput(value = 13).apply {
placeholder = "place"
id = "idti"
@@ -61,7 +61,7 @@ class SpinnerInputSpec : DomSpec {
@Test
fun spinDown() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val si = SpinnerInput(value = 13).apply {
placeholder = "place"
id = "idti"
diff --git a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt
index 30f12a93..928fe0b1 100644
--- a/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt
+++ b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt
@@ -33,7 +33,7 @@ class SpinnerSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ti = Spinner(value = 13, label = "Label").apply {
placeholder = "place"
name = "name"
@@ -59,7 +59,7 @@ class SpinnerSpec : DomSpec {
@Test
fun spinUp() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val si = Spinner(value = 13)
root.add(si)
assertEquals(13, si.value, "Should return initial value before spinUp")
@@ -71,7 +71,7 @@ class SpinnerSpec : DomSpec {
@Test
fun spinDown() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val si = Spinner(value = 13)
root.add(si)
assertEquals(13, si.value, "Should return initial value before spinDown")
diff --git a/kvision-modules/kvision-tabulator/build.gradle b/kvision-modules/kvision-tabulator/build.gradle
index d461e542..5ea97e50 100644
--- a/kvision-modules/kvision-tabulator/build.gradle
+++ b/kvision-modules/kvision-tabulator/build.gradle
@@ -11,6 +11,7 @@ kotlinFrontend {
dependency("tabulator-tables", "4.2.5")
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
index dac29ab9..08f2603b 100644
--- a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
+++ b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Options.kt
@@ -22,8 +22,19 @@
package pl.treksoft.kvision.tabulator
+import kotlinx.serialization.KSerializer
+import org.w3c.dom.HTMLElement
+import pl.treksoft.kvision.core.Component
+import pl.treksoft.kvision.form.FormControl
+import pl.treksoft.kvision.form.FormInput
+import pl.treksoft.kvision.panel.Root
+import pl.treksoft.kvision.tabulator.EditorRoot.disposeTimer
+import pl.treksoft.kvision.tabulator.EditorRoot.root
import pl.treksoft.kvision.tabulator.js.Tabulator
+import pl.treksoft.kvision.utils.JSON
import pl.treksoft.kvision.utils.obj
+import kotlin.browser.document
+import kotlin.browser.window
import kotlin.js.Promise
/**
@@ -248,7 +259,7 @@ fun DownloadConfig.toJs(): Tabulator.DownloadConfig {
/**
* Column definition options.
*/
-data class ColumnDefinition(
+data class ColumnDefinition<T : Any>(
val title: String,
val field: String? = null,
val visible: Boolean? = null,
@@ -265,18 +276,36 @@ data class ColumnDefinition(
val rowHandle: Boolean? = null,
val hideInHtml: Boolean? = null,
val sorter: Sorter? = null,
+ val sorterFunction: ((
+ a: dynamic, b: dynamic, aRow: Tabulator.RowComponent, bRow: Tabulator.RowComponent,
+ column: Tabulator.ColumnComponent, dir: SortingDir, sorterParams: dynamic
+ ) -> Number)? = null,
val sorterParams: dynamic = null,
val formatter: Formatter? = null,
val formatterFunction: ((
cell: Tabulator.CellComponent, formatterParams: dynamic,
onRendered: (callback: () -> Unit) -> Unit
) -> dynamic)? = null,
+ val formatterComponentFunction: ((
+ cell: Tabulator.CellComponent, onRendered: (callback: () -> Unit) -> Unit, data: T
+ ) -> Component)? = null,
val formatterParams: dynamic = null,
val variableHeight: Boolean? = null,
val editable: ((cell: Tabulator.CellComponent) -> Boolean)? = null,
val editor: Editor? = null,
+ val editorFunction: ((
+ cell: Tabulator.CellComponent,
+ onRendered: (callback: () -> Unit) -> Unit,
+ success: (value: dynamic) -> Unit, cancel: (value: dynamic) -> Unit, editorParams: dynamic
+ ) -> dynamic)? = null,
+ val editorComponentFunction: ((
+ cell: Tabulator.CellComponent,
+ onRendered: (callback: () -> Unit) -> Unit,
+ success: (value: dynamic) -> Unit, cancel: (value: dynamic) -> Unit, data: T
+ ) -> Component)? = null,
val editorParams: dynamic = null,
val validator: Validator? = null,
+ val validatorFunction: dynamic = null,
val validatorParams: String? = null,
val download: Boolean? = null,
val downloadTitle: String? = null,
@@ -325,11 +354,78 @@ data class ColumnDefinition(
val cellEditCancelled: ((cell: Tabulator.CellComponent) -> Unit)? = null
)
+internal object EditorRoot {
+ internal var root: Root? = null
+ internal var disposeTimer: Int? = null
+}
+
/**
* An extension function to convert column definition class to JS object.
*/
-@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "ComplexMethod")
-fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.ColumnDefinition {
+@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "ComplexMethod", "MagicNumber")
+fun <T : Any> ColumnDefinition<T>.toJs(
+ i18nTranslator: (String) -> (String),
+ dataSerializer: KSerializer<T>? = null
+): Tabulator.ColumnDefinition {
+ val tmpEditorFunction = editorComponentFunction?.let {
+ { cell: Tabulator.CellComponent,
+ onRendered: (callback: () -> Unit) -> Unit,
+ success: (value: dynamic) -> Unit, cancel: (value: dynamic) -> Unit, _: dynamic ->
+ var onRenderedCallback: (() -> Unit)? = null
+ val str = kotlin.js.JSON.stringify(cell.getData())
+ @Suppress("UNCHECKED_CAST") val data = dataSerializer?.let {
+ JSON.plain.parse(it, str)
+ } ?: cell.getData() as T
+ val component = it(cell, { callback ->
+ onRenderedCallback = callback
+ }, { value ->
+ success(value)
+ disposeTimer = window.setTimeout({
+ root?.dispose()
+ disposeTimer = null
+ root = null
+ }, 500)
+ }, cancel, data)
+ val rootElement = document.createElement("div") as HTMLElement
+ onRendered {
+ if (root != null) {
+ disposeTimer?.let { window.clearTimeout(it) }
+ root?.dispose()
+ }
+ root = Root(element = rootElement)
+ @Suppress("UnsafeCastFromDynamic")
+ root?.add(component)
+ (component as? FormControl)?.focus()
+ (component as? FormInput)?.focus()
+ cell.checkHeight()
+ onRenderedCallback?.invoke()
+ }
+ rootElement
+ }
+ }
+
+ val tmpFormatterFunction = formatterComponentFunction?.let {
+ { cell: Tabulator.CellComponent, _: dynamic,
+ onRendered: (callback: () -> Unit) -> Unit ->
+ var onRenderedCallback: (() -> Unit)? = null
+ val str = kotlin.js.JSON.stringify(cell.getData())
+ @Suppress("UNCHECKED_CAST") val data =
+ dataSerializer?.let { JSON.plain.parse(it, str) } ?: cell.getData() as T
+ val component = it(cell, { callback ->
+ onRenderedCallback = callback
+ }, data)
+ val rootElement = document.createElement("div") as HTMLElement
+ onRendered {
+ val root = Root(element = rootElement)
+ @Suppress("UnsafeCastFromDynamic")
+ root.add(component)
+ cell.checkHeight()
+ onRenderedCallback?.invoke()
+ }
+ rootElement
+ }
+ }
+
return obj {
this.title = i18nTranslator(title)
if (field != null) this.field = field
@@ -346,17 +442,25 @@ fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.Colum
if (cssClass != null) this.cssClass = cssClass
if (rowHandle != null) this.rowHandle = rowHandle
if (hideInHtml != null) this.hideInHtml = hideInHtml
- if (sorter != null) this.sorter = sorter.sorter
+ if (sorterFunction != null) {
+ this.sorter = sorterFunction
+ } else if (sorter != null) {
+ this.sorter = sorter.sorter
+ }
if (sorterParams != null) this.sorterParams = sorterParams
- if (formatterFunction != null) {
- this.formatter = formatterFunction
- } else if (formatter != null) {
- this.formatter = formatter.formatter
+ when {
+ tmpFormatterFunction != null -> this.formatter = tmpFormatterFunction
+ formatterFunction != null -> this.formatter = formatterFunction
+ formatter != null -> this.formatter = formatter.formatter
}
if (formatterParams != null) this.formatterParams = formatterParams
if (variableHeight != null) this.variableHeight = variableHeight
if (editable != null) this.editable = editable
- if (editor != null) this.editor = editor.editor
+ when {
+ tmpEditorFunction != null -> this.editor = tmpEditorFunction
+ editorFunction != null -> this.editor = editorFunction
+ editor != null -> this.editor = editor.editor
+ }
if (editorParams != null) this.editorParams = editorParams
if (validator != null) this.validator = validator.validator
if (validatorParams != null) this.validatorParams = validatorParams
@@ -404,14 +508,20 @@ fun ColumnDefinition.toJs(i18nTranslator: (String) -> (String)): Tabulator.Colum
if (cellMouseMove != null) this.cellMouseMove = cellMouseMove
if (cellEditing != null) this.cellEditing = cellEditing
if (cellEdited != null) this.cellEdited = cellEdited
- if (cellEditCancelled != null) this.cellEditCancelled = cellEditCancelled
+ if (cellEditCancelled != null) {
+ this.cellEditCancelled = cellEditCancelled
+ } else if (tmpEditorFunction != null) {
+ this.cellEditCancelled = { cell: Tabulator.CellComponent ->
+ cell.checkHeight()
+ }
+ }
} as Tabulator.ColumnDefinition
}
/**
* Tabulator options.
*/
-data class TabulatorOptions(
+data class TabulatorOptions<T : Any>(
val height: String? = null,
val virtualDom: Boolean? = null,
val virtualDomBuffer: Int? = null,
@@ -425,7 +535,7 @@ data class TabulatorOptions(
val downloadConfig: DownloadConfig? = null,
val reactiveData: Boolean? = null,
val autoResize: Boolean? = null,
- val columns: List<ColumnDefinition>? = null,
+ val columns: List<ColumnDefinition<T>>? = null,
val autoColumns: Boolean? = null,
val layout: Layout? = null,
val layoutColumnsOnNewData: Boolean? = null,
@@ -572,7 +682,17 @@ data class TabulatorOptions(
* An extension function to convert tabulator options class to JS object.
*/
@Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE", "ComplexMethod")
-fun TabulatorOptions.toJs(i18nTranslator: (String) -> (String)): Tabulator.Options {
+fun <T : Any> TabulatorOptions<T>.toJs(
+ i18nTranslator: (String) -> (String),
+ dataSerializer: KSerializer<T>? = null
+): Tabulator.Options {
+ val tmpCellEditCancelled = this.columns?.find { it.editorComponentFunction != null }?.let {
+ { cell: Tabulator.CellComponent ->
+ cellEditCancelled?.invoke(cell)
+ cell.getTable().redraw(true)
+ }
+ } ?: cellEditCancelled
+
return obj {
if (height != null) this.height = height
if (virtualDom != null) this.virtualDom = virtualDom
@@ -587,7 +707,7 @@ fun TabulatorOptions.toJs(i18nTranslator: (String) -> (String)): Tabulator.Optio
if (downloadConfig != null) this.downloadConfig = downloadConfig.toJs()
if (reactiveData != null) this.reactiveData = reactiveData
if (autoResize != null) this.autoResize = autoResize
- if (columns != null) this.columns = columns.map { it.toJs(i18nTranslator) }.toTypedArray()
+ if (columns != null) this.columns = columns.map { it.toJs(i18nTranslator, dataSerializer) }.toTypedArray()
if (autoColumns != null) {
this.autoColumns = autoColumns
} else if (columns == null) {
@@ -700,7 +820,9 @@ fun TabulatorOptions.toJs(i18nTranslator: (String) -> (String)): Tabulator.Optio
if (cellMouseMove != null) this.cellMouseMove = cellMouseMove
if (cellEditing != null) this.cellEditing = cellEditing
if (cellEdited != null) this.cellEdited = cellEdited
- if (cellEditCancelled != null) this.cellEditCancelled = cellEditCancelled
+ if (tmpCellEditCancelled != null) {
+ this.cellEditCancelled = tmpCellEditCancelled
+ }
if (columnMoved != null) this.columnMoved = columnMoved
if (columnResized != null) this.columnResized = columnResized
if (columnVisibilityChanged != null) this.columnVisibilityChanged = columnVisibilityChanged
diff --git a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt
index 249c578f..72a2809a 100644
--- a/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt
+++ b/kvision-modules/kvision-tabulator/src/main/kotlin/pl/treksoft/kvision/tabulator/Tabulator.kt
@@ -38,6 +38,7 @@ import pl.treksoft.kvision.table.TableType
import pl.treksoft.kvision.utils.JSON
import pl.treksoft.kvision.utils.createInstance
import pl.treksoft.kvision.utils.obj
+import pl.treksoft.kvision.utils.syncWithList
import redux.RAction
import pl.treksoft.kvision.tabulator.js.Tabulator as JsTabulator
@@ -47,14 +48,17 @@ import pl.treksoft.kvision.tabulator.js.Tabulator as JsTabulator
* @constructor
* @param T serializable type
* @param data a list of serializable objects
+ * @param dataUpdateOnEdit determines if the data model is automatically updated after tabulator edit action
* @param options tabulator options
+ * @param types a set of table types
* @param classes a set of CSS class names
* @param dataSerializer a serializer for class T
*/
@Suppress("LargeClass", "TooManyFunctions")
open class Tabulator<T : Any>(
protected val data: List<T>? = null,
- val options: TabulatorOptions = TabulatorOptions(),
+ protected val dataUpdateOnEdit: Boolean = true,
+ val options: TabulatorOptions<T> = TabulatorOptions(),
types: Set<TableType> = setOf(),
classes: Set<String> = setOf(),
protected val dataSerializer: KSerializer<T>? = null
@@ -191,6 +195,9 @@ open class Tabulator<T : Any>(
val d = nativeToData(data, dataSerializer)
@Suppress("UnsafeCastFromDynamic")
this.dispatchEvent("tabulatorDataEdited", obj { detail = d })
+ if (dataUpdateOnEdit && this.data is MutableList<T>) {
+ this.data.syncWithList(d)
+ }
}
}
counter++
@@ -228,7 +235,7 @@ open class Tabulator<T : Any>(
(this.getElement() as? HTMLElement)?.let {
jsTabulator =
KVManagerTabulator.getConstructor()
- .createInstance(it, options.toJs(this::translate))
+ .createInstance(it, options.toJs(this::translate, dataSerializer))
if (currentPage != null) {
jsTabulator?.setPageSize(pageSize ?: 0)
jsTabulator?.setPage(currentPage)
@@ -561,12 +568,13 @@ open class Tabulator<T : Any>(
*/
inline fun <reified T : Any> Container.tabulator(
data: List<T>? = null,
- options: TabulatorOptions = TabulatorOptions(),
+ dataUpdateOnEdit: Boolean = true,
+ options: TabulatorOptions<T> = TabulatorOptions(),
types: Set<TableType> = setOf(),
classes: Set<String> = setOf(),
noinline init: (Tabulator<T>.() -> Unit)? = null
): Tabulator<T> {
- val tabulator = create(data, options, types, classes)
+ val tabulator = create(data, dataUpdateOnEdit, options, types, classes)
init?.invoke(tabulator)
this.add(tabulator)
return tabulator
@@ -578,7 +586,7 @@ open class Tabulator<T : Any>(
inline fun <reified T : Any, S : Any, A : RAction> Container.tabulator(
store: ReduxStore<S, A>,
noinline dataFactory: (S) -> List<T>,
- options: TabulatorOptions = TabulatorOptions(),
+ options: TabulatorOptions<T> = TabulatorOptions(),
types: Set<TableType> = setOf(),
classes: Set<String> = setOf(),
noinline init: (Tabulator<T>.() -> Unit)? = null
@@ -594,7 +602,7 @@ open class Tabulator<T : Any>(
*/
inline fun <reified T : Any, A : RAction> Container.tabulator(
store: ReduxStore<List<T>, A>,
- options: TabulatorOptions = TabulatorOptions(),
+ options: TabulatorOptions<T> = TabulatorOptions(),
types: Set<TableType> = setOf(),
classes: Set<String> = setOf(),
noinline init: (Tabulator<T>.() -> Unit)? = null
@@ -609,12 +617,12 @@ open class Tabulator<T : Any>(
* DSL builder extension function for dynamic data (send within options parameter).
*/
fun <T : Any> Container.tabulator(
- options: TabulatorOptions = TabulatorOptions(),
+ options: TabulatorOptions<T> = TabulatorOptions(),
types: Set<TableType> = setOf(),
classes: Set<String> = setOf(),
init: (Tabulator<T>.() -> Unit)? = null
): Tabulator<T> {
- val tabulator = Tabulator<T>(options = options, types = types, classes = classes)
+ val tabulator = Tabulator(dataUpdateOnEdit = false, options = options, types = types, classes = classes)
init?.invoke(tabulator)
this.add(tabulator)
return tabulator
@@ -625,12 +633,14 @@ open class Tabulator<T : Any>(
*/
@UseExperimental(ImplicitReflectionSerializer::class)
inline fun <reified T : Any> create(
- data: List<T>? = null, options: TabulatorOptions = TabulatorOptions(),
+ data: List<T>? = null,
+ dataUpdateOnEdit: Boolean = true,
+ options: TabulatorOptions<T> = TabulatorOptions(),
types: Set<TableType> = setOf(),
classes: Set<String> = setOf(),
noinline init: (Tabulator<T>.() -> Unit)? = null
): Tabulator<T> {
- val tabulator = Tabulator(data, options, types, classes, T::class.serializer())
+ val tabulator = Tabulator(data, dataUpdateOnEdit, options, types, classes, T::class.serializer())
init?.invoke(tabulator)
return tabulator
}
@@ -642,13 +652,13 @@ open class Tabulator<T : Any>(
inline fun <reified T : Any, S : Any, A : RAction> create(
store: ReduxStore<S, A>,
noinline dataFactory: (S) -> List<T>,
- options: TabulatorOptions = TabulatorOptions(),
+ options: TabulatorOptions<T> = TabulatorOptions(),
types: Set<TableType> = setOf(),
classes: Set<String> = setOf(),
noinline init: (Tabulator<T>.() -> Unit)? = null
): Tabulator<T> {
val data = dataFactory(store.getState())
- val tabulator = Tabulator(data, options, types, classes, T::class.serializer())
+ val tabulator = Tabulator(data, false, options, types, classes, T::class.serializer())
init?.invoke(tabulator)
store.subscribe { s ->
tabulator.replaceData(dataFactory(s))
@@ -662,13 +672,13 @@ open class Tabulator<T : Any>(
@UseExperimental(ImplicitReflectionSerializer::class)
inline fun <reified T : Any, A : RAction> create(
store: ReduxStore<List<T>, A>,
- options: TabulatorOptions = TabulatorOptions(),
+ options: TabulatorOptions<T> = TabulatorOptions(),
types: Set<TableType> = setOf(),
classes: Set<String> = setOf(),
noinline init: (Tabulator<T>.() -> Unit)? = null
): Tabulator<T> {
val data = store.getState()
- val tabulator = Tabulator(data, options, types, classes, T::class.serializer())
+ val tabulator = Tabulator(data, false, options, types, classes, T::class.serializer())
init?.invoke(tabulator)
store.subscribe { s ->
tabulator.replaceData(s)
diff --git a/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
fun runW(code: (widget: Widget, element: Element?) -> Unit) {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
widget.id = "test_id"
root.add(widget)
diff --git a/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt b/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt
index d6b33a78..1f49ee93 100644
--- a/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt
+++ b/kvision-modules/kvision-tabulator/src/test/kotlin/test/pl/treksoft/kvision/tabulator/TabulatorSpec.kt
@@ -34,7 +34,7 @@ class TabulatorSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val tabulator = Tabulator<Any>(options = TabulatorOptions(data = arrayOf(obj {
id = 1
name = "Name"
diff --git a/kvision-modules/kvision-upload/build.gradle b/kvision-modules/kvision-upload/build.gradle
index 21ac7eab..2ac20c32 100644
--- a/kvision-modules/kvision-upload/build.gradle
+++ b/kvision-modules/kvision-upload/build.gradle
@@ -10,6 +10,7 @@ kotlinFrontend {
dependency("bootstrap-fileinput", "4.5.2")
devDependency("karma", "3.1.4")
devDependency("karma-chrome-launcher", "2.2.0")
+ devDependency("karma-webpack", "3.0.5")
devDependency("qunit", "2.8.0")
}
diff --git a/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt b/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt
index a250df42..39c4c4c6 100644
--- a/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt
+++ b/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt
@@ -278,14 +278,14 @@ open class UploadInput(uploadUrl: String? = null, multiple: Boolean = false, cla
/**
* Makes the input element focused.
*/
- open fun focus() {
+ override fun focus() {
getElementJQuery()?.focus()
}
/**
* Makes the input element blur.
*/
- open fun blur() {
+ override fun blur() {
getElementJQuery()?.blur()
}
diff --git a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
fun runW(code: (widget: Widget, element: Element?) -> Unit) {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
widget.id = "test_id"
root.add(widget)
diff --git a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt
index 626b70e4..de7a9315 100644
--- a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt
+++ b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt
@@ -33,7 +33,7 @@ class UploadInputSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val upi = UploadInput(multiple = true).apply {
id = "idti"
}
diff --git a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt
index bea4ddee..92078153 100644
--- a/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt
+++ b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt
@@ -33,7 +33,7 @@ class UploadSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val upi = Upload(multiple = true)
val id = upi.input.id
root.add(upi)
diff --git a/settings.gradle b/settings.gradle
index c7d58bfd..20498616 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -23,4 +23,5 @@ include 'kvision-modules:kvision-base',
'kvision-modules:kvision-server-jooby',
'kvision-modules:kvision-server-ktor',
'kvision-modules:kvision-server-spring-boot',
- 'kvision-modules:kvision-electron'
+ 'kvision-modules:kvision-electron',
+ 'kvision-modules:kvision-cordova'
diff --git a/src/main/kotlin/pl/treksoft/kvision/KVManager.kt b/src/main/kotlin/pl/treksoft/kvision/KVManager.kt
index faa0b77c..d1a4a8be 100644
--- a/src/main/kotlin/pl/treksoft/kvision/KVManager.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/KVManager.kt
@@ -29,6 +29,7 @@ import com.github.snabbdom.datasetModule
import com.github.snabbdom.eventListenersModule
import com.github.snabbdom.propsModule
import com.github.snabbdom.styleModule
+import org.w3c.dom.HTMLElement
import pl.treksoft.kvision.core.Component
import pl.treksoft.kvision.utils.isIE11
import kotlin.browser.document
@@ -72,6 +73,10 @@ internal object KVManager {
return sdPatch(container, vnode)
}
+ internal fun patch(element: HTMLElement, vnode: VNode): VNode {
+ return sdPatch(element, vnode)
+ }
+
internal fun patch(oldVNode: VNode, newVNode: VNode): VNode {
return sdPatch(oldVNode, newVNode)
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Component.kt b/src/main/kotlin/pl/treksoft/kvision/core/Component.kt
index 411eae8d..fe5569d4 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/Component.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Component.kt
@@ -97,6 +97,26 @@ interface Component {
fun removeSurroundingCssClass(css: Style): Component
/**
+ * Returns the value of an additional attribute.
+ * @param name the name of the attribute
+ * @return the value of the attribute
+ */
+ fun getAttribute(name: String): String?
+
+ /**
+ * Sets the value of additional attribute.
+ * @param name the name of the attribute
+ * @param value the value of the attribute
+ */
+ fun setAttribute(name: String, value: String): Component
+
+ /**
+ * Removes the value of additional attribute.
+ * @param name the name of the attribute
+ */
+ fun removeAttribute(name: String): Component
+
+ /**
* @suppress
* Internal function
* Renders current component as a Snabbdom vnode.
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Style.kt b/src/main/kotlin/pl/treksoft/kvision/core/Style.kt
index 282d2e7e..ff91c429 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/Style.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Style.kt
@@ -21,11 +21,6 @@
*/
package pl.treksoft.kvision.core
-import com.github.snabbdom.VNode
-import com.github.snabbdom.h
-import org.w3c.dom.Node
-import pl.treksoft.jquery.JQuery
-import pl.treksoft.kvision.panel.Root
import kotlin.reflect.KProperty
/**
@@ -41,8 +36,6 @@ open class Style(className: String? = null, parentStyle: Style? = null, init: (S
StyledComponent() {
private val propertyValues: MutableMap<String, Any?> = mutableMapOf()
- override var parent: Container? = Root.getFirstRoot()
-
private val newClassName: String = if (parentStyle == null) {
className ?: "kv_styleclass_${counter++}"
} else {
@@ -55,86 +48,19 @@ open class Style(className: String? = null, parentStyle: Style? = null, init: (S
var className: String by refreshOnUpdate(newClassName)
init {
+ @Suppress("LeakingThis")
styles.add(this)
@Suppress("LeakingThis")
init?.invoke(this)
}
- override var visible: Boolean = true
- set(value) {
- val oldField = field
- field = value
- if (oldField != field) refresh()
- }
-
- override fun addCssClass(css: String): Component {
- return this
- }
-
- override fun removeCssClass(css: String): Component {
- return this
- }
-
- override fun addSurroundingCssClass(css: String): Component {
- return this
- }
-
- override fun removeSurroundingCssClass(css: String): Component {
- return this
- }
-
- override fun addCssClass(css: Style): Component {
- return this
- }
-
- override fun removeCssClass(css: Style): Component {
- return this
- }
-
- override fun addSurroundingCssClass(css: Style): Component {
- return this
- }
-
- override fun removeSurroundingCssClass(css: Style): Component {
- return this
- }
-
- override fun renderVNode(): VNode {
- return h("style", arrayOf(generateStyle()))
- }
-
internal fun generateStyle(): String {
- val styles = getSnStyle()
+ val styles = getSnStyleInternal()
return ".$className {\n" + styles.joinToString("\n") {
"${it.first}: ${it.second};"
} + "\n}"
}
- override fun getElement(): Node? {
- return null
- }
-
- override fun getElementJQuery(): JQuery? {
- return null
- }
-
- override fun getElementJQueryD(): dynamic {
- return null
- }
-
- override fun clearParent(): Component {
- this.parent = null
- return this
- }
-
- override fun getRoot(): Root? {
- return this.parent?.getRoot()
- }
-
- override fun dispose() {
- styles.remove(this)
- }
-
protected fun <T> refreshOnUpdate(refreshFunction: ((T) -> Unit) = { this.refresh() }) =
RefreshDelegateProvider<T>(null, refreshFunction)
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt b/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt
index aa3f26bb..d9fa63fc 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/StyledComponent.kt
@@ -29,7 +29,7 @@ import kotlin.reflect.KProperty
* Base class for components supporting CSS styling.
*/
@Suppress("LargeClass")
-abstract class StyledComponent : Component {
+abstract class StyledComponent {
private val propertyValues: MutableMap<String, Any?> = mutableMapOf()
/**
diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
index 3388a011..fbcd89da 100644
--- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt
@@ -53,11 +53,12 @@ import kotlin.reflect.KProperty
* @param classes Set of CSS class names
*/
@Suppress("TooManyFunctions", "LargeClass")
-open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
+open class Widget(classes: Set<String> = setOf()) : StyledComponent(), Component {
private val propertyValues: MutableMap<String, Any?> = mutableMapOf()
internal val classes = classes.toMutableSet()
internal val surroundingClasses: MutableSet<String> = mutableSetOf()
+ internal val attributes: MutableMap<String, String> = mutableMapOf()
internal val internalListeners = mutableListOf<SnOn<Widget>.() -> Unit>()
internal val listeners = mutableListOf<SnOn<Widget>.() -> Unit>()
@@ -243,6 +244,9 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
if (draggable == true) {
snattrs.add("draggable" to "true")
}
+ if (attributes.isNotEmpty()) {
+ snattrs += attributes.toList()
+ }
return snattrs
}
@@ -565,6 +569,22 @@ open class Widget(classes: Set<String> = setOf()) : StyledComponent() {
return removeSurroundingCssClass(css.className)
}
+ override fun getAttribute(name: String): String? {
+ return this.attributes[name]
+ }
+
+ override fun setAttribute(name: String, value: String): Widget {
+ this.attributes[name] = value
+ refresh()
+ return this
+ }
+
+ override fun removeAttribute(name: String): Widget {
+ this.attributes.remove(name)
+ refresh()
+ return this
+ }
+
override fun getElement(): Node? {
return this.vnode?.elm
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt b/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt
index 57ce88eb..3019fb6f 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/FormControl.kt
@@ -47,6 +47,16 @@ interface FormInput : Component {
* The name attribute of the generated HTML input element.
*/
var name: String?
+
+ /**
+ * Makes the input element focused.
+ */
+ fun focus()
+
+ /**
+ * Makes the input element blur.
+ */
+ fun blur()
}
/**
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt
index 6973efe9..07b86b4c 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/check/CheckInput.kt
@@ -152,14 +152,14 @@ abstract class CheckInput(
/**
* Makes the input element focused.
*/
- open fun focus() {
+ override fun focus() {
getElementJQuery()?.focus()
}
/**
* Makes the input element blur.
*/
- open fun blur() {
+ override fun blur() {
getElementJQuery()?.blur()
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt
index 98839982..fca681f6 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/check/RadioGroupInput.kt
@@ -150,11 +150,11 @@ open class RadioGroupInput(
}
}
- fun focus() {
+ override fun focus() {
getChildren().filterIsInstance<Radio>().firstOrNull()?.focus()
}
- fun blur() {
+ override fun blur() {
getChildren().filterIsInstance<Radio>().firstOrNull()?.blur()
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt
new file mode 100644
index 00000000..4d278ad2
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelect.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.form.select
+
+import pl.treksoft.kvision.core.Component
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.StringBoolPair
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.form.FieldLabel
+import pl.treksoft.kvision.form.HelpBlock
+import pl.treksoft.kvision.form.StringFormControl
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.SnOn
+
+/**
+ * The form field component for SimpleSelect control.
+ *
+ * @constructor
+ * @param options an optional list of options (value to label pairs) for the select control
+ * @param value selected value
+ * @param emptyOption determines if an empty option is automatically generated
+ * @param name the name attribute of the generated HTML input element
+ * @param label label text bound to the input element
+ * @param rich determines if [label] can contain HTML code
+ */
+@Suppress("TooManyFunctions")
+open class SimpleSelect(
+ options: List<StringPair>? = null, value: String? = null, emptyOption: Boolean = false,
+ name: String? = null, label: String? = null, rich: Boolean = false
+) : SimplePanel(setOf("form-group")), StringFormControl {
+
+ /**
+ * A list of options (value to label pairs) for the select control.
+ */
+ var options
+ get() = input.options
+ set(value) {
+ input.options = value
+ }
+ /**
+ * A value of the selected option.
+ */
+ override var value
+ get() = input.value
+ set(value) {
+ input.value = value
+ }
+ /**
+ * The value of the selected child option.
+ *
+ * This value is placed directly in the generated HTML code, while the [value] property is dynamically
+ * bound to the select component.
+ */
+ var startValue
+ get() = input.startValue
+ set(value) {
+ input.startValue = value
+ }
+ /**
+ * Determines if an empty option is automatically generated.
+ */
+ var emptyOption
+ get() = input.emptyOption
+ set(value) {
+ input.emptyOption = value
+ }
+ /**
+ * Determines if the select is automatically focused.
+ */
+ var autofocus
+ get() = input.autofocus
+ set(value) {
+ input.autofocus = value
+ }
+ /**
+ * The label text bound to the select element.
+ */
+ var label
+ get() = flabel.content
+ set(value) {
+ flabel.content = value
+ }
+ /**
+ * Determines if [label] can contain HTML code.
+ */
+ var rich
+ get() = flabel.rich
+ set(value) {
+ flabel.rich = value
+ }
+
+ private val idc = "kv_form_simpleselect_$counter"
+ final override val input: SimpleSelectInput = SimpleSelectInput(
+ options, value, emptyOption, setOf("form-control")
+ ).apply {
+ this.id = idc
+ this.name = name
+ }
+ final override val flabel: FieldLabel = FieldLabel(idc, label, rich)
+ final override val validationInfo: HelpBlock = HelpBlock().apply { visible = false }
+
+ init {
+ @Suppress("LeakingThis")
+ input.eventTarget = this
+ this.addInternal(flabel)
+ this.addInternal(input)
+ this.addInternal(validationInfo)
+ counter++
+ }
+
+ override fun getSnClass(): List<StringBoolPair> {
+ val cl = super.getSnClass().toMutableList()
+ if (validatorError != null) {
+ cl.add("has-error" to true)
+ }
+ return cl
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ override fun <T : Widget> setEventListener(block: SnOn<T>.() -> Unit): Widget {
+ input.setEventListener(block)
+ return this
+ }
+
+ override fun setEventListener(block: SnOn<Widget>.() -> Unit): Widget {
+ input.setEventListener(block)
+ return this
+ }
+
+ override fun removeEventListeners(): Widget {
+ input.removeEventListeners()
+ return this
+ }
+
+ override fun add(child: Component): SimplePanel {
+ input.add(child)
+ return this
+ }
+
+ override fun addAll(children: List<Component>): SimplePanel {
+ input.addAll(children)
+ return this
+ }
+
+ override fun remove(child: Component): SimplePanel {
+ input.remove(child)
+ return this
+ }
+
+ override fun removeAll(): SimplePanel {
+ input.removeAll()
+ return this
+ }
+
+ override fun getChildren(): List<Component> {
+ return input.getChildren()
+ }
+
+ override fun focus() {
+ input.focus()
+ }
+
+ override fun blur() {
+ input.blur()
+ }
+
+ companion object {
+ internal var counter = 0
+
+ /**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+ fun Container.simpleSelect(
+ options: List<StringPair>? = null,
+ value: String? = null,
+ emptyOption: Boolean = false,
+ name: String? = null,
+ label: String? = null,
+ rich: Boolean = false,
+ init: (SimpleSelect.() -> Unit)? = null
+ ): SimpleSelect {
+ val simpleSelect = SimpleSelect(options, value, emptyOption, name, label, rich).apply { init?.invoke(this) }
+ this.add(simpleSelect)
+ return simpleSelect
+ }
+ }
+}
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt
new file mode 100644
index 00000000..df334c1c
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/form/select/SimpleSelectInput.kt
@@ -0,0 +1,213 @@
+/*
+ * 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.form.select
+
+import com.github.snabbdom.VNode
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.StringBoolPair
+import pl.treksoft.kvision.core.StringPair
+import pl.treksoft.kvision.form.FormInput
+import pl.treksoft.kvision.form.InputSize
+import pl.treksoft.kvision.html.TAG
+import pl.treksoft.kvision.html.Tag
+import pl.treksoft.kvision.panel.SimplePanel
+
+internal const val KVNULL = "#kvnull"
+
+/**
+ * Simple select component.
+ *
+ * @constructor
+ * @param options an optional list of options (value to label pairs) for the select control
+ * @param value text input value
+ * @param emptyOption determines if an empty option is automatically generated
+ * @param classes a set of CSS class names
+ */
+open class SimpleSelectInput(
+ options: List<StringPair>? = null, value: String? = null, emptyOption: Boolean = false,
+ classes: Set<String> = setOf()
+) : SimplePanel(classes + "form-control"), FormInput {
+
+ /**
+ * A list of options (value to label pairs) for the select control.
+ */
+ var options by refreshOnUpdate(options) { setChildrenFromOptions() }
+
+ /**
+ * Text input value.
+ */
+ var value by refreshOnUpdate(value) { refreshState() }
+ /**
+ * The value of the selected child option.
+ *
+ * This value is placed directly in the generated HTML code, while the [value] property is dynamically
+ * bound to the select component.
+ */
+ var startValue by refreshOnUpdate(value) { this.value = it; selectOption() }
+ /**
+ * The name attribute of the generated HTML input element.
+ */
+ override var name: String? by refreshOnUpdate()
+ /**
+ * Determines if the field is disabled.
+ */
+ override var disabled by refreshOnUpdate(false)
+ /**
+ * Determines if the text input is automatically focused.
+ */
+ var autofocus: Boolean? by refreshOnUpdate()
+ /**
+ * Determines if an empty option is automatically generated.
+ */
+ var emptyOption by refreshOnUpdate(emptyOption) { setChildrenFromOptions() }
+ /**
+ * The size of the input.
+ */
+ override var size: InputSize? by refreshOnUpdate()
+
+ init {
+ this.vnkey = "kv_simpleselectinput_${counter++}"
+ setChildrenFromOptions()
+ this.setInternalEventListener<SimpleSelectInput> {
+ change = {
+ self.changeValue()
+ }
+ }
+ }
+
+ override fun render(): VNode {
+ return render("select", childrenVNodes())
+ }
+
+ private fun setChildrenFromOptions() {
+ super.removeAll()
+ if (emptyOption) {
+ super.add(Tag(TAG.OPTION, "", attributes = mapOf("value" to KVNULL)))
+ }
+ options?.let {
+ val c = it.map {
+ val attributes = if (it.first == value) {
+ mapOf("value" to it.first, "selected" to "selected")
+ } else {
+ mapOf("value" to it.first)
+ }
+ Tag(TAG.OPTION, it.second, attributes = attributes)
+ }
+ super.addAll(c)
+ }
+ }
+
+ private fun selectOption() {
+ children.forEach { child ->
+ if (child is Tag && child.type == TAG.OPTION) {
+ if (value != null && child.getAttribute("value") == value) {
+ child.setAttribute("selected", "selected")
+ } else {
+ child.removeAttribute("selected")
+ }
+ }
+ }
+ }
+
+ override fun getSnClass(): List<StringBoolPair> {
+ val cl = super.getSnClass().toMutableList()
+ size?.let {
+ cl.add(it.className to true)
+ }
+ return cl
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ name?.let {
+ sn.add("name" to it)
+ }
+ autofocus?.let {
+ if (it) {
+ sn.add("autofocus" to "autofocus")
+ }
+ }
+ if (disabled) {
+ sn.add("disabled" to "disabled")
+ }
+ return sn
+ }
+
+ override fun afterInsert(node: VNode) {
+ refreshState()
+ }
+
+ /**
+ * @suppress
+ * Internal function
+ */
+ protected open fun refreshState() {
+ value?.let {
+ getElementJQuery()?.`val`(it)
+ } ?: getElementJQueryD()?.`val`(null)
+ }
+
+ /**
+ * @suppress
+ * Internal function
+ */
+ protected open fun changeValue() {
+ val v = getElementJQuery()?.`val`() as String?
+ if (v != null && v.isNotEmpty() && v != KVNULL) {
+ this.value = v
+ } else {
+ this.value = null
+ }
+ }
+
+ /**
+ * Makes the input element focused.
+ */
+ override fun focus() {
+ getElementJQuery()?.focus()
+ }
+
+ /**
+ * Makes the input element blur.
+ */
+ override fun blur() {
+ getElementJQuery()?.blur()
+ }
+
+ companion object {
+ internal var counter = 0
+
+ /**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+ fun Container.simpleSelectInput(
+ options: List<StringPair>? = null, value: String? = null, emptyOption: Boolean = false,
+ classes: Set<String> = setOf(), init: (SimpleSelectInput.() -> Unit)? = null
+ ): SimpleSelectInput {
+ val simpleSelectInput = SimpleSelectInput(options, value, emptyOption, classes).apply { init?.invoke(this) }
+ this.add(simpleSelectInput)
+ return simpleSelectInput
+ }
+ }
+}
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt
index c9ea2dba..393ae63f 100644
--- a/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/form/text/AbstractTextInput.kt
@@ -157,14 +157,14 @@ abstract class AbstractTextInput(
/**
* Makes the input element focused.
*/
- open fun focus() {
+ override fun focus() {
getElementJQuery()?.focus()
}
/**
* Makes the input element blur.
*/
- open fun blur() {
+ override fun blur() {
getElementJQuery()?.blur()
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
index 32119a90..68e43da0 100644
--- a/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/html/Tag.kt
@@ -69,6 +69,7 @@ enum class TAG(internal val tagName: String) {
SAMP("samp"),
SPAN("span"),
LI("li"),
+ HR("hr"),
CAPTION("caption"),
THEAD("thead"),
@@ -78,7 +79,10 @@ enum class TAG(internal val tagName: String) {
TD("td"),
FORM("form"),
- INPUT("input")
+ INPUT("input"),
+ SELECT("select"),
+ OPTION("option"),
+ BUTTON("button")
}
/**
@@ -101,11 +105,13 @@ enum class Align(val className: String) {
* @param rich determines if [content] can contain HTML code
* @param align content align
* @param classes a set of CSS class names
+ * @param attributes a map of additional attributes
* @param init an initializer extension function
*/
open class Tag(
type: TAG, content: String? = null, rich: Boolean = false, align: Align? = null,
- classes: Set<String> = setOf(), init: (Tag.() -> Unit)? = null
+ classes: Set<String> = setOf(), attributes: Map<String, String> = mapOf(),
+ init: (Tag.() -> Unit)? = null
) : SimplePanel(classes), Template {
/**
@@ -139,6 +145,7 @@ open class Tag(
override var templates: Map<String, (Any?) -> String> by refreshOnUpdate(mapOf())
init {
+ this.attributes += attributes
@Suppress("LeakingThis")
init?.invoke(this)
}
@@ -187,9 +194,10 @@ open class Tag(
*/
fun Container.tag(
type: TAG, content: String? = null, rich: Boolean = false, align: Align? = null,
- classes: Set<String> = setOf(), init: (Tag.() -> Unit)? = null
+ classes: Set<String> = setOf(), attributes: Map<String, String> = mapOf(),
+ init: (Tag.() -> Unit)? = null
): Tag {
- val tag = Tag(type, content, rich, align, classes, init)
+ val tag = Tag(type, content, rich, align, classes, attributes, init)
this.add(tag)
return tag
}
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt
index 310d4d49..010f7cba 100644
--- a/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt
@@ -24,7 +24,7 @@ package pl.treksoft.kvision.panel
import pl.treksoft.kvision.core.Component
import pl.treksoft.kvision.core.Container
import pl.treksoft.kvision.core.StringPair
-import pl.treksoft.kvision.core.StyledComponent
+import pl.treksoft.kvision.core.Widget
import pl.treksoft.kvision.core.WidgetWrapper
import pl.treksoft.kvision.utils.px
@@ -152,10 +152,10 @@ open class FlexPanel(
}
private fun refreshSpacing() {
- getChildren().filterIsInstance<StyledComponent>().map { applySpacing(it) }
+ getChildren().filterIsInstance<Widget>().map { applySpacing(it) }
}
- private fun applySpacing(wrapper: StyledComponent): StyledComponent {
+ private fun applySpacing(wrapper: Widget): Widget {
wrapper.marginTop = null
wrapper.marginRight = null
wrapper.marginBottom = null
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
index c17ea1a4..2d9dcc46 100644
--- a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt
@@ -23,6 +23,7 @@ package pl.treksoft.kvision.panel
import com.github.snabbdom.VNode
import com.github.snabbdom.h
+import org.w3c.dom.HTMLElement
import pl.treksoft.kvision.KVManager
import pl.treksoft.kvision.core.StringBoolPair
import pl.treksoft.kvision.core.Style
@@ -45,7 +46,12 @@ import pl.treksoft.kvision.utils.snOpt
* @param init an initializer extension function
*/
@Suppress("TooManyFunctions")
-class Root(id: String, private val fixed: Boolean = false, init: (Root.() -> Unit)? = null) : SimplePanel() {
+class Root(
+ id: String? = null,
+ element: HTMLElement? = null,
+ private val fixed: Boolean = false,
+ init: (Root.() -> Unit)? = null
+) : SimplePanel() {
private val contextMenus: MutableList<ContextMenu> = mutableListOf()
private var rootVnode: VNode = renderVNode()
@@ -54,11 +60,17 @@ class Root(id: String, private val fixed: Boolean = false, init: (Root.() -> Uni
val isFirstRoot = roots.isEmpty()
init {
- rootVnode = KVManager.patch(id, this.renderVNode())
- this.id = id
+ if (id != null) {
+ rootVnode = KVManager.patch(id, this.renderVNode())
+ this.id = id
+ } else if (element != null) {
+ rootVnode = KVManager.patch(element, this.renderVNode())
+ this.id = "kv_root_${counter++}"
+ } else {
+ throw IllegalArgumentException("No root element specified!")
+ }
roots.add(this)
if (isFirstRoot) {
- Style.styles.forEach { it.parent = this }
Modal.modals.forEach { it.parent = this }
}
@Suppress("LeakingThis")
@@ -89,9 +101,8 @@ class Root(id: String, private val fixed: Boolean = false, init: (Root.() -> Uni
private fun stylesVNodes(): Array<VNode> {
return if (isFirstRoot) {
- val visibleStyles = Style.styles.filter { it.visible }
- if (visibleStyles.isNotEmpty()) {
- val stylesDesc = visibleStyles.joinToString("\n") { it.generateStyle() }
+ if (Style.styles.isNotEmpty()) {
+ val stylesDesc = Style.styles.joinToString("\n") { it.generateStyle() }
arrayOf(h("style", arrayOf(stylesDesc)))
} else {
arrayOf()
@@ -144,6 +155,8 @@ class Root(id: String, private val fixed: Boolean = false, init: (Root.() -> Uni
}
companion object {
+ internal var counter = 0
+
/**
* @suppress internal function
*/
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt
index 3b045fa6..37dd449b 100644
--- a/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt
@@ -55,6 +55,8 @@ open class StackPanel(
activeIndex = children.indexOf(value)
}
+ internal val childrenMap = mutableMapOf<Int, Component>()
+
init {
@Suppress("LeakingThis")
init?.invoke(this)
@@ -76,8 +78,11 @@ open class StackPanel(
*/
open fun add(panel: Component, route: String): StackPanel {
add(panel)
- val currentIndex = children.size - 1
- routing.on(route, { _ -> activeIndex = currentIndex }).resolve()
+ val currentIndex = counter++
+ childrenMap[currentIndex] = panel
+ routing.on(route, { _ ->
+ activeChild = childrenMap[currentIndex]!!
+ }).resolve()
return this
}
@@ -97,17 +102,23 @@ open class StackPanel(
override fun remove(child: Component): StackPanel {
super.remove(child)
+ childrenMap.filter { it.value == child }.keys.firstOrNull()?.let {
+ childrenMap.remove(it)
+ }
if (activeIndex > children.size - 1) activeIndex = children.size - 1
return this
}
override fun removeAll(): StackPanel {
super.removeAll()
+ childrenMap.clear()
if (activeIndex > children.size - 1) activeIndex = children.size - 1
return this
}
companion object {
+ internal var counter = 0
+
/**
* DSL builder extension function.
*
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt
index 5a4fac0c..ae8360b5 100644
--- a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt
@@ -25,10 +25,13 @@ import pl.treksoft.kvision.core.Component
import pl.treksoft.kvision.core.Container
import pl.treksoft.kvision.core.ResString
import pl.treksoft.kvision.core.WidgetWrapper
-import pl.treksoft.kvision.html.Link
+import pl.treksoft.kvision.html.Icon
+import pl.treksoft.kvision.html.Link.Companion.link
import pl.treksoft.kvision.html.TAG
import pl.treksoft.kvision.html.Tag
import pl.treksoft.kvision.routing.routing
+import pl.treksoft.kvision.utils.obj
+import pl.treksoft.kvision.html.Icon.Companion.icon as cicon
/**
* Tab position.
@@ -95,6 +98,8 @@ open class TabPanel(
private var nav = Tag(TAG.UL, classes = navClasses)
private var content = StackPanel(false)
+ internal val childrenMap = mutableMapOf<Int, Component>()
+
init {
when (tabPosition) {
TabPosition.TOP -> {
@@ -135,23 +140,46 @@ open class TabPanel(
* @param panel child component
* @param icon icon of the tab
* @param image image of the tab
+ * @param closable determines if this tab is closable
* @param route JavaScript route to activate given child
* @return current container
*/
open fun addTab(
title: String, panel: Component, icon: String? = null,
- image: ResString? = null, route: String? = null
+ image: ResString? = null, closable: Boolean = false, route: String? = null
): TabPanel {
- val tag = Tag(TAG.LI)
- tag.role = "presentation"
- tag.add(Link(title, "#", icon, image))
- val index = nav.children.size
- tag.setEventListener {
- click = { e ->
- activeIndex = index
- e.preventDefault()
- if (route != null) {
- routing.navigate(route)
+ val currentIndex = counter++
+ childrenMap[currentIndex] = panel
+ val tag = Tag(TAG.LI) {
+ role = "presentation"
+ link(title, "#", icon, image) {
+ if (closable) {
+ cicon("remove") {
+ addCssClass("kv-tab-close")
+ setEventListener<Icon> {
+ click = { e ->
+ val actIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex])
+ e.asDynamic().data = actIndex
+ if (this@TabPanel.dispatchEvent(
+ "tabClosing",
+ obj { detail = e; cancelable = true }) != false
+ ) {
+ this@TabPanel.removeTab(actIndex)
+ this@TabPanel.dispatchEvent("tabClosed", obj { detail = e })
+ }
+ e.stopPropagation()
+ }
+ }
+ }
+ }
+ }
+ setEventListener {
+ click = { e ->
+ activeIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex])
+ e.preventDefault()
+ if (route != null) {
+ routing.navigate(route)
+ }
}
}
}
@@ -162,7 +190,8 @@ open class TabPanel(
}
content.add(panel)
if (route != null) {
- routing.on(route, { _ -> activeIndex = index }).resolve()
+ routing.on(route, { _ -> activeIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex]) })
+ .resolve()
}
return this
}
@@ -172,6 +201,9 @@ open class TabPanel(
*/
open fun removeTab(index: Int): TabPanel {
nav.remove(nav.children[index])
+ childrenMap.filter { it.value == content.children[index] }.keys.firstOrNull()?.let {
+ childrenMap.remove(it)
+ }
content.remove(content.children[index])
activeIndex = content.activeIndex
return this
@@ -191,14 +223,32 @@ open class TabPanel(
return removeTab(index)
}
+ /**
+ * Returns child component by tab index.
+ * @param index tab index
+ */
+ open fun getChildComponent(index: Int): Component? {
+ return content.children[index]
+ }
+
+ /**
+ * Returns tab header component by tab index.
+ * @param index tab index
+ */
+ open fun getNavComponent(index: Int): Tag? {
+ return nav.children[index] as? Tag
+ }
+
override fun removeAll(): TabPanel {
content.removeAll()
nav.removeAll()
+ childrenMap.clear()
refresh()
return this
}
companion object {
+ internal var counter = 0
/**
* DSL builder extension function.
*
diff --git a/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt b/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt
index 950dfe1c..4628d989 100644
--- a/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/routing/Routing.kt
@@ -48,4 +48,4 @@ open class Routing : Navigo(null, true, "#!") {
/**
* Default JavaScript router.
*/
-var routing = Routing()
+var routing = Routing().also { it.resolve() }
diff --git a/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt b/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt
index d2177a97..cb48cfd1 100644
--- a/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt
+++ b/src/main/kotlin/pl/treksoft/kvision/utils/Snabbdom.kt
@@ -130,6 +130,8 @@ interface BtOn : On {
var tabulatorDataLoading: ((KvEvent) -> Unit)?
var tabulatorDataLoaded: ((KvEvent) -> Unit)?
var tabulatorDataEdited: ((KvEvent) -> Unit)?
+ var tabClosing: ((KvEvent) -> Unit)?
+ var tabClosed: ((KvEvent) -> Unit)?
}
/**
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt b/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
index 37d7a9df..13c8531b 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -86,7 +86,7 @@ interface WSpec : DomSpec {
fun runW(code: (widget: Widget, element: Element?) -> Unit) {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
widget.id = "test_id"
root.add(widget)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt
index 3e16fff8..960a18b6 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt
@@ -34,7 +34,7 @@ class ContainerSpec : DomSpec {
@Test
fun add() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val container = SimplePanel()
val child1 = Widget()
child1.id = "child1"
@@ -52,7 +52,7 @@ class ContainerSpec : DomSpec {
@Test
fun addAll() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val container = SimplePanel()
val child1 = Widget()
child1.id = "child1"
@@ -69,7 +69,7 @@ class ContainerSpec : DomSpec {
@Test
fun remove() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val container = SimplePanel()
val child1 = Widget()
child1.id = "child1"
@@ -88,7 +88,7 @@ class ContainerSpec : DomSpec {
@Test
fun removeAll() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val container = SimplePanel()
val child1 = Widget()
child1.id = "child1"
@@ -107,7 +107,7 @@ class ContainerSpec : DomSpec {
@Test
fun getChildren() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val container = SimplePanel()
val child1 = Widget()
child1.id = "child1"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt
index bdb93744..4fbe3d59 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/core/StyleSpec.kt
@@ -37,7 +37,7 @@ class StyleSpec : DomSpec {
@Test
fun render() {
run {
- Root("test", true) {
+ Root("test", fixed = true) {
widget {
style {
margin = 2.px
@@ -58,7 +58,7 @@ class StyleSpec : DomSpec {
@Test
fun renderCustomClass() {
run {
- Root("test", true) {
+ Root("test", fixed = true) {
widget {
style("customclass") {
margin = 2.px
@@ -79,7 +79,7 @@ class StyleSpec : DomSpec {
@Test
fun renderSubclass() {
run {
- Root("test", true) {
+ Root("test", fixed = true) {
widget {
style("customclass") {
margin = 2.px
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt
index fc1cc761..6b9be23d 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt
@@ -130,7 +130,7 @@ class WidgetSpec : WSpec {
@Test
fun getRoot() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
root.add(widget)
val r = widget.getRoot()
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt
index aac92c50..0c28c327 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetWrapperSpec.kt
@@ -35,7 +35,7 @@ class WidgetWrapperSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val widget = Widget()
val wrapper = WidgetWrapper(widget)
wrapper.width = 100 to UNIT.em
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt
index 7d320e2e..35172267 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/ContextMenuSpec.kt
@@ -34,7 +34,7 @@ class ContextMenuSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val m = ContextMenu {
link("a", "b")
link("c", "d")
@@ -54,7 +54,7 @@ class ContextMenuSpec : DomSpec {
@Test
fun positionMenu() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val m = ContextMenu {
link("a", "b")
link("c", "d")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt
index 5a9a050c..e75baf9e 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/HeaderSpec.kt
@@ -32,7 +32,7 @@ class HeaderSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val h = Header("Test")
root.add(h)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt
index 2f2d22bf..86607ec7 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/dropdown/SeparatorSpec.kt
@@ -32,7 +32,7 @@ class SeparatorSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val s = Separator()
root.add(s)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt
index d13bc920..5319d4bc 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/FieldLabelSpec.kt
@@ -32,7 +32,7 @@ class FieldLabelSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val fl = FieldLabel("input", "Label")
root.add(fl)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt
index c7d0b0da..c7c4ede5 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/HelpBlockSpec.kt
@@ -32,7 +32,7 @@ class HelpBlockSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val fl = HelpBlock("Form Error")
root.add(fl)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt
index 8a9f86d5..677a2b8e 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxInputSpec.kt
@@ -32,7 +32,7 @@ class CheckBoxInputSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ci = CheckBoxInput(value = true).apply {
name = "name"
id = "idti"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt
index 9a178abb..16da0c70 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/CheckBoxSpec.kt
@@ -33,7 +33,7 @@ class CheckBoxSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ci = CheckBox(value = true, label = "Label").apply {
name = "name"
style = CheckBoxStyle.DANGER
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt
index f74a76f7..55788c84 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupInputSpec.kt
@@ -33,7 +33,7 @@ class RadioGroupInputSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ci = RadioGroupInput(options = listOf("a" to "A", "b" to "B"), value = "a").apply {
disabled = true
inline = true
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt
index e55e9913..2ed52b67 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioGroupSpec.kt
@@ -33,7 +33,7 @@ class RadioGroupSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ci = RadioGroup(options = listOf("a" to "A", "b" to "B"), value = "a", label = "Label").apply {
disabled = true
inline = true
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt
index 55d4108a..5b4fe836 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioInputSpec.kt
@@ -32,7 +32,7 @@ class RadioInputSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ci = RadioInput(value = true).apply {
name = "name"
id = "idti"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt
index 09763cc7..a8fbbcc5 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/check/RadioSpec.kt
@@ -33,7 +33,7 @@ class RadioSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ci = Radio(value = true, label = "Label", extraValue = "abc").apply {
name = "name"
style = RadioStyle.DANGER
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectInputSpec.kt
new file mode 100644
index 00000000..b2c77d10
--- /dev/null
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectInputSpec.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.form.select
+
+import pl.treksoft.kvision.form.select.SimpleSelectInput
+import pl.treksoft.kvision.panel.Root
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+
+class SimpleSelectInputSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", fixed = true)
+ val si = SimpleSelectInput(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", true).apply {
+ name = "name"
+ id = "idti"
+ disabled = true
+ }
+ root.add(si)
+ val element = document.getElementById("test")
+ assertEqualsHtml(
+ "<select class=\"form-control\" id=\"idti\" name=\"name\" disabled=\"disabled\"><option value=\"#kvnull\"></option><option value=\"test1\" selected=\"selected\">Test 1</option><option value=\"test2\">Test 2</option></select>",
+ element?.innerHTML,
+ "Should render correct simple select input control"
+ )
+ }
+ }
+
+}
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectSpec.kt
new file mode 100644
index 00000000..db1c36f0
--- /dev/null
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/select/SimpleSelectSpec.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.form.select
+
+import pl.treksoft.kvision.form.select.SimpleSelect
+import pl.treksoft.kvision.panel.Root
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+
+class SimpleSelectSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", fixed = true)
+ val select =
+ SimpleSelect(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", true, "select", "Label").apply {
+ name = "name"
+ id = "idti"
+ disabled = true
+ }
+ root.add(select)
+ val element = document.getElementById("test")
+ val id = select.input.id
+ assertEqualsHtml(
+ "<div class=\"form-group\" id=\"idti\"><label class=\"control-label\" for=\"$id\">Label</label><select class=\"form-control\" id=\"$id\" name=\"name\" disabled=\"disabled\"><option value=\"#kvnull\"></option><option value=\"test1\" selected=\"selected\">Test 1</option><option value=\"test2\">Test 2</option></select></div>",
+ element?.innerHTML,
+ "Should render correct simple select form control"
+ )
+ }
+ }
+
+}
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt
index dc46782b..5d6c2738 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/PasswordSpec.kt
@@ -32,7 +32,7 @@ class PasswordSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ti = Password(value = "abc", label = "Label").apply {
placeholder = "place"
name = "name"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt
index 0e8257ff..815f12e7 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaInputSpec.kt
@@ -32,7 +32,7 @@ class TextAreaInputSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ti = TextAreaInput(cols = 5, rows = 2, value = "abc").apply {
placeholder = "place"
name = "name"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt
index c948628b..31b4baf2 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextAreaSpec.kt
@@ -32,7 +32,7 @@ class TextAreaSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ti = TextArea(cols = 5, rows = 2, value = "abc", label = "Label").apply {
placeholder = "place"
name = "name"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt
index bd8c9786..6d21ab14 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextInputSpec.kt
@@ -33,7 +33,7 @@ class TextInputSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ti = TextInput(type = TextInputType.PASSWORD, value = "abc").apply {
placeholder = "place"
name = "name"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt
index 071bf35b..7ce811fa 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/form/text/TextSpec.kt
@@ -32,7 +32,7 @@ class TextSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ti = Text(value = "abc", label = "Label").apply {
placeholder = "place"
name = "name"
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt
index de2ea036..512c3bc2 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/ButtonSpec.kt
@@ -34,7 +34,7 @@ class ButtonSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val button = Button("Cancel", "fa-bars", ButtonStyle.PRIMARY)
button.size = ButtonSize.LARGE
button.block = true
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt
index a896d9e5..f30872ca 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/CanvasSpec.kt
@@ -32,7 +32,7 @@ class CanvasSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val canvas = Canvas(800, 600)
root.add(canvas)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt
index 4fd64478..528fc0ae 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/DivSpec.kt
@@ -32,7 +32,7 @@ class DivSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val div = Div("This is a div")
root.add(div)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt
index 6398d95c..69819203 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/FooterSpec.kt
@@ -32,7 +32,7 @@ class FooterSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val footer = Footer("This is a footer")
root.add(footer)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt
index 6f22b89f..bd0c69b6 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H1Spec.kt
@@ -32,7 +32,7 @@ class H1Spec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val h1 = H1("This is h1 header")
root.add(h1)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt
index 1ca74077..5ec2a666 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H2Spec.kt
@@ -32,7 +32,7 @@ class H2Spec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val h2 = H2("This is h2 header")
root.add(h2)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt
index a9f58473..f85ede34 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H3Spec.kt
@@ -32,7 +32,7 @@ class H3Spec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val h3 = H3("This is h3 header")
root.add(h3)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt
index 47550459..ce52282b 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H4Spec.kt
@@ -32,7 +32,7 @@ class H4Spec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val h4 = H4("This is h4 header")
root.add(h4)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt
index 6b3fc647..029a692f 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H5Spec.kt
@@ -32,7 +32,7 @@ class H5Spec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val h5 = H5("This is h5 header")
root.add(h5)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt
index e22bd401..06a851e0 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/H6Spec.kt
@@ -32,7 +32,7 @@ class H6Spec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val div = H1("This is h1 header")
root.add(div)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt
index e5ea8679..1b33adcc 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/HeaderSpec.kt
@@ -32,7 +32,7 @@ class HeaderSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val header = Header("This is a header")
root.add(header)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt
index fde07413..ea3425c5 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/IconSpec.kt
@@ -32,7 +32,7 @@ class IconSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val icon = Icon("fa-check")
root.add(icon)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt
index 60a380b7..69051f92 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/IframeSpec.kt
@@ -33,7 +33,7 @@ class IframeSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val iframe = Iframe("https://www.google.com", null, "test", 800, 600, setOf(Sandbox.ALLOWSAMEORIGIN))
root.add(iframe)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt
index fa32a24f..169575fc 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/ImageSpec.kt
@@ -34,7 +34,7 @@ class ImageSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val res = require("./img/placeholder.png")
@Suppress("UnsafeCastFromDynamic")
val image = Image(res, "Image", true, ImageShape.ROUNDED, true)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt
index 6a4434ae..88869cf4 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/LinkSpec.kt
@@ -32,7 +32,7 @@ class LinkSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val link = Link("Google", "http://www.google.com")
root.add(link)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt
index 5f0aac69..bf52f1dc 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/ListSpec.kt
@@ -35,7 +35,7 @@ class ListSpec : DomSpec {
@Test
fun renderElements() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val list = ListTag(ListType.DL_HORIZ, listOf("a1", "a2", "b1", "b2"))
root.add(list)
val element = document.getElementById("test")
@@ -50,7 +50,7 @@ class ListSpec : DomSpec {
@Test
fun renderAsContainer() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val list = ListTag(ListType.UL)
list.add(Tag(TAG.PRE, "pre"))
list.add(Tag(TAG.DEL, "del"))
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt
index d5af81e3..0ab9c5bc 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/PSpec.kt
@@ -32,7 +32,7 @@ class PSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val par = P("This is a paragraph")
root.add(par)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt
index 4fb7a490..fa987d05 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/SectionSpec.kt
@@ -32,7 +32,7 @@ class SectionSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val section = Section("This is a section")
root.add(section)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt
index b1f49b45..88604b5e 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/SpanSpec.kt
@@ -32,7 +32,7 @@ class SpanSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val span = Span("This is a label")
root.add(span)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt
index 29a5c079..b6017804 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/html/TagSpec.kt
@@ -35,7 +35,7 @@ class TagSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val tag = Tag(TAG.H1, "This is <b>h1</b>", rich = false, align = Align.CENTER)
root.add(tag)
val element = document.getElementById("test")
@@ -50,7 +50,7 @@ class TagSpec : DomSpec {
@Test
fun renderRich() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val tag = Tag(TAG.H1, "This is <b>h1</b>", rich = true, align = Align.RIGHT)
root.add(tag)
val element = document.getElementById("test")
@@ -65,7 +65,7 @@ class TagSpec : DomSpec {
@Test
fun renderAsContainer() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val tag = Tag(TAG.P, align = Align.RIGHT)
tag.add(Tag(TAG.DEL, "This is test"))
tag.add(Link("abc", "/x"))
@@ -82,7 +82,7 @@ class TagSpec : DomSpec {
@Test
fun renderUnaryPlus() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val tag = Tag(TAG.H1, rich = true) {
+"This is <b>h1</b>"
}
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt
index aeea7a3b..40720bcb 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavFormSpec.kt
@@ -32,7 +32,7 @@ class NavFormSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val navf = NavForm()
root.add(navf)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt
index 2ecad124..988a706d 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavSpec.kt
@@ -32,7 +32,7 @@ class NavSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val nav = Nav()
root.add(nav)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt
index 90af4450..f38a05f9 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/navbar/NavbarSpec.kt
@@ -37,7 +37,7 @@ class NavbarSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val navbar = Navbar("TEST", NavbarType.FIXEDTOP)
root.add(navbar)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt
index d387be56..5684120d 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/DockPanelSpec.kt
@@ -34,7 +34,7 @@ class DockPanelSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val dockPanel = DockPanel()
root.add(dockPanel)
dockPanel.add(Span("abc"), Side.UP)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt
index 5bcc68f6..b897fef2 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/FlexPanelSpec.kt
@@ -35,7 +35,7 @@ class FlexPanelSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val flexPanel = FlexPanel(FlexDir.ROWREV, justify = FlexJustify.SPACEEVENLY)
root.add(flexPanel)
flexPanel.add(Span("abc"), 1)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt
index add3638f..d7d9beb7 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/GridPanelSpec.kt
@@ -33,7 +33,7 @@ class GridPanelSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val gridPanel = GridPanel()
root.add(gridPanel)
gridPanel.add(Span("abc"), 1, 1)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt
index a185420c..c53b2e57 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/HPanelSpec.kt
@@ -34,7 +34,7 @@ class HPanelSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val hPanel = HPanel(justify = FlexJustify.SPACEBETWEEN)
root.add(hPanel)
hPanel.add(Span("abc"), 1)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt
index eb710cef..fcdf9860 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/ResponsiveGridPanelSpec.kt
@@ -33,7 +33,7 @@ class ResponsiveGridPanelSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val rgPanel = ResponsiveGridPanel()
root.add(rgPanel)
rgPanel.add(Span("abc"), 1, 1)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt
index 659dac9a..474b1d6a 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/RootSpec.kt
@@ -50,7 +50,7 @@ class RootSpec : DomSpec {
@Test
fun getRoot() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val r = root.getRoot()
assertTrue("Should return self") { r == root }
}
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt
index 09c397ce..94ed165a 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/SplitPanelSpec.kt
@@ -34,7 +34,7 @@ class SplitPanelSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val splitPanel = SplitPanel(Direction.VERTICAL)
root.add(splitPanel)
val label1 = Span("abc")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt
index f52b6486..8a1ce795 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/StackPanelSpec.kt
@@ -33,7 +33,7 @@ class StackPanelSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val stackPanel = StackPanel()
root.add(stackPanel)
val label1 = Span("abc")
@@ -48,7 +48,7 @@ class StackPanelSpec : DomSpec {
@Test
fun renderNotActivateLast() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val stackPanel = StackPanel(activateLast = false)
root.add(stackPanel)
val label1 = Span("abc")
@@ -67,7 +67,7 @@ class StackPanelSpec : DomSpec {
@Test
fun remove() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val stackPanel = StackPanel(activateLast = false)
root.add(stackPanel)
val label1 = Span("abc")
@@ -83,7 +83,7 @@ class StackPanelSpec : DomSpec {
@Test
fun removeAll() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val stackPanel = StackPanel(activateLast = false)
root.add(stackPanel)
val label1 = Span("abc")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt
index 900a7268..35620818 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/TabPanelSpec.kt
@@ -21,6 +21,7 @@
*/
package test.pl.treksoft.kvision.panel
+import pl.treksoft.jquery.jQuery
import pl.treksoft.kvision.html.Span
import pl.treksoft.kvision.panel.Root
import pl.treksoft.kvision.panel.TabPanel
@@ -33,7 +34,7 @@ class TabPanelSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val tabs = TabPanel()
root.add(tabs)
val label1 = Span("abc")
@@ -52,7 +53,7 @@ class TabPanelSpec : DomSpec {
@Test
fun setActiveIndex() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val tabs = TabPanel()
root.add(tabs)
val label1 = Span("abc")
@@ -72,7 +73,7 @@ class TabPanelSpec : DomSpec {
@Test
fun removeTab() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val tabs = TabPanel()
root.add(tabs)
val label1 = Span("abc")
@@ -89,4 +90,28 @@ class TabPanelSpec : DomSpec {
)
}
}
+
+
+ @Test
+ fun tabClick() {
+ run {
+ val root = Root("test", fixed = true)
+ val tabs = TabPanel()
+ root.add(tabs)
+ val label1 = Span("abc")
+ val label2 = Span("def")
+ tabs.addTab("ABC", label1)
+ tabs.addTab("DEF", label2)
+ tabs.removeTab(0)
+ val label3 = Span("ghi")
+ tabs.addTab("GHI", label3)
+ jQuery("#test a")[0]?.click()
+ val element = document.getElementById("test")
+ assertEqualsHtml(
+ "<div><ul class=\"nav nav-tabs\"><li role=\"presentation\" class=\"\"><a href=\"#\">DEF</a></li><li role=\"presentation\"><a href=\"#\">GHI</a></li></ul><div><span>def</span></div></div>",
+ element?.innerHTML,
+ "Should select correct tab by clicking"
+ )
+ }
+ }
} \ No newline at end of file
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt
index 81ea720d..5bed89f1 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/panel/VPanelSpec.kt
@@ -34,7 +34,7 @@ class VPanelSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val vPanel = VPanel(justify = FlexJustify.SPACEBETWEEN)
root.add(vPanel)
vPanel.add(Span("abc"), 1)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt
index d520f0b6..2f044987 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressBarSpec.kt
@@ -33,7 +33,7 @@ class ProgressBarSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val progressBar =
ProgressBar(50, style = ProgressBarStyle.SUCCESS, striped = true, content = "Processing ...")
root.add(progressBar)
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt
index 83892ed9..4aa14230 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/progress/ProgressIndicatorSpec.kt
@@ -33,7 +33,7 @@ class ProgressIndicatorSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val ind = ProgressIndicator(50, style = ProgressBarStyle.SUCCESS, striped = true)
root.add(ind)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt
index 435e9b22..582212bd 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/table/CellSpec.kt
@@ -32,7 +32,7 @@ class CellSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val cell = Cell("This is a cell")
root.add(cell)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt
index 8c210ae0..40f25a66 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/table/HeaderCellSpec.kt
@@ -32,7 +32,7 @@ class HeaderCellSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val cell = HeaderCell("This is a header cell")
root.add(cell)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt
index 6c2f3c1c..d9a6fdfa 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/table/RowSpec.kt
@@ -33,7 +33,7 @@ class RowSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val row = Row {
cell("A")
}
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt
index 637f8f28..997da597 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/table/TableSpec.kt
@@ -35,7 +35,7 @@ class TableSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val table = Table(listOf("a", "b")) {
row {
cell("A")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt
index 16b352d8..b324b8ab 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ButtonGroupSpec.kt
@@ -34,7 +34,7 @@ class ButtonGroupSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val group = ButtonGroup()
root.add(group)
val element = document.getElementById("test")
diff --git a/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt
index 4d296e03..d41ef05e 100644
--- a/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt
+++ b/src/test/kotlin/test/pl/treksoft/kvision/toolbar/ToolbarSpec.kt
@@ -32,7 +32,7 @@ class ToolbarSpec : DomSpec {
@Test
fun render() {
run {
- val root = Root("test", true)
+ val root = Root("test", fixed = true)
val toolbar = Toolbar()
root.add(toolbar)
val element = document.getElementById("test")