aboutsummaryrefslogtreecommitdiff
path: root/kvision-modules
diff options
context:
space:
mode:
Diffstat (limited to 'kvision-modules')
-rw-r--r--kvision-modules/kvision-base/build.gradle7
-rw-r--r--kvision-modules/kvision-bootstrap/build.gradle13
-rw-r--r--kvision-modules/kvision-bootstrap/package.json.d/project.info3
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.kt53
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/resources/css/paper.css12
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.js64
-rw-r--r--kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.less0
-rw-r--r--kvision-modules/kvision-common/build.gradle10
-rw-r--r--kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/remote/Jooby.kt37
-rw-r--r--kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/remote/JsonRpc.kt37
-rw-r--r--kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt139
-rw-r--r--kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/types/KDate.kt49
-rw-r--r--kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/types/KFile.kt34
-rw-r--r--kvision-modules/kvision-datetime/build.gradle9
-rw-r--r--kvision-modules/kvision-datetime/package.json.d/project.info3
-rw-r--r--kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/KVManagerDatetime.kt81
-rw-r--r--kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt246
-rw-r--r--kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt291
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ar.js18
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.az.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.bg.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.bn.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ca.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.cs.js20
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.da.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.de.js19
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ee.js19
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.el.js16
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.es.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.fi.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.fr.js19
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.he.js18
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hr.js16
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hu.js18
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hy.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.id.js20
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.is.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.it.js19
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ja.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ka.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ko.js18
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.lt.js19
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.lv.js19
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ms.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.nb.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.nl.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.no.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pl.js18
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pt-BR.js18
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pt.js18
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ro.js18
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.rs-latin.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.rs.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ru.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sk.js20
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sl.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sv.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sw.js18
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.th.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.tr.js18
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ua.js16
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.uk.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.zh-TW.js17
-rw-r--r--kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.zh.js17
-rw-r--r--kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt99
-rw-r--r--kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.kt53
-rw-r--r--kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt62
-rw-r--r--kvision-modules/kvision-handlebars/build.gradle10
-rw-r--r--kvision-modules/kvision-handlebars/package.json.d/project.info3
-rw-r--r--kvision-modules/kvision-handlebars/src/main/kotlin/pl/treksoft/kvision/KVManagerHandlebars.kt41
-rw-r--r--kvision-modules/kvision-i18n/build.gradle9
-rw-r--r--kvision-modules/kvision-i18n/package.json.d/project.info3
-rw-r--r--kvision-modules/kvision-i18n/src/main/kotlin/pl/treksoft/kvision/KVManagerI18n.kt41
-rw-r--r--kvision-modules/kvision-i18n/src/main/kotlin/pl/treksoft/kvision/i18n/DefaultI18nManager.kt44
-rw-r--r--kvision-modules/kvision-richtext/build.gradle9
-rw-r--r--kvision-modules/kvision-richtext/package.json.d/project.info3
-rw-r--r--kvision-modules/kvision-richtext/src/main/kotlin/pl/treksoft/kvision/KVManagerRichText.kt64
-rw-r--r--kvision-modules/kvision-richtext/src/main/kotlin/pl/treksoft/kvision/form/text/RichText.kt79
-rw-r--r--kvision-modules/kvision-richtext/src/main/kotlin/pl/treksoft/kvision/form/text/RichTextInput.kt133
-rw-r--r--kvision-modules/kvision-richtext/src/main/resources/js/locales/trix/trix.pl.js28
-rw-r--r--kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt99
-rw-r--r--kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.kt51
-rw-r--r--kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt58
-rw-r--r--kvision-modules/kvision-select/build.gradle10
-rw-r--r--kvision-modules/kvision-select/package.json.d/project.info3
-rw-r--r--kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/KVManagerSelect.kt65
-rw-r--r--kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/AjaxOptions.kt128
-rw-r--r--kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt285
-rw-r--r--kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt363
-rw-r--r--kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOptGroup.kt91
-rw-r--r--kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOption.kt103
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.de-DE.min.js22
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.en-US.min.js22
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.es-ES.min.js22
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.fr-FR.min.js22
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.it-IT.min.js22
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ja-JP.min.js22
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ko-KR.min.js22
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.nl-NL.min.js22
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.pl-PL.min.js22
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.pt-BR.min.js22
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ru-RU.min.js22
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.tr-TR.min.js22
-rw-r--r--kvision-modules/kvision-select/src/main/resources/js/locales/bootstrap-select/bootstrap-select-i18n.min.js1
-rw-r--r--kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt99
-rw-r--r--kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.kt53
-rw-r--r--kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt54
-rw-r--r--kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt59
-rw-r--r--kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt58
-rw-r--r--kvision-modules/kvision-server-jooby/build.gradle31
-rw-r--r--kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/Jooby.kt91
-rw-r--r--kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt338
-rw-r--r--kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/types/KDate.kt58
-rw-r--r--kvision-modules/kvision-spinner/build.gradle9
-rw-r--r--kvision-modules/kvision-spinner/package.json.d/project.info3
-rw-r--r--kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/KVManagerSpinner.kt42
-rw-r--r--kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt263
-rw-r--r--kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt323
-rw-r--r--kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt99
-rw-r--r--kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt75
-rw-r--r--kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt82
-rw-r--r--kvision-modules/kvision-upload/build.gradle9
-rw-r--r--kvision-modules/kvision-upload/package.json.d/project.info3
-rw-r--r--kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/KVManagerUpload.kt86
-rw-r--r--kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/Upload.kt333
-rw-r--r--kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt364
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ar.js101
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/az.js101
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/bg.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ca.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/cr.js101
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/cs.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/da.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/de.js98
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/el.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/es.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/et.js99
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/fa.js101
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/fi.js91
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/fr.js99
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/gl.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/hu.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/id.js101
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/it.js102
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ja.js109
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ka.js101
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ko.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/kz.js88
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/lt.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/nl.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/no.js99
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/pl.js90
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/pt-BR.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/pt.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ro.js101
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ru.js101
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/sk.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/sl.js98
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/sv.js99
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/th.js100
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/tr.js99
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/uk.js101
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/vi.js101
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/zh-TW.js102
-rw-r--r--kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/zh.js100
-rw-r--r--kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt99
-rw-r--r--kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt57
-rw-r--r--kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt56
-rw-r--r--kvision-modules/shared.gradle65
169 files changed, 10792 insertions, 0 deletions
diff --git a/kvision-modules/kvision-base/build.gradle b/kvision-modules/kvision-base/build.gradle
new file mode 100644
index 00000000..51970a40
--- /dev/null
+++ b/kvision-modules/kvision-base/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: 'kotlin-platform-js'
+
+dependencies {
+ compile rootProject
+ compile project(path: ":", configuration: "tests")
+ testCompile rootProject
+}
diff --git a/kvision-modules/kvision-bootstrap/build.gradle b/kvision-modules/kvision-bootstrap/build.gradle
new file mode 100644
index 00000000..9dc6953c
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap/build.gradle
@@ -0,0 +1,13 @@
+apply from: "../shared.gradle"
+
+kotlinFrontend {
+
+ npm {
+ dependency("bootstrap", "3.3.7")
+ dependency("bootstrap-webpack", "0.0.6")
+ dependency("font-awesome", "4.7.0")
+ dependency("font-awesome-webpack", "github:jarecsni/font-awesome-webpack")
+ dependency("awesome-bootstrap-checkbox", "0.3.7")
+ }
+
+}
diff --git a/kvision-modules/kvision-bootstrap/package.json.d/project.info b/kvision-modules/kvision-bootstrap/package.json.d/project.info
new file mode 100644
index 00000000..e77a0f47
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap/package.json.d/project.info
@@ -0,0 +1,3 @@
+{
+ "description": "KVision Bootstrap module"
+}
diff --git a/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.kt b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.kt
new file mode 100644
index 00000000..552f8d42
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap/src/main/kotlin/pl/treksoft/kvision/KVManagerBootstrap.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 pl.treksoft.kvision
+
+import org.w3c.dom.asList
+import kotlin.browser.document
+
+/**
+ * Internal singleton object which initializes and configures KVision Bootstrap module.
+ */
+@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught")
+internal object KVManagerBootstrap {
+ private val links = document.getElementsByTagName("link")
+ private val bootstrapWebpack = try {
+ val bootswatch = links.asList().find { it.getAttribute("href")?.contains("bootstrap.min.css") ?: false }
+ if (bootswatch != null) {
+ if (bootswatch.getAttribute("href")?.contains("/paper/") == true) {
+ require("./css/paper.css")
+ }
+ require("bootstrap-webpack!./js/bootstrap.config.js")
+ } else {
+ require("bootstrap-webpack")
+ }
+ } catch (e: Throwable) {
+ }
+ private val fontAwesomeWebpack = try {
+ require("font-awesome-webpack")
+ } catch (e: Throwable) {
+ }
+ private val awesomeBootstrapCheckbox = try {
+ require("awesome-bootstrap-checkbox")
+ } catch (e: Throwable) {
+ }
+}
diff --git a/kvision-modules/kvision-bootstrap/src/main/resources/css/paper.css b/kvision-modules/kvision-bootstrap/src/main/resources/css/paper.css
new file mode 100644
index 00000000..6e923117
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap/src/main/resources/css/paper.css
@@ -0,0 +1,12 @@
+body {
+ font-size: 14px;
+ line-height: 1.42857143;
+}
+
+.kv-radio-checkbox {
+ padding-left: 20px !important;
+}
+
+.radio label, .checkbox label {
+ white-space: nowrap;
+}
diff --git a/kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.js b/kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.js
new file mode 100644
index 00000000..906942d1
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.js
@@ -0,0 +1,64 @@
+module.exports = {
+
+ // Default for the style loading
+ styleLoader: 'style-loader!css-loader!less-loader',
+
+ scripts: {
+ 'transition': true,
+ 'alert': true,
+ 'button': true,
+ 'carousel': true,
+ 'collapse': true,
+ 'dropdown': true,
+ 'modal': true,
+ 'tooltip': true,
+ 'popover': true,
+ 'scrollspy': true,
+ 'tab': true,
+ 'affix': true
+ },
+ styles: {
+ "mixins": false,
+
+ "normalize": false,
+ "print": false,
+
+ "scaffolding": false,
+ "type": false,
+ "code": false,
+ "grid": false,
+ "tables": false,
+ "forms": false,
+ "buttons": false,
+
+ "component-animations": false,
+ "glyphicons": false,
+ "dropdowns": false,
+ "button-groups": false,
+ "input-groups": false,
+ "navs": false,
+ "navbar": false,
+ "breadcrumbs": false,
+ "pagination": false,
+ "pager": false,
+ "labels": false,
+ "badges": false,
+ "jumbotron": false,
+ "thumbnails": false,
+ "alerts": false,
+ "progress-bars": false,
+ "media": false,
+ "list-group": false,
+ "panels": false,
+ "wells": false,
+ "close": false,
+
+ "modals": false,
+ "tooltip": false,
+ "popovers": false,
+ "carousel": false,
+
+ "utilities": false,
+ "responsive-utilities": false
+ }
+};
diff --git a/kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.less b/kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.less
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap/src/main/resources/js/bootstrap.config.less
diff --git a/kvision-modules/kvision-common/build.gradle b/kvision-modules/kvision-common/build.gradle
new file mode 100644
index 00000000..af3703e6
--- /dev/null
+++ b/kvision-modules/kvision-common/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'kotlin-platform-common'
+apply plugin: 'kotlinx-serialization'
+
+dependencies {
+ compile "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion"
+ compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serializationVersion"
+ compile "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion"
+ testCompile "org.jetbrains.kotlin:kotlin-test-common:$kotlinVersion"
+ testCompile "org.jetbrains.kotlin:kotlin-test-annotations-common:$kotlinVersion"
+}
diff --git a/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/remote/Jooby.kt b/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/remote/Jooby.kt
new file mode 100644
index 00000000..ec348a25
--- /dev/null
+++ b/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/remote/Jooby.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.remote
+
+/**
+ * A Jooby based server.
+ */
+expect open class JoobyServer
+
+/**
+ * A server request.
+ */
+expect interface Request
+
+/**
+ * A user profile.
+ */
+expect class Profile
diff --git a/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/remote/JsonRpc.kt b/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/remote/JsonRpc.kt
new file mode 100644
index 00000000..7953ea01
--- /dev/null
+++ b/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/remote/JsonRpc.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.remote
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class JsonRpcRequest(val id: Int, val method: String, val params: List<String?>, val jsonrpc: String = "2.0") {
+ constructor() : this(0, "", listOf())
+}
+
+@Serializable
+data class JsonRpcResponse(
+ val id: Int? = null,
+ val result: String? = null,
+ val error: String? = null,
+ val jsonrpc: String = "2.0"
+)
diff --git a/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt b/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt
new file mode 100644
index 00000000..8225a785
--- /dev/null
+++ b/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.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.remote
+
+import kotlinx.coroutines.Deferred
+
+enum class RpcHttpMethod {
+ POST,
+ PUT,
+ DELETE,
+ OPTIONS
+}
+
+enum class HttpMethod {
+ GET,
+ POST,
+ PUT,
+ DELETE,
+ OPTIONS
+}
+
+/**
+ * Multiplatform service manager.
+ */
+expect open class ServiceManager<out T>(service: T) {
+ /**
+ * Binds a given route with a function of the receiver.
+ * @param function a function of the receiver
+ * @param route a route
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
+ */
+ protected inline fun <reified RET> bind(
+ noinline function: T.(Request?) -> Deferred<RET>,
+ route: String? = null,
+ method: RpcHttpMethod = RpcHttpMethod.POST,
+ prefix: String = "/"
+ )
+
+ /**
+ * Binds a given route with a function of the receiver.
+ * @param function a function of the receiver
+ * @param route a route
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
+ */
+ protected inline fun <reified PAR, reified RET> bind(
+ noinline function: T.(PAR, Request?) -> Deferred<RET>,
+ route: String? = null,
+ method: RpcHttpMethod = RpcHttpMethod.POST,
+ prefix: String = "/"
+ )
+
+ /**
+ * Binds a given route with a function of the receiver.
+ * @param function a function of the receiver
+ * @param route a route
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
+ */
+ protected inline fun <reified PAR1, reified PAR2, reified RET> bind(
+ noinline function: T.(PAR1, PAR2, Request?) -> Deferred<RET>,
+ route: String? = null,
+ method: RpcHttpMethod = RpcHttpMethod.POST,
+ prefix: String = "/"
+ )
+
+ /**
+ * Binds a given route with a function of the receiver.
+ * @param function a function of the receiver
+ * @param route a route
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
+ */
+ protected inline fun <reified PAR1, reified PAR2, reified PAR3, reified RET> bind(
+ noinline function: T.(PAR1, PAR2, PAR3, Request?) -> Deferred<RET>,
+ route: String? = null,
+ method: RpcHttpMethod = RpcHttpMethod.POST,
+ prefix: String = "/"
+ )
+
+ /**
+ * Binds a given route with a function of the receiver.
+ * @param function a function of the receiver
+ * @param route a route
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
+ */
+ protected inline fun <reified PAR1, reified PAR2, reified PAR3, reified PAR4, reified RET> bind(
+ noinline function: T.(PAR1, PAR2, PAR3, PAR4, Request?) -> Deferred<RET>,
+ route: String? = null,
+ method: RpcHttpMethod = RpcHttpMethod.POST,
+ prefix: String = "/"
+ )
+
+ /**
+ * Binds a given route with a function of the receiver.
+ * @param function a function of the receiver
+ * @param route a route
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
+ */
+ protected inline fun <reified PAR1, reified PAR2, reified PAR3, reified PAR4, reified PAR5, reified RET> bind(
+ noinline function: T.(PAR1, PAR2, PAR3, PAR4, PAR5, Request?) -> Deferred<RET>,
+ route: String? = null,
+ method: RpcHttpMethod = RpcHttpMethod.POST,
+ prefix: String = "/"
+ )
+
+ /**
+ * Applies all defined routes to the given server.
+ * @param k a Jooby server
+ */
+ fun applyRoutes(k: JoobyServer)
+
+ /**
+ * Returns the map of defined paths.
+ */
+ fun getCalls(): Map<String, Pair<String, RpcHttpMethod>>
+}
diff --git a/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/types/KDate.kt b/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/types/KDate.kt
new file mode 100644
index 00000000..0cb1a1f1
--- /dev/null
+++ b/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/types/KDate.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.types
+
+import kotlinx.serialization.Serializable
+
+expect val KDATE_FORMAT: String
+
+/**
+ * A serializable wrapper for a multiplatform Date type.
+ */
+@Serializable
+data class KDate(val time: Long) {
+ constructor() : this(now().time)
+ constructor(str: String) : this(str.toKDateF(KDATE_FORMAT).time)
+
+ override fun toString(): String {
+ return this.toStringF(KDATE_FORMAT)
+ }
+
+ companion object {
+ fun now() = nowDate()
+ }
+}
+
+expect fun nowDate(): KDate
+
+expect fun String.toKDateF(format: String): KDate
+
+expect fun KDate.toStringF(format: String): String
diff --git a/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/types/KFile.kt b/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/types/KFile.kt
new file mode 100644
index 00000000..ce4adca4
--- /dev/null
+++ b/kvision-modules/kvision-common/src/main/kotlin/pl/treksoft/kvision/types/KFile.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision.types
+
+import kotlinx.serialization.Serializable
+
+/**
+ * A serializable class for a multiplatform File type.
+ */
+@Serializable
+data class KFile(
+ val name: String,
+ val size: Int,
+ val content: String? = null
+)
diff --git a/kvision-modules/kvision-datetime/build.gradle b/kvision-modules/kvision-datetime/build.gradle
new file mode 100644
index 00000000..b853d2f4
--- /dev/null
+++ b/kvision-modules/kvision-datetime/build.gradle
@@ -0,0 +1,9 @@
+apply from: "../shared.gradle"
+
+kotlinFrontend {
+
+ npm {
+ dependency("bootstrap-datetime-picker", "2.4.4")
+ }
+
+}
diff --git a/kvision-modules/kvision-datetime/package.json.d/project.info b/kvision-modules/kvision-datetime/package.json.d/project.info
new file mode 100644
index 00000000..3d332806
--- /dev/null
+++ b/kvision-modules/kvision-datetime/package.json.d/project.info
@@ -0,0 +1,3 @@
+{
+ "description": "KVision Datetime module"
+}
diff --git a/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/KVManagerDatetime.kt b/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/KVManagerDatetime.kt
new file mode 100644
index 00000000..cde55b1e
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/KVManagerDatetime.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision
+
+internal val KVManagerDatetimeInit = KVManagerDatetime.init()
+
+/**
+ * Internal singleton object which initializes and configures KVision datetime module.
+ */
+@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught")
+internal object KVManagerDatetime {
+ fun init() {}
+
+ private val bootstrapDateTimePickerCss = try {
+ require("bootstrap-datetime-picker/css/bootstrap-datetimepicker.min.css")
+ } catch (e: Throwable) {
+ }
+ private val bootstrapDateTimePicker = try {
+ require("bootstrap-datetime-picker/js/bootstrap-datetimepicker.min.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ar.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.az.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.bg.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.bn.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ca.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.cs.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.da.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.de.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ee.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.el.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.es.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.fi.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.fr.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.he.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hr.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hu.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hy.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.id.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.is.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.it.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ja.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ko.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.lt.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.lv.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.nl.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.no.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pl.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pt.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ro.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.rs.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ru.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sk.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sl.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sv.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.th.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.tr.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ua.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.uk.js")
+ require("./js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.zh.js")
+ } catch (e: Throwable) {
+ }
+
+}
diff --git a/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt b/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt
new file mode 100644
index 00000000..9cdd0369
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTime.kt
@@ -0,0 +1,246 @@
+/*
+ * 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.time
+
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.StringBoolPair
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.form.FieldLabel
+import pl.treksoft.kvision.form.HelpBlock
+import pl.treksoft.kvision.form.KDateFormControl
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.types.KDate
+import pl.treksoft.kvision.utils.SnOn
+
+/**
+ * Form field date/time chooser component.
+ *
+ * @constructor
+ * @param value date/time input value
+ * @param name the name attribute of the generated HTML input element
+ * @param format date/time format (default YYYY-MM-DD HH:mm)
+ * @param label label text bound to the input element
+ * @param rich determines if [label] can contain HTML code
+ */
+open class DateTime(
+ value: KDate? = null, name: String? = null, format: String = "YYYY-MM-DD HH:mm", label: String? = null,
+ rich: Boolean = false
+) : SimplePanel(setOf("form-group")), KDateFormControl {
+
+ /**
+ * Date/time input value.
+ */
+ override var value
+ get() = input.value
+ set(value) {
+ input.value = value
+ }
+ /**
+ * Date/time format.
+ */
+ var format
+ get() = input.format
+ set(value) {
+ input.format = value
+ }
+ /**
+ * The placeholder for the date/time input.
+ */
+ var placeholder
+ get() = input.placeholder
+ set(value) {
+ input.placeholder = value
+ }
+ /**
+ * Determines if the date/time input is automatically focused.
+ */
+ var autofocus
+ get() = input.autofocus
+ set(value) {
+ input.autofocus = value
+ }
+ /**
+ * Determines if the date/time input is read-only.
+ */
+ var readonly
+ get() = input.readonly
+ set(value) {
+ input.readonly = value
+ }
+ /**
+ * Day of the week start. 0 (Sunday) to 6 (Saturday).
+ */
+ var weekStart
+ get() = input.weekStart
+ set(value) {
+ input.weekStart = value
+ }
+ /**
+ * Days of the week that should be disabled. Multiple values should be comma separated.
+ */
+ var daysOfWeekDisabled
+ get() = input.daysOfWeekDisabled
+ set(value) {
+ input.daysOfWeekDisabled = value
+ }
+ /**
+ * Determines if *Clear* button should be visible.
+ */
+ var clearBtn
+ get() = input.clearBtn
+ set(value) {
+ input.clearBtn = value
+ }
+ /**
+ * Determines if *Today* button should be visible.
+ */
+ var todayBtn
+ get() = input.todayBtn
+ set(value) {
+ input.todayBtn = value
+ }
+ /**
+ * Determines if the current day should be highlighted.
+ */
+ var todayHighlight
+ get() = input.todayHighlight
+ set(value) {
+ input.todayHighlight = value
+ }
+ /**
+ * The increment used to build the hour view.
+ */
+ var minuteStep
+ get() = input.minuteStep
+ set(value) {
+ input.minuteStep = value
+ }
+ /**
+ * Determines if meridian views are visible in day and hour views.
+ */
+ var showMeridian
+ get() = input.showMeridian
+ set(value) {
+ input.showMeridian = value
+ }
+ /**
+ * The label text bound to the input 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_time_$counter"
+ final override val input: DateTimeInput = DateTimeInput(value, format).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
+ }
+
+ /**
+ * Open date/time chooser popup.
+ */
+ open fun showPopup() {
+ input.showPopup()
+ }
+
+ /**
+ * Hides date/time chooser popup.
+ */
+ open fun hidePopup() {
+ input.hidePopup()
+ }
+
+ override fun getValueAsString(): String? {
+ return input.getValueAsString()
+ }
+
+ 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.dateTime(
+ value: KDate? = null, name: String? = null, format: String = "YYYY-MM-DD HH:mm", label: String? = null,
+ rich: Boolean = false, init: (DateTime.() -> Unit)? = null
+ ): DateTime {
+ val dateTime = DateTime(value, name, format, label, rich).apply { init?.invoke(this) }
+ this.add(dateTime)
+ return dateTime
+ }
+ }
+}
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
new file mode 100644
index 00000000..1df8a082
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/kotlin/pl/treksoft/kvision/form/time/DateTimeInput.kt
@@ -0,0 +1,291 @@
+/*
+ * 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.time
+
+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.core.Widget
+import pl.treksoft.kvision.form.FormInput
+import pl.treksoft.kvision.form.InputSize
+import pl.treksoft.kvision.i18n.I18n
+import pl.treksoft.kvision.types.KDate
+import pl.treksoft.kvision.types.toJS
+import pl.treksoft.kvision.types.toKDateF
+import pl.treksoft.kvision.types.toStringF
+import pl.treksoft.kvision.utils.obj
+
+internal const val DEFAULT_MINUTE_STEP = 5
+internal const val MAX_VIEW = 4
+
+/**
+ * Basic date/time chooser component.
+ *
+ * @constructor
+ * @param value date/time input value
+ * @param format date/time format (default YYYY-MM-DD HH:mm)
+ * @param classes a set of CSS class names
+ */
+@Suppress("TooManyFunctions")
+open class DateTimeInput(
+ value: KDate? = null, format: String = "YYYY-MM-DD HH:mm",
+ classes: Set<String> = setOf()
+) : Widget(classes + "form-control"), FormInput {
+
+ init {
+ this.setInternalEventListener<DateTimeInput> {
+ change = {
+ self.changeValue()
+ }
+ }
+ }
+
+ /**
+ * Date/time input value.
+ */
+ var value by refreshOnUpdate(value, { refreshState() })
+ /**
+ * Date/time format.
+ */
+ var format by refreshOnUpdate(format, { refreshDatePicker() })
+ /**
+ * The placeholder for the date/time input.
+ */
+ var placeholder: String? by refreshOnUpdate()
+ /**
+ * 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 the date/time input is read-only.
+ */
+ var readonly: Boolean? by refreshOnUpdate()
+ /**
+ * The size of the input.
+ */
+ override var size: InputSize? by refreshOnUpdate()
+ /**
+ * Day of the week start. 0 (Sunday) to 6 (Saturday).
+ */
+ var weekStart by refreshOnUpdate(0, { refreshDatePicker() })
+ /**
+ * Days of the week that should be disabled. Multiple values should be comma separated.
+ */
+ var daysOfWeekDisabled by refreshOnUpdate(arrayOf<Int>(), { refreshDatePicker() })
+ /**
+ * Determines if *Clear* button should be visible.
+ */
+ var clearBtn by refreshOnUpdate(true, { refreshDatePicker() })
+ /**
+ * Determines if *Today* button should be visible.
+ */
+ var todayBtn by refreshOnUpdate(false, { refreshDatePicker() })
+ /**
+ * Determines if the current day should be highlighted.
+ */
+ var todayHighlight by refreshOnUpdate(false, { refreshDatePicker() })
+ /**
+ * The increment used to build the hour view.
+ */
+ var minuteStep by refreshOnUpdate(DEFAULT_MINUTE_STEP, { refreshDatePicker() })
+ /**
+ * Determines if meridian views are visible in day and hour views.
+ */
+ var showMeridian by refreshOnUpdate(false, { refreshDatePicker() })
+
+ override fun render(): VNode {
+ return render("input")
+ }
+
+ 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()
+ sn.add("type" to "text")
+ placeholder?.let {
+ sn.add("placeholder" to translate(it))
+ }
+ name?.let {
+ sn.add("name" to it)
+ }
+ autofocus?.let {
+ if (it) {
+ sn.add("autofocus" to "autofocus")
+ }
+ }
+ readonly?.let {
+ if (it) {
+ sn.add("readonly" to "readonly")
+ }
+ }
+ if (disabled) {
+ sn.add("disabled" to "disabled")
+ value?.let {
+ sn.add("value" to it.toStringF(format))
+ }
+ }
+ return sn
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ protected open fun refreshState() {
+ value?.let {
+ getElementJQueryD()?.datetimepicker("update", it.toJS())
+ } ?: run {
+ getElementJQueryD()?.`val`(null)
+ getElementJQueryD()?.datetimepicker("update", null)
+ }
+ }
+
+ protected open fun refreshDatePicker() {
+ getElementJQueryD()?.`val`(null)
+ getElementJQueryD()?.datetimepicker("remove")
+ initDateTimePicker()
+ refreshState()
+ }
+
+ protected open fun changeValue() {
+ val v = getElementJQuery()?.`val`() as String?
+ if (v != null && v.isNotEmpty()) {
+ this.value = v.toKDateF(format)
+ } else {
+ this.value = null
+ }
+ }
+
+ /**
+ * Open date/time chooser popup.
+ */
+ open fun showPopup() {
+ getElementJQueryD()?.datetimepicker("show")
+ }
+
+ /**
+ * Hides date/time chooser popup.
+ */
+ open fun hidePopup() {
+ getElementJQueryD()?.datetimepicker("hide")
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ override fun afterInsert(node: VNode) {
+ if (!this.disabled) {
+ this.initDateTimePicker()
+ this.getElementJQuery()?.on("changeDate", { e, _ ->
+ this.dispatchEvent("change", obj { detail = e })
+ })
+ this.getElementJQuery()?.on("show", { e, _ ->
+ this.dispatchEvent("showBsDateTime", obj { detail = e })
+ })
+ this.getElementJQuery()?.on("hide", { e, _ ->
+ this.dispatchEvent("hideBsDateTime", obj { detail = e })
+ })
+ refreshState()
+ }
+ }
+
+ override fun afterDestroy() {
+ getElementJQueryD()?.datetimepicker("remove")
+ }
+
+ private fun initDateTimePicker() {
+ val datePickerFormat = format.toDatePickerFormat()
+ val minView = if (format.contains("HH") || format.contains("mm")) 0 else 2
+ val maxView = if (format.contains("YY") || format.contains("M") || format.contains("D")) MAX_VIEW else 1
+ val startView = if (maxView < 2) maxView else 2
+ val language = I18n.language
+ getElementJQueryD()?.datetimepicker(obj {
+ this.format = datePickerFormat
+ this.startView = startView
+ this.minView = minView
+ this.maxView = maxView
+ this.minuteStep = minuteStep
+ this.todayHighlight = todayHighlight
+ this.clearBtn = clearBtn
+ this.todayBtn = todayBtn
+ this.weekStart = weekStart
+ this.showMeridian = showMeridian
+ this.daysOfWeekDisabled = daysOfWeekDisabled
+ this.autoclose = true
+ this.language = language
+ })
+ }
+
+ /**
+ * Get value of date/time input control as String
+ * @return value as a String
+ */
+ fun getValueAsString(): String? {
+ return value?.toStringF(format)
+ }
+
+ /**
+ * Makes the input element focused.
+ */
+ open fun focus() {
+ getElementJQuery()?.focus()
+ }
+
+ /**
+ * Makes the input element blur.
+ */
+ open fun blur() {
+ getElementJQuery()?.blur()
+ }
+
+ companion object {
+ private fun String.toDatePickerFormat(): String {
+ return this.replace("YY", "yy").replace("m", "i").replace("MMMM", "{----}").replace("MMM", "{---}")
+ .replace("M", "m").replace("{----}", "MM").replace("{---}", "M").replace("H", "{-}")
+ .replace("h", "H").replace("{-}", "h").replace("D", "d").replace("a", "p").replace("A", "P")
+ }
+
+ /**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+ fun Container.dateTimeInput(
+ value: KDate? = null, format: String = "YYYY-MM-DD HH:mm", classes: Set<String> = setOf(),
+ init: (DateTimeInput.() -> Unit)? = null
+ ): DateTimeInput {
+ val dateTimeInput = DateTimeInput(value, format, classes).apply { init?.invoke(this) }
+ this.add(dateTimeInput)
+ return dateTimeInput
+ }
+ }
+}
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ar.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ar.js
new file mode 100644
index 00000000..a43b4739
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ar.js
@@ -0,0 +1,18 @@
+/**
+* Arabic translation for bootstrap-datetimepicker
+* Ala' Mohammad <amohammad@birzeit.ecu>
+*/
+;(function($){
+ $.fn.datetimepicker.dates['ar'] = {
+ days: ["الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس", "الجمعة", "السبت", "الأحد"],
+ daysShort: ["أحد", "اثنين", "ثلاثاء", "أربعاء", "خميس", "جمعة", "سبت", "أحد"],
+ daysMin: ["أح", "إث", "ث", "أر", "خ", "ج", "س", "أح"],
+ months: ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
+ monthsShort: ["يناير", "فبراير", "مارس", "أبريل", "مايو", "يونيو", "يوليو", "أغسطس", "سبتمبر", "أكتوبر", "نوفمبر", "ديسمبر"],
+ today: "هذا اليوم",
+ clear: "x",
+ suffix: [],
+ meridiem: [],
+ rtl: true
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.az.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.az.js
new file mode 100644
index 00000000..c840d6f0
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.az.js
@@ -0,0 +1,17 @@
+/**
+ * Azerbaijani translation for bootstrap-datetimepicker
+ * Konstantin Kaluzhnikov <k.kaluzhnikov@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['az'] = {
+ days: ["Bazar", "Bazar ertəsi", "Çərşənbə axşamı", "Çərşənbə", "Cümə axşamı", "Cümə", "Şənbə", "Bazar"],
+ daysShort: ["B", "Be", "Ça", "Ç", "Ca", "C", "Ş", "B"],
+ daysMin: ["B", "Be", "Ça", "Ç", "Ca", "C", "Ş", "B"],
+ months: ["Yanvar", "Fevral", "Mart", "Aprel", "May", "İyun", "İyul", "Avqust", "Sentyabr", "Oktyabr", "Noyabr", "Dekabr"],
+ monthsShort: ["Yan", "Fev", "Mar", "Apr", "May", "İyun", "İyul", "Avq", "Sen", "Okt", "Noy", "Dek"],
+ today: "Bugün",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.bg.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.bg.js
new file mode 100644
index 00000000..3bc7e273
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.bg.js
@@ -0,0 +1,17 @@
+/**
+ * Bulgarian translation for bootstrap-datetimepicker
+ * Apostol Apostolov <apostol.s.apostolov@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['bg'] = {
+ days: ["Неделя", "Понеделник", "Вторник", "Сряда", "Четвъртък", "Петък", "Събота", "Неделя"],
+ daysShort: ["Нед", "Пон", "Вто", "Сря", "Чет", "Пет", "Съб", "Нед"],
+ daysMin: ["Н", "П", "В", "С", "Ч", "П", "С", "Н"],
+ months: ["Януари", "Февруари", "Март", "Април", "Май", "Юни", "Юли", "Август", "Септември", "Октомври", "Ноември", "Декември"],
+ monthsShort: ["Ян", "Фев", "Мар", "Апр", "Май", "Юни", "Юли", "Авг", "Сеп", "Окт", "Ное", "Дек"],
+ today: "днес",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.bn.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.bn.js
new file mode 100644
index 00000000..b46bd54f
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.bn.js
@@ -0,0 +1,17 @@
+/**
+ * Bangla(Bangladesh) translation for bootstrap-datetimepicker
+ * Mahbub Rabbani <mahbub [dot] rucse [at] gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['bn'] = {
+ days: ["রবিবার", "সোমবার", "মঙ্গলবার", "বুধবার", "বৃহষ্পতিবার", "শুক্রবার", "শনিবার", "রবিবার"],
+ daysShort: ["রবি", "সোম", "মঙ্গল", "বুধ", " বৃহঃ", "শুক্র", "শনি", "রবি"],
+ daysMin: ["রবি", "সোম", "মঙ্গ", "বুধ", "বৃহ", "শুক্র", "শনি", "রবি"],
+ months: ['জানুয়ারী', 'ফেব্রুয়ারী', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই', 'অগাস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর' ],
+ monthsShort: ['জানু', 'ফেব্রু', 'মার্চ', 'এপ্রি', 'মে', 'জুন', 'জুলা', 'অগা', 'সেপ্টে', 'অক্টো', 'নভে', 'ডিসে' ],
+ today: "আজ",
+ clear: "x",
+ suffix: [],
+ meridiem: ['পূর্বাহ্ণ', 'অপরাহ্ন']
+ };
+}(jQuery)); \ No newline at end of file
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ca.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ca.js
new file mode 100644
index 00000000..d3137a2d
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ca.js
@@ -0,0 +1,17 @@
+/**
+ * Catalan translation for bootstrap-datetimepicker
+ * J. Garcia <jogaco.en@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['ca'] = {
+ days: ["Diumenge", "Dilluns", "Dimarts", "Dimecres", "Dijous", "Divendres", "Dissabte", "Diumenge"],
+ daysShort: ["Diu", "Dil", "Dmt", "Dmc", "Dij", "Div", "Dis", "Diu"],
+ daysMin: ["dg", "dl", "dt", "dc", "dj", "dv", "ds", "dg"],
+ months: ["Gener", "Febrer", "Març", "Abril", "Maig", "Juny", "Juliol", "Agost", "Setembre", "Octubre", "Novembre", "Desembre"],
+ monthsShort: ["Gen", "Feb", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Oct", "Nov", "Des"],
+ today: "Avui",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.cs.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.cs.js
new file mode 100644
index 00000000..318cd5cf
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.cs.js
@@ -0,0 +1,20 @@
+/**
+ * Czech translation for bootstrap-datetimepicker
+ * Matěj Koubík <matej@koubik.name>
+ * Fixes by Michal Remiš <michal.remis@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['cs'] = {
+ days: ["Neděle", "Pondělí", "Úterý", "Středa", "Čtvrtek", "Pátek", "Sobota", "Neděle"],
+ daysShort: ["Ned", "Pon", "Úte", "Stř", "Čtv", "Pát", "Sob", "Ned"],
+ daysMin: ["Ne", "Po", "Út", "St", "Čt", "Pá", "So", "Ne"],
+ months: ["Leden", "Únor", "Březen", "Duben", "Květen", "Červen", "Červenec", "Srpen", "Září", "Říjen", "Listopad", "Prosinec"],
+ monthsShort: ["Led", "Úno", "Bře", "Dub", "Kvě", "Čer", "Čnc", "Srp", "Zář", "Říj", "Lis", "Pro"],
+ today: "Dnes",
+ suffix: [],
+ clear: "x",
+ meridiem: [],
+ weekStart: 1,
+ format: "dd.mm.yyyy"
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.da.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.da.js
new file mode 100644
index 00000000..30d9a34a
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.da.js
@@ -0,0 +1,17 @@
+/**
+ * Danish translation for bootstrap-datetimepicker
+ * Christian Pedersen <http://github.com/chripede>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['da'] = {
+ days: ["Søndag", "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag", "Søndag"],
+ daysShort: ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn"],
+ daysMin: ["Sø", "Ma", "Ti", "On", "To", "Fr", "Lø", "Sø"],
+ months: ["Januar", "Februar", "Marts", "April", "Maj", "Juni", "Juli", "August", "September", "Oktober", "November", "December"],
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"],
+ today: "I Dag",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery)); \ No newline at end of file
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.de.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.de.js
new file mode 100644
index 00000000..52a19060
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.de.js
@@ -0,0 +1,19 @@
+/**
+ * German translation for bootstrap-datetimepicker
+ * Sam Zurcher <sam@orelias.ch>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['de'] = {
+ days: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag"],
+ daysShort: ["Son", "Mon", "Die", "Mit", "Don", "Fre", "Sam", "Son"],
+ daysMin: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"],
+ months: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
+ monthsShort: ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"],
+ today: "Heute",
+ clear: "x",
+ suffix: [],
+ meridiem: [],
+ weekStart: 1,
+ format: "dd.mm.yyyy"
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ee.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ee.js
new file mode 100644
index 00000000..882378f3
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ee.js
@@ -0,0 +1,19 @@
+/**
+ * Estonian translation for bootstrap-datetimepicker
+ * Rene Korss <http://rene.korss.ee>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['ee'] = {
+ days: ["Pühapäev", "Esmaspäev", "Teisipäev", "Kolmapäev", "Neljapäev", "Reede", "Laupäev", "Pühapäev"],
+ daysShort: ["P", "E", "T", "K", "N", "R", "L", "P"],
+ daysMin: ["P", "E", "T", "K", "N", "R", "L", "P"],
+ months: ["Jaanuar", "Veebruar", "Märts", "Aprill", "Mai", "Juuni", "Juuli", "August", "September", "Oktoober", "November", "Detsember"],
+ monthsShort: ["Jaan", "Veebr", "Märts", "Apr", "Mai", "Juuni", "Juuli", "Aug", "Sept", "Okt", "Nov", "Dets"],
+ today: "Täna",
+ clear: "x",
+ suffix: [],
+ meridiem: [],
+ weekStart: 1,
+ format: "dd.mm.yyyy hh:ii"
+ };
+}(jQuery)); \ No newline at end of file
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.el.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.el.js
new file mode 100644
index 00000000..cbbdc9a7
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.el.js
@@ -0,0 +1,16 @@
+/**
+* Greek translation for bootstrap-datetimepicker
+*/
+;(function($){
+ $.fn.datetimepicker.dates['el'] = {
+ days: ["Κυριακή", "Δευτέρα", "Τρίτη", "Τετάρτη", "Πέμπτη", "Παρασκευή", "Σάββατο", "Κυριακή"],
+ daysShort: ["Κυρ", "Δευ", "Τρι", "Τετ", "Πεμ", "Παρ", "Σαβ", "Κυρ"],
+ daysMin: ["Κυ", "Δε", "Τρ", "Τε", "Πε", "Πα", "Σα", "Κυ"],
+ months: ["Ιανουάριος", "Φεβρουάριος", "Μάρτιος", "Απρίλιος", "Μάιος", "Ιούνιος", "Ιούλιος", "Αύγουστος", "Σεπτέμβριος", "Οκτώβριος", "Νοέμβριος", "Δεκέμβριος"],
+ monthsShort: ["Ιαν", "Φεβ", "Μαρ", "Απρ", "Μάι", "Ιουν", "Ιουλ", "Αυγ", "Σεπ", "Οκτ", "Νοε", "Δεκ"],
+ today: "Σήμερα",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery)); \ No newline at end of file
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.es.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.es.js
new file mode 100644
index 00000000..bbf3c207
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.es.js
@@ -0,0 +1,17 @@
+/**
+ * Spanish translation for bootstrap-datetimepicker
+ * Bruno Bonamin <bruno.bonamin@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['es'] = {
+ days: ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"],
+ daysShort: ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb", "Dom"],
+ daysMin: ["Do", "Lu", "Ma", "Mi", "Ju", "Vi", "Sa", "Do"],
+ months: ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
+ monthsShort: ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"],
+ today: "Hoy",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.fi.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.fi.js
new file mode 100644
index 00000000..95eb4a8d
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.fi.js
@@ -0,0 +1,17 @@
+/**
+ * Finnish translation for bootstrap-datetimepicker
+ * Jaakko Salonen <https://github.com/jsalonen>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['fi'] = {
+ days: ["sunnuntai", "maanantai", "tiistai", "keskiviikko", "torstai", "perjantai", "lauantai", "sunnuntai"],
+ daysShort: ["sun", "maa", "tii", "kes", "tor", "per", "lau", "sun"],
+ daysMin: ["su", "ma", "ti", "ke", "to", "pe", "la", "su"],
+ months: ["tammikuu", "helmikuu", "maaliskuu", "huhtikuu", "toukokuu", "kesäkuu", "heinäkuu", "elokuu", "syyskuu", "lokakuu", "marraskuu", "joulukuu"],
+ monthsShort: ["tam", "hel", "maa", "huh", "tou", "kes", "hei", "elo", "syy", "lok", "mar", "jou"],
+ today: "tänään",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.fr.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.fr.js
new file mode 100644
index 00000000..f9194cbd
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.fr.js
@@ -0,0 +1,19 @@
+/**
+ * French translation for bootstrap-datetimepicker
+ * Nico Mollet <nico.mollet@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['fr'] = {
+ days: ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche"],
+ daysShort: ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"],
+ daysMin: ["D", "L", "Ma", "Me", "J", "V", "S", "D"],
+ months: ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
+ monthsShort: ["Jan", "Fev", "Mar", "Avr", "Mai", "Jui", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec"],
+ today: "Aujourd'hui",
+ suffix: [],
+ clear: "x",
+ meridiem: ["am", "pm"],
+ weekStart: 1,
+ format: "dd/mm/yyyy hh:ii"
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.he.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.he.js
new file mode 100644
index 00000000..1060a4a7
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.he.js
@@ -0,0 +1,18 @@
+/**
+ * Hebrew translation for bootstrap-datetimepicker
+ * Sagie Maoz <sagie@maoz.info>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['he'] = {
+ days: ["ראשון", "שני", "שלישי", "רביעי", "חמישי", "שישי", "שבת", "ראשון"],
+ daysShort: ["א", "ב", "ג", "ד", "ה", "ו", "ש", "א"],
+ daysMin: ["א", "ב", "ג", "ד", "ה", "ו", "ש", "א"],
+ months: ["ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר"],
+ monthsShort: ["ינו", "פבר", "מרץ", "אפר", "מאי", "יונ", "יול", "אוג", "ספט", "אוק", "נוב", "דצמ"],
+ today: "היום",
+ clear: "x",
+ suffix: [],
+ meridiem: [],
+ rtl: true
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hr.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hr.js
new file mode 100644
index 00000000..f85540fa
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hr.js
@@ -0,0 +1,16 @@
+/**
+ * Croatian localisation
+ */
+;(function($){
+ $.fn.datetimepicker.dates['hr'] = {
+ days: ["Nedjelja", "Ponedjelja", "Utorak", "Srijeda", "Četrtak", "Petak", "Subota", "Nedjelja"],
+ daysShort: ["Ned", "Pon", "Uto", "Srr", "Čet", "Pet", "Sub", "Ned"],
+ daysMin: ["Ne", "Po", "Ut", "Sr", "Če", "Pe", "Su", "Ne"],
+ months: ["Siječanj", "Veljača", "Ožujak", "Travanj", "Svibanj", "Lipanj", "Srpanj", "Kolovoz", "Rujan", "Listopad", "Studeni", "Prosinac"],
+ monthsShort: ["Sije", "Velj", "Ožu", "Tra", "Svi", "Lip", "Jul", "Kol", "Ruj", "Lis", "Stu", "Pro"],
+ today: "Danas",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hu.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hu.js
new file mode 100644
index 00000000..5de9fe9e
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hu.js
@@ -0,0 +1,18 @@
+/**
+ * Hungarian translation for bootstrap-datetimepicker
+ * darevish <http://github.com/darevish>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['hu'] = {
+ days: ["Vasárnap", "Hétfő", "Kedd", "Szerda", "Csütörtök", "Péntek", "Szombat", "Vasárnap"],
+ daysShort: ["Vas", "Hét", "Ked", "Sze", "Csü", "Pén", "Szo", "Vas"],
+ daysMin: ["V", "H", "K", "Sze", "Cs", "P", "Szo", "V"],
+ months: ["Január", "Február", "Március", "Április", "Május", "Június", "Július", "Augusztus", "Szeptember", "Október", "November", "December"],
+ monthsShort: ["Jan", "Feb", "Már", "Ápr", "Máj", "Jún", "Júl", "Aug", "Sze", "Okt", "Nov", "Dec"],
+ today: "Ma",
+ clear: "x",
+ suffix: [],
+ meridiem: [],
+ weekStart: 1
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hy.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hy.js
new file mode 100644
index 00000000..cedee4db
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.hy.js
@@ -0,0 +1,17 @@
+/**
+ * Armenian translation for bootstrap-datepicker
+ * Hayk Chamyan <hamshen@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['hy'] = {
+ days: ["Կիրակի", "Երկուշաբթի", "Երեքշաբթի", "Չորեքշաբթի", "Հինգշաբթի", "Ուրբաթ", "Շաբաթ", "Կիրակի"],
+ daysShort: ["Կիր", "Երկ", "Երք", "Չոր", "Հնգ", "Ուր", "Շաբ", "Կիր"],
+ daysMin: ["Կի", "Եկ", "Եք", "Չո", "Հի", "Ու", "Շա", "Կի"],
+ months: ["Հունվար", "Փետրվար", "Մարտ", "Ապրիլ", "Մայիս", "Հունիս", "Հուլիս", "Օգոստոս", "Սեպտեմբեր", "Հոկտեմբեր", "Նոյեմբեր", "Դեկտեմբեր"],
+ monthsShort: ["Հնվ", "Փետ", "Մար", "Ապր", "Մայ", "Հուն", "Հուլ", "Օգս", "Սեպ", "Հոկ", "Նոյ", "Դեկ"],
+ today: "Այսօր",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.id.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.id.js
new file mode 100644
index 00000000..feef4a3b
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.id.js
@@ -0,0 +1,20 @@
+/**
+ * Bahasa translation for bootstrap-datetimepicker
+ * Azwar Akbar <azwar.akbar@gmail.com>
+ * Addtional by Yulian Sutopo <yuliansutopo@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['id'] = {
+ days: ["Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"],
+ daysShort: ["Mng", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab", "Mng"],
+ daysMin: ["Mg", "Sn", "Sl", "Ra", "Ka", "Ju", "Sa", "Mg"],
+ months: ["Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"],
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Ags", "Sep", "Okt", "Nov", "Des"],
+ today: "Hari Ini",
+ clear: "x",
+ suffix: [],
+ meridiem: [],
+ weekStart: 1,
+ format: "dd/mm/yyyy hh:ii:ss"
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.is.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.is.js
new file mode 100644
index 00000000..9944c6d7
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.is.js
@@ -0,0 +1,17 @@
+/**
+ * Icelandic translation for bootstrap-datetimepicker
+ * Hinrik Örn Sigurðsson <hinrik.sig@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['is'] = {
+ days: ["Sunnudagur", "Mánudagur", "Þriðjudagur", "Miðvikudagur", "Fimmtudagur", "Föstudagur", "Laugardagur", "Sunnudagur"],
+ daysShort: ["Sun", "Mán", "Þri", "Mið", "Fim", "Fös", "Lau", "Sun"],
+ daysMin: ["Su", "Má", "Þr", "Mi", "Fi", "Fö", "La", "Su"],
+ months: ["Janúar", "Febrúar", "Mars", "Apríl", "Maí", "Júní", "Júlí", "Ágúst", "September", "Október", "Nóvember", "Desember"],
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maí", "Jún", "Júl", "Ágú", "Sep", "Okt", "Nóv", "Des"],
+ today: "Í Dag",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.it.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.it.js
new file mode 100644
index 00000000..c00a0499
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.it.js
@@ -0,0 +1,19 @@
+/**
+ * Italian translation for bootstrap-datetimepicker
+ * Enrico Rubboli <rubboli@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['it'] = {
+ days: ["Domenica", "Lunedi", "Martedi", "Mercoledi", "Giovedi", "Venerdi", "Sabato", "Domenica"],
+ daysShort: ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab", "Dom"],
+ daysMin: ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"],
+ months: ["Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"],
+ monthsShort: ["Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"],
+ today: "Oggi",
+ clear: "x",
+ suffix: [],
+ meridiem: [],
+ weekStart: 1,
+ format: "dd/mm/yyyy hh:ii:ss"
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ja.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ja.js
new file mode 100644
index 00000000..3a2d3b9b
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ja.js
@@ -0,0 +1,17 @@
+/**
+ * Japanese translation for bootstrap-datetimepicker
+ * Norio Suzuki <https://github.com/suzuki/>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['ja'] = {
+ days: ["日曜", "月曜", "火曜", "水曜", "木曜", "金曜", "土曜", "日曜"],
+ daysShort: ["日", "月", "火", "水", "木", "金", "土", "日"],
+ daysMin: ["日", "月", "火", "水", "木", "金", "土", "日"],
+ months: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
+ monthsShort: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"],
+ today: "今日",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ka.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ka.js
new file mode 100644
index 00000000..d4595eff
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ka.js
@@ -0,0 +1,17 @@
+/**
+ * Georgian translation for bootstrap-datetimepicker
+ * Zura Jijavadze <mailzura@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['ka'] = {
+ days: ["კვირა", "ორშაბათი", "სამშაბათი", "ოთხშაბათი", "ხუთშაბათი", "პარასკევი", "შაბათი", "კვირა"],
+ daysShort: ["კვი", "ორშ", "სამ", "ოთხ", "ხუთ", "პარ", "შაბ", "კვი"],
+ daysMin: ["კვ", "ორ", "სა", "ოთ", "ხუ", "პა", "შა", "კვ"],
+ months: ["იანვარი", "თებერვალი", "მარტი", "აპრილი", "მაისი", "ივნისი", "ივლისი", "აგვისტო", "სექტემბერი", "ოქტომბერი", "ნოემბერი", "დეკემბერი"],
+ monthsShort: ["იან", "თებ", "მარ", "აპრ", "მაი", "ივნ", "ივლ", "აგვ", "სექ", "ოქტ", "ნოე", "დეკ"],
+ today: "დღეს",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery)); \ No newline at end of file
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ko.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ko.js
new file mode 100644
index 00000000..adeb6cb6
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ko.js
@@ -0,0 +1,18 @@
+/**
+ * Korean translation for bootstrap-datetimepicker
+ * Gu Youn <http://github.com/guyoun>
+ * Baekjoon Choi <http://github.com/Baekjoon>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['ko'] = {
+ days: ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일", "일요일"],
+ daysShort: ["일", "월", "화", "수", "목", "금", "토", "일"],
+ daysMin: ["일", "월", "화", "수", "목", "금", "토", "일"],
+ months: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
+ monthsShort: ["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"],
+ suffix: [],
+ meridiem: ["오전", "오후"],
+ today: "오늘",
+ clear: "x"
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.lt.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.lt.js
new file mode 100644
index 00000000..917243c4
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.lt.js
@@ -0,0 +1,19 @@
+/**
+ * Lithuanian translation for bootstrap-datetimepicker
+ * Šarūnas Gliebus <ssharunas@yahoo.co.uk>
+ */
+
+;(function($){
+ $.fn.datetimepicker.dates['lt'] = {
+ days: ["Sekmadienis", "Pirmadienis", "Antradienis", "Trečiadienis", "Ketvirtadienis", "Penktadienis", "Šeštadienis", "Sekmadienis"],
+ daysShort: ["S", "Pr", "A", "T", "K", "Pn", "Š", "S"],
+ daysMin: ["Sk", "Pr", "An", "Tr", "Ke", "Pn", "Št", "Sk"],
+ months: ["Sausis", "Vasaris", "Kovas", "Balandis", "Gegužė", "Birželis", "Liepa", "Rugpjūtis", "Rugsėjis", "Spalis", "Lapkritis", "Gruodis"],
+ monthsShort: ["Sau", "Vas", "Kov", "Bal", "Geg", "Bir", "Lie", "Rugp", "Rugs", "Spa", "Lap", "Gru"],
+ today: "Šiandien",
+ clear: "x",
+ suffix: [],
+ meridiem: [],
+ weekStart: 1
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.lv.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.lv.js
new file mode 100644
index 00000000..f9b3c774
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.lv.js
@@ -0,0 +1,19 @@
+/**
+ * Latvian translation for bootstrap-datetimepicker
+ * Artis Avotins <artis@apit.lv>
+ */
+
+;(function($){
+ $.fn.datetimepicker.dates['lv'] = {
+ days: ["Svētdiena", "Pirmdiena", "Otrdiena", "Trešdiena", "Ceturtdiena", "Piektdiena", "Sestdiena", "Svētdiena"],
+ daysShort: ["Sv", "P", "O", "T", "C", "Pk", "S", "Sv"],
+ daysMin: ["Sv", "Pr", "Ot", "Tr", "Ce", "Pk", "St", "Sv"],
+ months: ["Janvāris", "Februāris", "Marts", "Aprīlis", "Maijs", "Jūnijs", "Jūlijs", "Augusts", "Septembris", "Oktobris", "Novembris", "Decembris"],
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mai", "Jūn", "Jūl", "Aug", "Sep", "Okt", "Nov", "Dec."],
+ today: "Šodien",
+ clear: "x",
+ suffix: [],
+ meridiem: [],
+ weekStart: 1
+ };
+}(jQuery)); \ No newline at end of file
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ms.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ms.js
new file mode 100644
index 00000000..26a2cc0e
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ms.js
@@ -0,0 +1,17 @@
+/**
+ * Malay translation for bootstrap-datetimepicker
+ * Ateman Faiz <noorulfaiz@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['ms'] = {
+ days: ["Ahad", "Isnin", "Selasa", "Rabu", "Khamis", "Jumaat", "Sabtu", "Ahad"],
+ daysShort: ["Aha", "Isn", "Sel", "Rab", "Kha", "Jum", "Sab", "Aha"],
+ daysMin: ["Ah", "Is", "Se", "Ra", "Kh", "Ju", "Sa", "Ah"],
+ months: ["Januari", "Februari", "Mac", "April", "Mei", "Jun", "Julai", "Ogos", "September", "Oktober", "November", "Disember"],
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Ogo", "Sep", "Okt", "Nov", "Dis"],
+ today: "Hari Ini",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.nb.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.nb.js
new file mode 100644
index 00000000..a2fd01a6
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.nb.js
@@ -0,0 +1,17 @@
+/**
+ * Norwegian (bokmål) translation for bootstrap-datetimepicker
+ * Fredrik Sundmyhr <http://github.com/fsundmyhr>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['nb'] = {
+ days: ["Søndag", "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag", "Søndag"],
+ daysShort: ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn"],
+ daysMin: ["Sø", "Ma", "Ti", "On", "To", "Fr", "Lø", "Sø"],
+ months: ["Januar", "Februar", "Mars", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Desember"],
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Des"],
+ today: "I Dag",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery)); \ No newline at end of file
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.nl.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.nl.js
new file mode 100644
index 00000000..a28fa031
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.nl.js
@@ -0,0 +1,17 @@
+/**
+ * Dutch translation for bootstrap-datetimepicker
+ * Reinier Goltstein <mrgoltstein@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['nl'] = {
+ days: ["Zondag", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag", "Zaterdag", "Zondag"],
+ daysShort: ["Zo", "Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"],
+ daysMin: ["Zo", "Ma", "Di", "Wo", "Do", "Vr", "Za", "Zo"],
+ months: ["Januari", "Februari", "Maart", "April", "Mei", "Juni", "Juli", "Augustus", "September", "Oktober", "November", "December"],
+ monthsShort: ["Jan", "Feb", "Mrt", "Apr", "Mei", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"],
+ today: "Vandaag",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.no.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.no.js
new file mode 100644
index 00000000..36e33a0b
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.no.js
@@ -0,0 +1,17 @@
+/**
+ * Norwegian translation for bootstrap-datetimepicker
+ * Rune Warhuus <rune@dinkdonkd.no>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['no'] = {
+ days: ["Søndag", "Mandag", "Tirsdag", "Onsdag", "Torsdag", "Fredag", "Lørdag", "Søndag"],
+ daysShort: ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør", "Søn"],
+ daysMin: ["Sø", "Ma", "Ti", "On", "To", "Fr", "Lø", "Sø"],
+ months: ["Januar", "Februar", "Mars", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Desember"],
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Des"],
+ today: "I Dag",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pl.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pl.js
new file mode 100644
index 00000000..12fc7422
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pl.js
@@ -0,0 +1,18 @@
+/**
+ * Polish translation for bootstrap-datetimepicker
+ * Robert <rtpm@gazeta.pl>
+ */
+;(function($){
+$.fn.datetimepicker.dates['pl'] = {
+ days: ["Niedziela", "Poniedziałek", "Wtorek", "Środa", "Czwartek", "Piątek", "Sobota", "Niedziela"],
+ daysShort: ["Nie", "Pn", "Wt", "Śr", "Czw", "Pt", "So", "Nie"],
+ daysMin: ["N", "Pn", "Wt", "Śr", "Cz", "Pt", "So", "N"],
+ months: ["Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"],
+ monthsShort: ["Sty", "Lu", "Mar", "Kw", "Maj", "Cze", "Lip", "Sie", "Wrz", "Pa", "Lis", "Gru"],
+ today: "Dzisiaj",
+ clear: "x",
+ suffix: [],
+ meridiem: [],
+ weekStart: 1
+};
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pt-BR.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pt-BR.js
new file mode 100644
index 00000000..1d307416
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pt-BR.js
@@ -0,0 +1,18 @@
+/**
+ * Brazilian translation for bootstrap-datetimepicker
+ * Cauan Cabral <cauan@radig.com.br>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['pt-BR'] = {
+ format: 'dd/mm/yyyy',
+ days: ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado", "Domingo"],
+ daysShort: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"],
+ daysMin: ["Do", "Se", "Te", "Qu", "Qu", "Se", "Sa", "Do"],
+ months: ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"],
+ monthsShort: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"],
+ today: "Hoje",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pt.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pt.js
new file mode 100644
index 00000000..166034ef
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.pt.js
@@ -0,0 +1,18 @@
+/**
+ * Portuguese translation for bootstrap-datetimepicker
+ * Original code: Cauan Cabral <cauan@radig.com.br>
+ * Tiago Melo <tiago.blackcode@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['pt'] = {
+ days: ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado", "Domingo"],
+ daysShort: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb", "Dom"],
+ daysMin: ["Do", "Se", "Te", "Qu", "Qu", "Se", "Sa", "Do"],
+ months: ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"],
+ monthsShort: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"],
+ suffix: [],
+ meridiem: [],
+ today: "Hoje",
+ clear: "x"
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ro.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ro.js
new file mode 100644
index 00000000..a7569c6f
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ro.js
@@ -0,0 +1,18 @@
+/**
+ * Romanian translation for bootstrap-datetimepicker
+ * Cristian Vasile <cristi.mie@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['ro'] = {
+ days: ["Duminică", "Luni", "Marţi", "Miercuri", "Joi", "Vineri", "Sâmbătă", "Duminică"],
+ daysShort: ["Dum", "Lun", "Mar", "Mie", "Joi", "Vin", "Sâm", "Dum"],
+ daysMin: ["Du", "Lu", "Ma", "Mi", "Jo", "Vi", "Sâ", "Du"],
+ months: ["Ianuarie", "Februarie", "Martie", "Aprilie", "Mai", "Iunie", "Iulie", "August", "Septembrie", "Octombrie", "Noiembrie", "Decembrie"],
+ monthsShort: ["Ian", "Feb", "Mar", "Apr", "Mai", "Iun", "Iul", "Aug", "Sep", "Oct", "Nov", "Dec"],
+ today: "Astăzi",
+ clear: "x",
+ suffix: [],
+ meridiem: [],
+ weekStart: 1
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.rs-latin.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.rs-latin.js
new file mode 100644
index 00000000..57c819f9
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.rs-latin.js
@@ -0,0 +1,17 @@
+/**
+ * Serbian latin translation for bootstrap-datetimepicker
+ * Bojan Milosavlević <milboj@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['rs'] = {
+ days: ["Nedelja","Ponedeljak", "Utorak", "Sreda", "Četvrtak", "Petak", "Subota", "Nedelja"],
+ daysShort: ["Ned", "Pon", "Uto", "Sre", "Čet", "Pet", "Sub", "Ned"],
+ daysMin: ["N", "Po", "U", "Sr", "Č", "Pe", "Su", "N"],
+ months: ["Januar", "Februar", "Mart", "April", "Maj", "Jun", "Jul", "Avgust", "Septembar", "Oktobar", "Novembar", "Decembar"],
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Avg", "Sep", "Okt", "Nov", "Dec"],
+ today: "Danas",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.rs.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.rs.js
new file mode 100644
index 00000000..3e9db3e6
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.rs.js
@@ -0,0 +1,17 @@
+/**
+ * Serbian cyrillic translation for bootstrap-datetimepicker
+ * Bojan Milosavlević <milboj@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['rs'] = {
+ days: ["Недеља","Понедељак", "Уторак", "Среда", "Четвртак", "Петак", "Субота", "Недеља"],
+ daysShort: ["Нед", "Пон", "Уто", "Сре", "Чет", "Пет", "Суб", "Нед"],
+ daysMin: ["Н", "По", "У", "Ср", "Ч", "Пе", "Су", "Н"],
+ months: ["Јануар", "Фебруар", "Март", "Април", "Мај", "Јун", "Јул", "Август", "Септембар", "Октобар", "Новембар", "Децембар"],
+ monthsShort: ["Јан", "Феб", "Мар", "Апр", "Мај", "Јун", "Јул", "Авг", "Сеп", "Окт", "Нов", "Дец"],
+ today: "Данас",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ru.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ru.js
new file mode 100644
index 00000000..20caf251
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ru.js
@@ -0,0 +1,17 @@
+/**
+ * Russian translation for bootstrap-datetimepicker
+ * Victor Taranenko <darwin@snowdale.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['ru'] = {
+ days: ["Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"],
+ daysShort: ["Вск", "Пнд", "Втр", "Срд", "Чтв", "Птн", "Суб", "Вск"],
+ daysMin: ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"],
+ months: ["Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь"],
+ monthsShort: ["Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"],
+ today: "Сегодня",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery)); \ No newline at end of file
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sk.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sk.js
new file mode 100644
index 00000000..acf1495a
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sk.js
@@ -0,0 +1,20 @@
+/**
+ * Slovak translation for bootstrap-datetimepicker
+ * Marek Lichtner <marek@licht.sk>
+ * Fixes by Michal Remiš <michal.remis@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates["sk"] = {
+ days: ["Nedeľa", "Pondelok", "Utorok", "Streda", "Štvrtok", "Piatok", "Sobota", "Nedeľa"],
+ daysShort: ["Ned", "Pon", "Uto", "Str", "Štv", "Pia", "Sob", "Ned"],
+ daysMin: ["Ne", "Po", "Ut", "St", "Št", "Pi", "So", "Ne"],
+ months: ["Január", "Február", "Marec", "Apríl", "Máj", "Jún", "Júl", "August", "September", "Október", "November", "December"],
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "Máj", "Jún", "Júl", "Aug", "Sep", "Okt", "Nov", "Dec"],
+ today: "Dnes",
+ clear: "x",
+ suffix: [],
+ meridiem: [],
+ weekStart: 1,
+ format: "dd.mm.yyyy"
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sl.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sl.js
new file mode 100644
index 00000000..a7e58bdf
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sl.js
@@ -0,0 +1,17 @@
+/**
+ * Slovene translation for bootstrap-datetimepicker
+ * Gregor Rudolf <gregor.rudolf@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['sl'] = {
+ days: ["Nedelja", "Ponedeljek", "Torek", "Sreda", "Četrtek", "Petek", "Sobota", "Nedelja"],
+ daysShort: ["Ned", "Pon", "Tor", "Sre", "Čet", "Pet", "Sob", "Ned"],
+ daysMin: ["Ne", "Po", "To", "Sr", "Če", "Pe", "So", "Ne"],
+ months: ["Januar", "Februar", "Marec", "April", "Maj", "Junij", "Julij", "Avgust", "September", "Oktober", "November", "December"],
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Avg", "Sep", "Okt", "Nov", "Dec"],
+ today: "Danes",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sv.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sv.js
new file mode 100644
index 00000000..050125ad
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sv.js
@@ -0,0 +1,17 @@
+/**
+ * Swedish translation for bootstrap-datetimepicker
+ * Patrik Ragnarsson <patrik@starkast.net>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['sv'] = {
+ days: ["Söndag", "Måndag", "Tisdag", "Onsdag", "Torsdag", "Fredag", "Lördag", "Söndag"],
+ daysShort: ["Sön", "Mån", "Tis", "Ons", "Tor", "Fre", "Lör", "Sön"],
+ daysMin: ["Sö", "Må", "Ti", "On", "To", "Fr", "Lö", "Sö"],
+ months: ["Januari", "Februari", "Mars", "April", "Maj", "Juni", "Juli", "Augusti", "September", "Oktober", "November", "December"],
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "Maj", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dec"],
+ today: "I Dag",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sw.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sw.js
new file mode 100644
index 00000000..8cdb4df2
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.sw.js
@@ -0,0 +1,18 @@
+/**
+ * Swahili translation for bootstrap-datetimepicker
+ * Edwin Mugendi <https://github.com/edwinmugendi>
+ * Source: http://scriptsource.org/cms/scripts/page.php?item_id=entry_detail&uid=xnfaqyzcku
+ */
+;(function($){
+ $.fn.datetimepicker.dates['sw'] = {
+ days: ["Jumapili", "Jumatatu", "Jumanne", "Jumatano", "Alhamisi", "Ijumaa", "Jumamosi", "Jumapili"],
+ daysShort: ["J2", "J3", "J4", "J5", "Alh", "Ij", "J1", "J2"],
+ daysMin: ["2", "3", "4", "5", "A", "I", "1", "2"],
+ months: ["Januari", "Februari", "Machi", "Aprili", "Mei", "Juni", "Julai", "Agosti", "Septemba", "Oktoba", "Novemba", "Desemba"],
+ monthsShort: ["Jan", "Feb", "Mac", "Apr", "Mei", "Jun", "Jul", "Ago", "Sep", "Okt", "Nov", "Des"],
+ today: "Leo",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.th.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.th.js
new file mode 100644
index 00000000..8adc129a
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.th.js
@@ -0,0 +1,17 @@
+/**
+ * Thai translation for bootstrap-datetimepicker
+ * Suchau Jiraprapot <seroz24@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['th'] = {
+ days: ["อาทิตย์", "จันทร์", "อังคาร", "พุธ", "พฤหัส", "ศุกร์", "เสาร์", "อาทิตย์"],
+ daysShort: ["อา", "จ", "อ", "พ", "พฤ", "ศ", "ส", "อา"],
+ daysMin: ["อา", "จ", "อ", "พ", "พฤ", "ศ", "ส", "อา"],
+ months: ["มกราคม", "กุมภาพันธ์", "มีนาคม", "เมษายน", "พฤษภาคม", "มิถุนายน", "กรกฎาคม", "สิงหาคม", "กันยายน", "ตุลาคม", "พฤศจิกายน", "ธันวาคม"],
+ monthsShort: ["ม.ค.", "ก.พ.", "มี.ค.", "เม.ย.", "พ.ค.", "มิ.ย.", "ก.ค.", "ส.ค.", "ก.ย.", "ต.ค.", "พ.ย.", "ธ.ค."],
+ today: "วันนี้",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.tr.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.tr.js
new file mode 100644
index 00000000..0130c94c
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.tr.js
@@ -0,0 +1,18 @@
+/**
+ * Turkish translation for bootstrap-datetimepicker
+ * Serkan Algur <kaisercrazy_2@hotmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['tr'] = {
+ days: ["Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi", "Pazar"],
+ daysShort: ["Pz", "Pzt", "Sal", "Çrş", "Prş", "Cu", "Cts", "Pz"],
+ daysMin: ["Pz", "Pzt", "Sa", "Çr", "Pr", "Cu", "Ct", "Pz"],
+ months: ["Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran", "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"],
+ monthsShort: ["Oca", "Şub", "Mar", "Nis", "May", "Haz", "Tem", "Ağu", "Eyl", "Eki", "Kas", "Ara"],
+ today: "Bugün",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery));
+
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ua.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ua.js
new file mode 100644
index 00000000..1687a1dc
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.ua.js
@@ -0,0 +1,16 @@
+/**
+ * Ukrainian translation for bootstrap-datepicker
+ * Igor Polynets
+ */
+;(function($){
+ $.fn.datetimepicker.dates['ua'] = {
+ days: ["Неділя", "Понеділок", "Вівторок", "Середа", "Четверг", "П'ятниця", "Субота", "Неділя"],
+ daysShort: ["Нед", "Пнд", "Втр", "Срд", "Чтв", "Птн", "Суб", "Нед"],
+ daysMin: ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Нд"],
+ months: ["Cічень", "Лютий", "Березень", "Квітень", "Травень", "Червень", "Липень", "Серпень", "Вересень", "Жовтень", "Листопад", "Грудень"],
+ monthsShort: ["Січ", "Лют", "Бер", "Квт", "Трв", "Чер", "Лип", "Сер", "Вер", "Жов", "Лис", "Грд"],
+ today: "Сьогодні",
+ clear: "x",
+ weekStart: 1
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.uk.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.uk.js
new file mode 100644
index 00000000..4b3a9e0a
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.uk.js
@@ -0,0 +1,17 @@
+/**
+ * Ukrainian translation for bootstrap-datetimepicker
+ * Andrey Vityuk <andrey [dot] vityuk [at] gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['uk'] = {
+ days: ["Неділя", "Понеділок", "Вівторок", "Середа", "Четвер", "П'ятниця", "Субота", "Неділя"],
+ daysShort: ["Нед", "Пнд", "Втр", "Срд", "Чтв", "Птн", "Суб", "Нед"],
+ daysMin: ["Нд", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Нд"],
+ months: ["Січень", "Лютий", "Березень", "Квітень", "Травень", "Червень", "Липень", "Серпень", "Вересень", "Жовтень", "Листопад", "Грудень"],
+ monthsShort: ["Січ", "Лют", "Бер", "Кві", "Тра", "Чер", "Лип", "Сер", "Вер", "Жов", "Лис", "Гру"],
+ today: "Сьогодні",
+ clear: "x",
+ suffix: [],
+ meridiem: []
+ };
+}(jQuery)); \ No newline at end of file
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.zh-TW.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.zh-TW.js
new file mode 100644
index 00000000..f84ba548
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.zh-TW.js
@@ -0,0 +1,17 @@
+/**
+ * Traditional Chinese translation for bootstrap-datetimepicker
+ * Rung-Sheng Jang <daniel@i-trend.co.cc>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['zh-TW'] = {
+ days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
+ daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"],
+ daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"],
+ months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
+ monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
+ today: "今天",
+ clear: "x",
+ suffix: [],
+ meridiem: ["上午", "下午"]
+ };
+}(jQuery));
diff --git a/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.zh.js b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.zh.js
new file mode 100644
index 00000000..66ac5a01
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/main/resources/js/locales/bootstrap-datetime-picker/bootstrap-datetimepicker.zh.js
@@ -0,0 +1,17 @@
+/**
+ * Simplified Chinese translation for bootstrap-datetimepicker
+ * Yuan Cheung <advanimal@gmail.com>
+ */
+;(function($){
+ $.fn.datetimepicker.dates['zh'] = {
+ days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
+ daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"],
+ daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"],
+ months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
+ monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
+ today: "今天",
+ clear: "x",
+ suffix: [],
+ meridiem: ["上午", "下午"]
+ };
+}(jQuery));
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
new file mode 100644
index 00000000..1da1fe1a
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package test.pl.treksoft.kvision
+
+import org.w3c.dom.Element
+import pl.treksoft.jquery.jQuery
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.panel.Root
+import kotlin.browser.document
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+interface TestSpec {
+ fun beforeTest()
+
+ fun afterTest()
+
+ fun run(code: () -> Unit) {
+ beforeTest()
+ code()
+ afterTest()
+ }
+}
+
+interface SimpleSpec : TestSpec {
+
+ override fun beforeTest() {
+ }
+
+ override fun afterTest() {
+ }
+
+}
+
+interface DomSpec : TestSpec {
+
+ override fun beforeTest() {
+ val fixture = "<div style=\"display: none\" id=\"pretest\">" +
+ "<div id=\"test\"></div></div>"
+ document.body?.insertAdjacentHTML("afterbegin", fixture)
+ }
+
+ override fun afterTest() {
+ val div = document.getElementById("pretest")
+ div?.let { jQuery(it).remove() }
+ jQuery(`object` = ".modal-backdrop").remove()
+ }
+
+ fun assertEqualsHtml(expected: String?, actual: String?, message: String?) {
+ if (expected != null && actual != null) {
+ val exp = jQuery(html = expected)
+ val act = jQuery(html = actual)
+ val result = exp[0]?.isEqualNode(act[0])
+ if (result == true) {
+ assertTrue(result == true, message)
+ } else {
+ assertEquals(expected, actual, message)
+ }
+ } else {
+ assertEquals(expected, actual, message)
+ }
+ }
+}
+
+interface WSpec : DomSpec {
+
+ fun runW(code: (widget: Widget, element: Element?) -> Unit) {
+ run {
+ val root = Root("test", true)
+ val widget = Widget()
+ widget.id = "test_id"
+ root.add(widget)
+ val element = document.getElementById("test_id")
+ code(widget, element)
+ }
+ }
+
+}
+
+external fun require(name: String): dynamic
diff --git a/kvision-modules/kvision-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
new file mode 100644
index 00000000..d824125b
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeInputSpec.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.time
+
+import pl.treksoft.kvision.form.time.DateTimeInput
+import pl.treksoft.kvision.panel.Root
+import pl.treksoft.kvision.types.KDate
+import pl.treksoft.kvision.types.toStringF
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class DateTimeInputSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", true)
+ val data = KDate()
+ val dti = DateTimeInput(value = data).apply {
+ placeholder = "place"
+ id = "idti"
+ }
+ root.add(dti)
+ val value = dti.getElementJQuery()?.`val`()
+ assertEquals(
+ data.toStringF(dti.format),
+ value,
+ "Should render date time input with correctly formatted value"
+ )
+ }
+ }
+
+} \ No newline at end of file
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
new file mode 100644
index 00000000..482a7b7a
--- /dev/null
+++ b/kvision-modules/kvision-datetime/src/test/kotlin/test/pl/treksoft/kvision/form/time/DateTimeSpec.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.time
+
+import pl.treksoft.kvision.form.time.DateTime
+import pl.treksoft.kvision.panel.Root
+import pl.treksoft.kvision.types.KDate
+import pl.treksoft.kvision.types.toStringF
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+
+class DateTimeSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", true)
+ val data = KDate()
+ val ti = DateTime(value = data, label = "Label").apply {
+ placeholder = "place"
+ name = "name"
+ disabled = true
+ }
+ root.add(ti)
+ val element = document.getElementById("test")
+ val id = ti.input.id
+ val datastr = data.toStringF(ti.format)
+ assertEqualsHtml(
+ "<div class=\"form-group\"><label class=\"control-label\" for=\"$id\">Label</label><input class=\"form-control\" id=\"$id\" type=\"text\" placeholder=\"place\" name=\"name\" disabled=\"disabled\" value=\"$datastr\"></div>",
+ element?.innerHTML,
+ "Should render correct date time input form control"
+ )
+ ti.validatorError = "Validation Error"
+ assertEqualsHtml(
+ "<div class=\"form-group has-error\"><label class=\"control-label\" for=\"$id\">Label</label><input class=\"form-control\" id=\"$id\" type=\"text\" placeholder=\"place\" name=\"name\" disabled=\"disabled\" value=\"$datastr\"><span class=\"help-block small\">Validation Error</span></div>",
+ element?.innerHTML,
+ "Should render correct date time input form control with validation error"
+ )
+ }
+ }
+
+} \ No newline at end of file
diff --git a/kvision-modules/kvision-handlebars/build.gradle b/kvision-modules/kvision-handlebars/build.gradle
new file mode 100644
index 00000000..918ed5a7
--- /dev/null
+++ b/kvision-modules/kvision-handlebars/build.gradle
@@ -0,0 +1,10 @@
+apply from: "../shared.gradle"
+
+kotlinFrontend {
+
+ npm {
+ dependency("handlebars", "4.0.11")
+ dependency("handlebars-loader", "1.7.0")
+ }
+
+}
diff --git a/kvision-modules/kvision-handlebars/package.json.d/project.info b/kvision-modules/kvision-handlebars/package.json.d/project.info
new file mode 100644
index 00000000..fd5bb305
--- /dev/null
+++ b/kvision-modules/kvision-handlebars/package.json.d/project.info
@@ -0,0 +1,3 @@
+{
+ "description": "KVision Handlebars module"
+}
diff --git a/kvision-modules/kvision-handlebars/src/main/kotlin/pl/treksoft/kvision/KVManagerHandlebars.kt b/kvision-modules/kvision-handlebars/src/main/kotlin/pl/treksoft/kvision/KVManagerHandlebars.kt
new file mode 100644
index 00000000..b7f5fbc6
--- /dev/null
+++ b/kvision-modules/kvision-handlebars/src/main/kotlin/pl/treksoft/kvision/KVManagerHandlebars.kt
@@ -0,0 +1,41 @@
+/*
+ * 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
+
+import org.w3c.dom.asList
+import kotlin.browser.document
+
+internal val KVManagerHandlebarsInit = KVManagerHandlebars.init()
+
+/**
+ * Internal singleton object which initializes and configures KVision handlebars module.
+ */
+@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught")
+internal object KVManagerHandlebars {
+ fun init() {}
+
+ private val handlebars = try {
+ require("handlebars/dist/handlebars.runtime.min.js")
+ } catch (e: Throwable) {
+ }
+
+}
diff --git a/kvision-modules/kvision-i18n/build.gradle b/kvision-modules/kvision-i18n/build.gradle
new file mode 100644
index 00000000..a2b6d3f8
--- /dev/null
+++ b/kvision-modules/kvision-i18n/build.gradle
@@ -0,0 +1,9 @@
+apply from: "../shared.gradle"
+
+kotlinFrontend {
+
+ npm {
+ dependency("jed", "1.1.1")
+ }
+
+}
diff --git a/kvision-modules/kvision-i18n/package.json.d/project.info b/kvision-modules/kvision-i18n/package.json.d/project.info
new file mode 100644
index 00000000..cefa5d1f
--- /dev/null
+++ b/kvision-modules/kvision-i18n/package.json.d/project.info
@@ -0,0 +1,3 @@
+{
+ "description": "KVision i18n module"
+}
diff --git a/kvision-modules/kvision-i18n/src/main/kotlin/pl/treksoft/kvision/KVManagerI18n.kt b/kvision-modules/kvision-i18n/src/main/kotlin/pl/treksoft/kvision/KVManagerI18n.kt
new file mode 100644
index 00000000..66e3e72c
--- /dev/null
+++ b/kvision-modules/kvision-i18n/src/main/kotlin/pl/treksoft/kvision/KVManagerI18n.kt
@@ -0,0 +1,41 @@
+/*
+ * 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
+
+import org.w3c.dom.asList
+import kotlin.browser.document
+
+internal val KVManagerI18nInit = KVManagerI18n.init()
+
+/**
+ * Internal singleton object which initializes and configures KVision i18n module.
+ */
+@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught")
+internal object KVManagerI18n {
+ fun init() {}
+
+ private val jed = try {
+ require("jed")
+ } catch (e: Throwable) {
+ }
+
+}
diff --git a/kvision-modules/kvision-i18n/src/main/kotlin/pl/treksoft/kvision/i18n/DefaultI18nManager.kt b/kvision-modules/kvision-i18n/src/main/kotlin/pl/treksoft/kvision/i18n/DefaultI18nManager.kt
new file mode 100644
index 00000000..1eebad5c
--- /dev/null
+++ b/kvision-modules/kvision-i18n/src/main/kotlin/pl/treksoft/kvision/i18n/DefaultI18nManager.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.i18n
+
+external class Jed(json: dynamic) {
+ fun gettext(key: String): String
+ fun ngettext(singularKey: String, pluralKey: String, value: Int): String
+ fun sprintf(format: String, value: Int): String
+}
+
+class DefaultI18nManager(translations: Map<String, dynamic>) : I18nManager {
+
+ private val cache: Map<String, Jed> = translations.map { it.key to Jed(it.value) }.toMap()
+
+ override fun gettext(key: String): String {
+ return cache[I18n.language]?.gettext(key) ?: key
+ }
+
+ override fun ngettext(singularKey: String, pluralKey: String, value: Int): String {
+ return cache[I18n.language]?.run {
+ sprintf(ngettext(singularKey, pluralKey, value), value)
+ } ?: if (value == 1) singularKey else pluralKey
+ }
+
+}
diff --git a/kvision-modules/kvision-richtext/build.gradle b/kvision-modules/kvision-richtext/build.gradle
new file mode 100644
index 00000000..c00c4c27
--- /dev/null
+++ b/kvision-modules/kvision-richtext/build.gradle
@@ -0,0 +1,9 @@
+apply from: "../shared.gradle"
+
+kotlinFrontend {
+
+ npm {
+ dependency("trix", "0.11.1")
+ }
+
+}
diff --git a/kvision-modules/kvision-richtext/package.json.d/project.info b/kvision-modules/kvision-richtext/package.json.d/project.info
new file mode 100644
index 00000000..856dc500
--- /dev/null
+++ b/kvision-modules/kvision-richtext/package.json.d/project.info
@@ -0,0 +1,3 @@
+{
+ "description": "KVision RichText module"
+}
diff --git a/kvision-modules/kvision-richtext/src/main/kotlin/pl/treksoft/kvision/KVManagerRichText.kt b/kvision-modules/kvision-richtext/src/main/kotlin/pl/treksoft/kvision/KVManagerRichText.kt
new file mode 100644
index 00000000..1ccd7a85
--- /dev/null
+++ b/kvision-modules/kvision-richtext/src/main/kotlin/pl/treksoft/kvision/KVManagerRichText.kt
@@ -0,0 +1,64 @@
+/*
+ * 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
+
+import pl.treksoft.kvision.i18n.I18n
+import pl.treksoft.kvision.utils.obj
+import kotlin.browser.window
+
+internal val KVManagerRichTextInit = KVManagerRichText.init()
+
+/**
+ * Internal singleton object which initializes and configures KVision RichText module.
+ */
+@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught")
+internal object KVManagerRichText {
+ fun init() {}
+
+ private val trixCss = try {
+ require("trix/dist/trix.css")
+ } catch (e: Throwable) {
+ }
+ private val trix = try {
+ val trix = require("trix")
+ window.asDynamic().Trix = trix
+ trix.config.languages = obj {}
+ trix.config.languages["en"] = obj {}
+ for (key in js("Object").keys(trix.config.lang)) {
+ trix.config.languages["en"][key] = trix.config.lang[key]
+ }
+ val orig = trix.config.toolbar.getDefaultHTML
+ trix.config.toolbar.getDefaultHTML = {
+ val config = if (trix.config.languages[I18n.language] != undefined) {
+ trix.config.languages[I18n.language]
+ } else {
+ trix.config.languages["en"]
+ }
+ for (key in js("Object").keys(trix.config.lang)) {
+ trix.config.lang[key] = config[key]
+ }
+ orig()
+ }
+ require("./js/locales/trix/trix.pl.js")
+ } catch (e: Throwable) {
+ }
+}
diff --git a/kvision-modules/kvision-richtext/src/main/kotlin/pl/treksoft/kvision/form/text/RichText.kt b/kvision-modules/kvision-richtext/src/main/kotlin/pl/treksoft/kvision/form/text/RichText.kt
new file mode 100644
index 00000000..22126797
--- /dev/null
+++ b/kvision-modules/kvision-richtext/src/main/kotlin/pl/treksoft/kvision/form/text/RichText.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision.form.text
+
+import pl.treksoft.kvision.core.Container
+
+/**
+ * Form field rich text component.
+ *
+ * @constructor
+ * @param value text input value
+ * @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
+ */
+open class RichText(
+ value: String? = null, name: String? = null,
+ label: String? = null, rich: Boolean = false
+) : AbstractText(label, rich) {
+
+ /**
+ * Rich input control height.
+ */
+ var inputHeight
+ get() = input.height
+ set(value) {
+ input.height = value
+ }
+
+ final override val input: RichTextInput = RichTextInput(value).apply {
+ this.id = idc
+ this.name = name
+ }
+
+ init {
+ @Suppress("LeakingThis")
+ input.eventTarget = this
+ this.addInternal(input)
+ this.addInternal(validationInfo)
+ }
+
+ companion object {
+ /**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+ fun Container.richText(
+ value: String? = null,
+ name: String? = null,
+ label: String? = null,
+ rich: Boolean = false,
+ init: (RichText.() -> Unit)? = null
+ ): RichText {
+ val richText = RichText(value, name, label, rich).apply { init?.invoke(this) }
+ this.add(richText)
+ return richText
+ }
+ }
+}
diff --git a/kvision-modules/kvision-richtext/src/main/kotlin/pl/treksoft/kvision/form/text/RichTextInput.kt b/kvision-modules/kvision-richtext/src/main/kotlin/pl/treksoft/kvision/form/text/RichTextInput.kt
new file mode 100644
index 00000000..961c27cd
--- /dev/null
+++ b/kvision-modules/kvision-richtext/src/main/kotlin/pl/treksoft/kvision/form/text/RichTextInput.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.text
+
+import com.github.snabbdom.VNode
+import pl.treksoft.jquery.jQuery
+import pl.treksoft.kvision.KVManagerRichText
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.StringPair
+import kotlin.browser.document
+
+/**
+ * Basic rich text component.
+ *
+ * @constructor
+ * @param value text input value
+ * @param classes a set of CSS class names
+ */
+open class RichTextInput(value: String? = null, classes: Set<String> = setOf()) :
+ AbstractTextInput(value, classes + "form-control" + "trix-control") {
+ private var trixId: String? = null
+
+ override fun render(): VNode {
+ return render("trix-editor")
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ placeholder?.let {
+ sn.add("placeholder" to translate(it))
+ }
+ 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
+ }
+
+ @Suppress("UnsafeCastFromDynamic", "ComplexMethod")
+ override fun afterInsert(node: VNode) {
+ if (this.disabled || this.readonly == true) {
+ this.getElementJQuery()?.removeAttr("contenteditable")
+ } else {
+ this.getElementJQuery()?.on("trix-change", { _, _ ->
+ if (trixId != null) {
+ val v = document.getElementById("trix-input-$trixId")?.let { jQuery(it).`val`() as String? }
+ value = if (v != null && v.isNotEmpty()) {
+ v
+ } else {
+ null
+ }
+ val event = org.w3c.dom.events.Event("change")
+ this.getElement()?.dispatchEvent(event)
+ }
+ })
+ }
+ this.getElementJQuery()?.on("trix-initialize", { _, _ ->
+ trixId = this.getElementJQuery()?.attr("trix-id")
+ if (trixId != null) {
+ value?.let {
+ if (this.getElement().asDynamic().editor != undefined) {
+ this.getElement().asDynamic().editor.loadHTML(it)
+ }
+ }
+ }
+ })
+ this.getElementJQuery()?.on("trix-file-accept", { e, _ -> e.preventDefault() })
+ }
+
+ override fun afterDestroy() {
+ document.getElementById("trix-input-$trixId")?.let { jQuery(it).remove() }
+ document.getElementById("trix-toolbar-$trixId")?.let { jQuery(it).remove() }
+ trixId = null
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ override fun refreshState() {
+ val v = document.getElementById("trix-input-$trixId")?.let { jQuery(it).`val`() as String? }
+ if (value != v) {
+ val editor = this.getElement().asDynamic().editor
+ if (editor != undefined) {
+ value?.let {
+ editor.loadHTML(it)
+ } ?: editor.loadHTML("")
+ }
+ }
+ }
+
+ override fun changeValue() {
+ // disabled parent class functionality
+ }
+
+ companion object {
+ /**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+ fun Container.richTextInput(
+ value: String? = null, classes: Set<String> = setOf(), init: (RichTextInput.() -> Unit)? = null
+ ): RichTextInput {
+ val richTextInput = RichTextInput(value, classes).apply { init?.invoke(this) }
+ this.add(richTextInput)
+ return richTextInput
+ }
+ }
+}
diff --git a/kvision-modules/kvision-richtext/src/main/resources/js/locales/trix/trix.pl.js b/kvision-modules/kvision-richtext/src/main/resources/js/locales/trix/trix.pl.js
new file mode 100644
index 00000000..84dbd886
--- /dev/null
+++ b/kvision-modules/kvision-richtext/src/main/resources/js/locales/trix/trix.pl.js
@@ -0,0 +1,28 @@
+(function($){
+ window.Trix.config.languages["pl"] = {
+ GB:"GB",
+ KB:"KB",
+ MB:"MB",
+ PB:"PB",
+ TB:"TB",
+ bold:"Pogrubienie",
+ bullets:"Wypunktowanie",
+ byte:"Bajt",
+ bytes:"Bajty",
+ captionPlaceholder:"Dodaj tytuł…",
+ code:"Kod źródłowy",
+ heading1:"Nagłówek",
+ indent:"Zwiększ poziom",
+ italic:"Pochylenie",
+ link:"Link",
+ numbers:"Numerowanie",
+ outdent:"Zmniejsz poziom",
+ quote:"Cytat",
+ redo:"Ponów",
+ remove:"Usuń",
+ strike:"Przekreślenie",
+ undo:"Cofnij",
+ unlink:"Usuń link",
+ urlPlaceholder:"Wprowadź adres URL…"
+ }
+}(jQuery));
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
new file mode 100644
index 00000000..1da1fe1a
--- /dev/null
+++ b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package test.pl.treksoft.kvision
+
+import org.w3c.dom.Element
+import pl.treksoft.jquery.jQuery
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.panel.Root
+import kotlin.browser.document
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+interface TestSpec {
+ fun beforeTest()
+
+ fun afterTest()
+
+ fun run(code: () -> Unit) {
+ beforeTest()
+ code()
+ afterTest()
+ }
+}
+
+interface SimpleSpec : TestSpec {
+
+ override fun beforeTest() {
+ }
+
+ override fun afterTest() {
+ }
+
+}
+
+interface DomSpec : TestSpec {
+
+ override fun beforeTest() {
+ val fixture = "<div style=\"display: none\" id=\"pretest\">" +
+ "<div id=\"test\"></div></div>"
+ document.body?.insertAdjacentHTML("afterbegin", fixture)
+ }
+
+ override fun afterTest() {
+ val div = document.getElementById("pretest")
+ div?.let { jQuery(it).remove() }
+ jQuery(`object` = ".modal-backdrop").remove()
+ }
+
+ fun assertEqualsHtml(expected: String?, actual: String?, message: String?) {
+ if (expected != null && actual != null) {
+ val exp = jQuery(html = expected)
+ val act = jQuery(html = actual)
+ val result = exp[0]?.isEqualNode(act[0])
+ if (result == true) {
+ assertTrue(result == true, message)
+ } else {
+ assertEquals(expected, actual, message)
+ }
+ } else {
+ assertEquals(expected, actual, message)
+ }
+ }
+}
+
+interface WSpec : DomSpec {
+
+ fun runW(code: (widget: Widget, element: Element?) -> Unit) {
+ run {
+ val root = Root("test", true)
+ val widget = Widget()
+ widget.id = "test_id"
+ root.add(widget)
+ val element = document.getElementById("test_id")
+ code(widget, element)
+ }
+ }
+
+}
+
+external fun require(name: String): dynamic
diff --git a/kvision-modules/kvision-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
new file mode 100644
index 00000000..21b7dc39
--- /dev/null
+++ b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextInputSpec.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.text
+
+import pl.treksoft.jquery.jQuery
+import pl.treksoft.kvision.form.text.RichTextInput
+import pl.treksoft.kvision.panel.Root
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+class RichTextInputSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", true)
+ val hai = RichTextInput(value = "abc").apply {
+ placeholder = "place"
+ id = "idti"
+ }
+ root.add(hai)
+ val content = document.getElementById("test")?.let { jQuery(it).find("trix-editor")[0]?.outerHTML }
+ assertTrue(
+ content?.startsWith("<trix-editor") == true,
+ "Should render correct html area control"
+ )
+ }
+ }
+
+} \ No newline at end of file
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
new file mode 100644
index 00000000..844b7e94
--- /dev/null
+++ b/kvision-modules/kvision-richtext/src/test/kotlin/test/pl/treksoft/kvision/form/text/RichTextSpec.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.text
+
+import pl.treksoft.jquery.jQuery
+import pl.treksoft.kvision.form.text.RichText
+import pl.treksoft.kvision.panel.Root
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+class RichTextSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", true)
+ val hai = RichText(value = "abc", label = "Field").apply {
+ placeholder = "place"
+ id = "idti"
+ }
+ root.add(hai)
+ val iid = hai.input.id
+ val content = document.getElementById("test")?.let { jQuery(it).find("trix-editor")[0]?.outerHTML }
+ assertTrue(
+ content?.startsWith("<trix-editor") == true,
+ "Should render correct html area form control"
+ )
+ val label = document.getElementById("test")?.let { jQuery(it).find("label")[0]?.outerHTML }
+ assertEqualsHtml(
+ "<label class=\"control-label\" for=\"$iid\">Field</label>",
+ label,
+ "Should render correct label for html area form control"
+ )
+ }
+ }
+
+} \ No newline at end of file
diff --git a/kvision-modules/kvision-select/build.gradle b/kvision-modules/kvision-select/build.gradle
new file mode 100644
index 00000000..f8fcbc8a
--- /dev/null
+++ b/kvision-modules/kvision-select/build.gradle
@@ -0,0 +1,10 @@
+apply from: "../shared.gradle"
+
+kotlinFrontend {
+
+ npm {
+ dependency("bootstrap-select", "1.12.4")
+ dependency("ajax-bootstrap-select", "1.4.3")
+ }
+
+}
diff --git a/kvision-modules/kvision-select/package.json.d/project.info b/kvision-modules/kvision-select/package.json.d/project.info
new file mode 100644
index 00000000..80e675b0
--- /dev/null
+++ b/kvision-modules/kvision-select/package.json.d/project.info
@@ -0,0 +1,3 @@
+{
+ "description": "KVision Select module"
+}
diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/KVManagerSelect.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/KVManagerSelect.kt
new file mode 100644
index 00000000..c7c3815a
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/KVManagerSelect.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision
+
+internal val KVManagerSelectInit = KVManagerSelect.init()
+
+/**
+ * Internal singleton object which initializes and configures KVision select module.
+ */
+@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught")
+internal object KVManagerSelect {
+ internal const val AJAX_REQUEST_DELAY = 300
+ internal const val KVNULL = "#kvnull"
+
+ fun init() {}
+
+ private val bootstrapSelectCss = try {
+ require("bootstrap-select/dist/css/bootstrap-select.min.css")
+ } catch (e: Throwable) {
+ }
+ private val bootstrapSelect = try {
+ require("bootstrap-select/dist/js/bootstrap-select.min.js")
+ require("./js/locales/bootstrap-select/bootstrap-select-i18n.min.js")
+ } catch (e: Throwable) {
+ }
+ private val bootstrapSelectAjaxCss = try {
+ require("ajax-bootstrap-select/dist/css/ajax-bootstrap-select.min.css")
+ } catch (e: Throwable) {
+ }
+ private val bootstrapSelectAjax = try {
+ require("ajax-bootstrap-select/dist/js/ajax-bootstrap-select.min.js")
+ require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.de-DE.min.js")
+ require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.es-ES.min.js")
+ require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.fr-FR.min.js")
+ require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.it-IT.min.js")
+ require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ja-JP.min.js")
+ require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ko-KR.min.js")
+ require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.nl-NL.min.js")
+ require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.pl-PL.min.js")
+ require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.pt-BR.min.js")
+ require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ru-RU.min.js")
+ require("./js/locales/ajax-bootstrap-select/ajax-bootstrap-select.tr-TR.min.js")
+ } catch (e: Throwable) {
+ }
+
+}
diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/AjaxOptions.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/AjaxOptions.kt
new file mode 100644
index 00000000..212dc456
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/AjaxOptions.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.jquery.JQueryXHR
+import pl.treksoft.kvision.KVManagerSelect.AJAX_REQUEST_DELAY
+import pl.treksoft.kvision.KVManagerSelect.KVNULL
+import pl.treksoft.kvision.i18n.I18n
+import pl.treksoft.kvision.utils.obj
+
+/**
+ * HTTP protocol type for the AJAX call.
+ */
+enum class HttpType(internal val type: String) {
+ GET("GET"),
+ POST("POST")
+}
+
+/**
+ * Data type for the AJAX call.
+ */
+enum class DataType(internal val type: String) {
+ JSON("json"),
+ JSONP("jsonp"),
+ XML("xml"),
+ TEXT("text"),
+ SCRIPT("script")
+}
+
+/**
+ * Data class for AJAX options.
+ *
+ * @constructor
+ * @param url the url address
+ * @param preprocessData
+ * [AjaxBootstrapSelect preprocessOption](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionspreprocessdata)
+ * option
+ * @param beforeSend
+ * [JQuery ajax.beforeSend](http://api.jquery.com/jquery.ajax/#jQuery-ajax-settings) option
+ * @param data
+ * [JQuery ajax.data](http://api.jquery.com/jquery.ajax/#jQuery-ajax-settings) option
+ * @param httpType
+ * [JQuery ajax.type](http://api.jquery.com/jquery.ajax/#jQuery-ajax-settings) option
+ * @param minLength
+ * [AjaxBootstrapSelect minLength](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionsminlength) option
+ * @param cache
+ * [AjaxBootstrapSelect cache](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionscache) option
+ * @param clearOnEmpty
+ * [AjaxBootstrapSelect clearOnEmpty](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionsclearonempty) option
+ * @param clearOnError
+ * [AjaxBootstrapSelect clearOnError](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionsclearonerror) option
+ * @param emptyRequest
+ * [AjaxBootstrapSelect emptyRequest](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionsemptyrequest) option
+ * @param requestDelay
+ * [AjaxBootstrapSelect requestDelay](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionsrequestdelay) option
+ * @param restoreOnError
+ * [AjaxBootstrapSelect restoreOnError](https://github.com/truckingsim/Ajax-Bootstrap-Select#optionsrestoreonerror)
+ * option
+ */
+data class AjaxOptions(
+ val url: String, val preprocessData: (dynamic) -> dynamic, val beforeSend: ((JQueryXHR) -> dynamic)? = null,
+ val data: dynamic = null, val httpType: HttpType = HttpType.GET,
+ val dataType: DataType = DataType.JSON, val minLength: Int = 0,
+ val cache: Boolean = true, val clearOnEmpty: Boolean = true, val clearOnError: Boolean = true,
+ val emptyRequest: Boolean = false,
+ val requestDelay: Int = AJAX_REQUEST_DELAY, val restoreOnError: Boolean = false
+)
+
+/**
+ * Convert AjaxOptions to JavaScript JSON object.
+ * @param emptyOption add an empty position as the first select option
+ * @return JSON object
+ */
+fun AjaxOptions.toJs(emptyOption: Boolean): dynamic {
+ val procData = { data: dynamic ->
+ val processedData = this.preprocessData(data)
+ if (emptyOption) {
+ val ret = mutableListOf(obj {
+ this.value = KVNULL
+ this.text = ""
+ })
+ @Suppress("UnsafeCastFromDynamic")
+ ret.addAll((processedData as Array<dynamic>).asList())
+ ret.toTypedArray()
+ } else {
+ processedData
+ }
+ }
+ val language = I18n.language
+ return obj {
+ this.ajax = obj {
+ this.url = url
+ this.type = httpType.type
+ this.dataType = dataType.type
+ this.data = data
+ this.beforeSend = beforeSend
+ }
+ this.preprocessData = procData
+ this.minLength = minLength
+ this.cache = cache
+ this.clearOnEmpty = clearOnEmpty
+ this.clearOnError = clearOnError
+ this.emptyRequest = emptyRequest
+ this.preserveSelected = false
+ this.requestDelay = requestDelay
+ this.restoreOnError = restoreOnError
+ this.langCode = language
+ }
+}
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
new file mode 100644
index 00000000..f19081e1
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/Select.kt
@@ -0,0 +1,285 @@
+/*
+ * 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 Select control.
+ *
+ * The select control can be populated directly from *options* parameter or manually by adding
+ * [SelectOption] or [SelectOptGroup] components to the container.
+ *
+ * @constructor
+ * @param options an optional list of options (label to value 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)
+ * @param ajaxOptions additional options for remote (AJAX) data source
+ * @param label label text bound to the input element
+ * @param rich determines if [label] can contain HTML code
+ */
+@Suppress("TooManyFunctions")
+open class Select(
+ options: List<StringPair>? = null, value: String? = null, name: String? = null,
+ multiple: Boolean = false, ajaxOptions: AjaxOptions? = null, label: String? = null,
+ rich: Boolean = false
+) : SimplePanel(setOf("form-group")), StringFormControl {
+
+ /**
+ * A list of options (label to value 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
+ }
+ /**
+ * Determines if multiple value selection is allowed.
+ */
+ var multiple
+ get() = input.multiple
+ set(value) {
+ input.multiple = value
+ }
+ /**
+ * Additional options for remote (AJAX) data source.
+ */
+ var ajaxOptions
+ get() = input.ajaxOptions
+ set(value) {
+ input.ajaxOptions = value
+ }
+ /**
+ * Maximal number of selected options.
+ */
+ var maxOptions
+ get() = input.maxOptions
+ set(value) {
+ input.maxOptions = value
+ }
+ /**
+ * Determines if live search is available.
+ */
+ var liveSearch
+ get() = input.liveSearch
+ set(value) {
+ input.liveSearch = value
+ }
+ /**
+ * The placeholder for the select control.
+ */
+ var placeholder
+ get() = input.placeholder
+ set(value) {
+ input.placeholder = value
+ }
+ /**
+ * The style of the select control.
+ */
+ var style
+ get() = input.style
+ set(value) {
+ input.style = value
+ }
+ /**
+ * The width of the select control.
+ */
+ var selectWidth
+ get() = input.selectWidth
+ set(value) {
+ input.selectWidth = value
+ }
+ /**
+ * The width type of the select control.
+ */
+ var selectWidthType
+ get() = input.selectWidthType
+ set(value) {
+ input.selectWidthType = 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_select_$counter"
+ final override val input: SelectInput = SelectInput(
+ options, value, multiple, ajaxOptions,
+ 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()
+ }
+
+ /**
+ * Opens dropdown with options.
+ */
+ open fun showOptions() {
+ input.showOptions()
+ }
+
+ /**
+ * Hides dropdown with options.
+ */
+ open fun hideOptions() {
+ input.hideOptions()
+ }
+
+ /**
+ * Toggles visibility of dropdown with options.
+ */
+ open fun toggleOptions() {
+ input.toggleOptions()
+ }
+
+ 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.select(
+ options: List<StringPair>? = null, value: String? = null, name: String? = null,
+ multiple: Boolean = false, ajaxOptions: AjaxOptions? = null, label: String? = null,
+ rich: Boolean = false, init: (Select.() -> Unit)? = null
+ ): Select {
+ val select = Select(options, value, name, multiple, ajaxOptions, label, rich).apply { init?.invoke(this) }
+ this.add(select)
+ return select
+ }
+ }
+}
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
new file mode 100644
index 00000000..8f4569c7
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectInput.kt
@@ -0,0 +1,363 @@
+/*
+ * 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.KVManagerSelect.KVNULL
+import pl.treksoft.kvision.core.Component
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.CssSize
+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.ButtonStyle
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.asString
+import pl.treksoft.kvision.utils.obj
+
+/**
+ * Select width types. See [Bootstrap Select width](http://silviomoreto.github.io/bootstrap-select/examples/#width).
+ */
+enum class SelectWidthType(internal val value: String) {
+ AUTO("auto"),
+ FIT("fit")
+}
+
+/**
+ * The basic component for Select control.
+ *
+ * The select control can be populated directly from *options* parameter or manually by adding
+ * [SelectOption] or [SelectOptGroup] components to the container.
+ *
+ * @constructor
+ * @param options an optional list of options (label to value 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
+ * @param classes a set of CSS class names
+ */
+@Suppress("TooManyFunctions")
+open class SelectInput(
+ options: List<StringPair>? = null, value: String? = null,
+ multiple: Boolean = false, ajaxOptions: AjaxOptions? = null,
+ classes: Set<String> = setOf()
+) : SimplePanel(classes), FormInput {
+
+ /**
+ * A list of options (label to value pairs) for the select control.
+ */
+ internal var options by refreshOnUpdate(options, { setChildrenFromOptions() })
+ /**
+ * A value of the selected option.
+ */
+ var value by refreshOnUpdate(value, { refreshState() })
+ /**
+ * The name attribute of the generated HTML select element.
+ */
+ override var name: String? by refreshOnUpdate()
+ /**
+ * Determines if multiple value selection is allowed.
+ */
+ var multiple by refreshOnUpdate(multiple)
+ /**
+ * Additional options for remote (AJAX) data source.
+ */
+ var ajaxOptions by refreshOnUpdate(ajaxOptions, {
+ if (it != null) {
+ liveSearch = true
+ }
+ refresh()
+ })
+ /**
+ * Maximal number of selected options.
+ */
+ var maxOptions: Int? by refreshOnUpdate()
+ /**
+ * Determines if live search is available.
+ */
+ var liveSearch by refreshOnUpdate(false)
+ /**
+ * The placeholder for the select control.
+ */
+ var placeholder: String? by refreshOnUpdate()
+ /**
+ * The style of the select control.
+ */
+ var style: ButtonStyle? by refreshOnUpdate()
+ /**
+ * The width of the select control.
+ */
+ var selectWidth: CssSize? by refreshOnUpdate()
+ /**
+ * The width type of the select control.
+ */
+ var selectWidthType: SelectWidthType? by refreshOnUpdate()
+ /**
+ * Determines if an empty option is automatically generated.
+ */
+ var emptyOption by refreshOnUpdate(false, { setChildrenFromOptions() })
+ /**
+ * Determines if the field is disabled.
+ */
+ override var disabled by refreshOnUpdate(false)
+ /**
+ * Determines if the select is automatically focused.
+ */
+ var autofocus: Boolean? by refreshOnUpdate()
+ /**
+ * The size of the input.
+ */
+ override var size: InputSize? by refreshOnUpdate()
+
+ init {
+ setChildrenFromOptions()
+ this.setInternalEventListener<SelectInput> {
+ change = {
+ val v = getElementJQuery()?.`val`()
+ self.value = v?.let {
+ calculateValue(it)
+ }
+ }
+ }
+ }
+
+ private fun calculateValue(v: Any): String? {
+ return if (this.multiple) {
+ @Suppress("UNCHECKED_CAST")
+ val arr = v as? Array<String>
+ if (arr != null && arr.isNotEmpty()) {
+ arr.joinToString()
+ } else {
+ null
+ }
+ } else {
+ val vs = v as String
+ if (KVNULL == vs || vs.isEmpty()) {
+ null
+ } else {
+ vs
+ }
+ }
+ }
+
+ override fun render(): VNode {
+ return render("select", childrenVNodes())
+ }
+
+ override fun add(child: Component): SimplePanel {
+ super.add(child)
+ refreshSelectInput()
+ return this
+ }
+
+ override fun addAll(children: List<Component>): SimplePanel {
+ super.addAll(children)
+ refreshSelectInput()
+ return this
+ }
+
+ override fun remove(child: Component): SimplePanel {
+ super.remove(child)
+ refreshSelectInput()
+ return this
+ }
+
+ override fun removeAll(): SimplePanel {
+ super.removeAll()
+ refreshSelectInput()
+ return this
+ }
+
+ private fun setChildrenFromOptions() {
+ if (ajaxOptions == null) {
+ super.removeAll()
+ if (emptyOption) {
+ super.add(SelectOption(KVNULL, ""))
+ }
+ options?.let {
+ val c = it.map {
+ SelectOption(it.first, it.second)
+ }
+ super.addAll(c)
+ }
+ }
+ this.refreshSelectInput()
+ }
+
+ /**
+ * Opens dropdown with options.
+ */
+ open fun showOptions() {
+ getElementJQueryD()?.selectpicker("show")
+ }
+
+ /**
+ * Hides dropdown with options.
+ */
+ open fun hideOptions() {
+ getElementJQueryD()?.selectpicker("hide")
+ }
+
+ /**
+ * Toggles visibility of dropdown with options.
+ */
+ open fun toggleOptions() {
+ getElementJQueryD()?.selectpicker("toggle")
+ }
+
+ override fun getSnClass(): List<StringBoolPair> {
+ val cl = super.getSnClass().toMutableList()
+ cl.add("selectpicker" to true)
+ size?.let {
+ cl.add(it.className to true)
+ }
+ return cl
+ }
+
+ private fun refreshSelectInput() {
+ getElementJQueryD()?.selectpicker("refresh")
+ refreshState()
+ }
+
+ @Suppress("ComplexMethod")
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ name?.let {
+ sn.add("name" to it)
+ }
+ if (multiple) {
+ sn.add("multiple" to "multiple")
+ }
+ maxOptions?.let {
+ sn.add("data-max-options" to "" + it)
+ }
+ if (liveSearch) {
+ sn.add("data-live-search" to "true")
+ }
+ placeholder?.let {
+ sn.add("title" to translate(it))
+ }
+ autofocus?.let {
+ if (it) {
+ sn.add("autofocus" to "autofocus")
+ }
+ }
+ if (disabled) {
+ sn.add("disabled" to "disabled")
+ }
+ val btnStyle = style?.className ?: "btn-default"
+ when (size) {
+ InputSize.LARGE -> {
+ sn.add("data-style" to "$btnStyle btn-lg")
+ }
+ InputSize.SMALL -> {
+ sn.add("data-style" to "$btnStyle btn-sm")
+ }
+ else -> {
+ sn.add("data-style" to btnStyle)
+ }
+ }
+ selectWidthType?.let {
+ sn.add("data-width" to it.value)
+ } ?: selectWidth?.let {
+ sn.add("data-width" to it.asString())
+ }
+ return sn
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ override fun afterInsert(node: VNode) {
+ ajaxOptions?.let {
+ getElementJQueryD()?.selectpicker("render").ajaxSelectPicker(it.toJs(emptyOption))
+ } ?: getElementJQueryD()?.selectpicker("render")
+
+ this.getElementJQuery()?.on("show.bs.select", { e, _ ->
+ this.dispatchEvent("showBsSelect", obj { detail = e })
+ })
+ this.getElementJQuery()?.on("shown.bs.select", { e, _ ->
+ this.dispatchEvent("shownBsSelect", obj { detail = e })
+ })
+ this.getElementJQuery()?.on("hide.bs.select", { e, _ ->
+ this.dispatchEvent("hideBsSelect", obj { detail = e })
+ })
+ this.getElementJQuery()?.on("hidden.bs.select", { e, _ ->
+ this.dispatchEvent("hiddenBsSelect", obj { detail = e })
+ })
+ this.getElementJQuery()?.on("loaded.bs.select", { e, _ ->
+ this.dispatchEvent("loadedBsSelect", obj { detail = e })
+ })
+ this.getElementJQuery()?.on("rendered.bs.select", { e, _ ->
+ this.dispatchEvent("renderedBsSelect", obj { detail = e })
+ })
+ this.getElementJQuery()?.on("refreshed.bs.select", { e, _ ->
+ this.dispatchEvent("refreshedBsSelect", obj { detail = e })
+ })
+ this.getElementJQueryD()?.on("changed.bs.select", { e, cIndex: Int ->
+ e["clickedIndex"] = cIndex
+ this.dispatchEvent("changedBsSelect", obj { detail = e })
+ })
+ refreshState()
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ private fun refreshState() {
+ value?.let {
+ if (multiple) {
+ getElementJQueryD()?.selectpicker("val", it.split(",").toTypedArray())
+ } else {
+ getElementJQueryD()?.selectpicker("val", it)
+ }
+ } ?: getElementJQueryD()?.selectpicker("val", null)
+ }
+
+ /**
+ * Makes the input element focused.
+ */
+ open fun focus() {
+ getElementJQuery()?.focus()
+ }
+
+ /**
+ * Makes the input element blur.
+ */
+ open fun blur() {
+ getElementJQuery()?.blur()
+ }
+
+ companion object {
+ /**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+ fun Container.selectInput(
+ options: List<StringPair>? = null, value: String? = null,
+ multiple: Boolean = false, ajaxOptions: AjaxOptions? = null,
+ classes: Set<String> = setOf(), init: (SelectInput.() -> Unit)? = null
+ ): SelectInput {
+ val selectInput = SelectInput(options, value, multiple, ajaxOptions, classes).apply { init?.invoke(this) }
+ this.add(selectInput)
+ return selectInput
+ }
+ }
+}
diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOptGroup.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOptGroup.kt
new file mode 100644
index 00000000..e33b3457
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOptGroup.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.StringPair
+import pl.treksoft.kvision.panel.SimplePanel
+
+/**
+ * The helper container for adding option groups to [Select].
+ *
+ * The option group can be populated directly from *options* parameter or manually by adding
+ * [SelectOption] components to the container.
+ *
+ * @constructor
+ * @param label the label of the group
+ * @param options an optional list of options (label to value pairs) for the group
+ * @param maxOptions maximal number of selected options in the group
+ * @param disabled renders a disabled group
+ * @param classes a set of CSS class names
+ */
+open class SelectOptGroup(
+ label: String, options: List<StringPair>? = null, maxOptions: Int? = null,
+ disabled: Boolean = false, classes: Set<String> = setOf()
+) : SimplePanel(classes) {
+ /**
+ * A label for the group.
+ */
+ var label by refreshOnUpdate(label)
+ /**
+ * A list of options (label to value pairs) for the group.
+ */
+ var options by refreshOnUpdate(options, { setChildrenFromOptions() })
+ /**
+ * Maximal number of selected options in the group.
+ */
+ var maxOptions by refreshOnUpdate(maxOptions)
+ /**
+ * Determines if the group is disabled.
+ */
+ var disabled by refreshOnUpdate(disabled)
+
+ init {
+ setChildrenFromOptions()
+ }
+
+ override fun render(): VNode {
+ return render("optgroup", childrenVNodes())
+ }
+
+ private fun setChildrenFromOptions() {
+ this.removeAll()
+ options?.let {
+ val c = it.map {
+ SelectOption(it.first, it.second)
+ }
+ this.addAll(c)
+ }
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ sn.add("label" to translate(label))
+ maxOptions?.let {
+ sn.add("data-max-options" to "" + it)
+ }
+ if (disabled) {
+ sn.add("disabled" to "disabled")
+ }
+ return sn
+ }
+}
diff --git a/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOption.kt b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOption.kt
new file mode 100644
index 00000000..d1bb636e
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/kotlin/pl/treksoft/kvision/form/select/SelectOption.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.StringPair
+import pl.treksoft.kvision.core.Widget
+
+/**
+ * The helper component for adding options to [Select] or [SelectOptGroup].
+ *
+ * @constructor
+ * @param value the value of the option
+ * @param label the label of the option
+ * @param subtext the small subtext after the label of the option
+ * @param icon the icon before the label of the option
+ * @param divider renders this option as a divider
+ * @param disabled renders a disabled option
+ * @param classes a set of CSS class names
+ */
+open class SelectOption(
+ value: String? = null, label: String? = null, subtext: String? = null, icon: String? = null,
+ divider: Boolean = false, disabled: Boolean = false,
+ classes: Set<String> = setOf()
+) : Widget(classes) {
+
+ /**
+ * The value of the option.
+ */
+ var value by refreshOnUpdate(value)
+ /**
+ * The label of the option.
+ */
+ var label by refreshOnUpdate(label)
+ /**
+ * The subtext after the label of the option.
+ */
+ var subtext by refreshOnUpdate(subtext)
+ /**
+ * The icon before the label of the option.
+ */
+ var icon by refreshOnUpdate(icon)
+ /**
+ * Determines if the option should be rendered as divider.
+ */
+ var divider by refreshOnUpdate(divider)
+ /**
+ * Determines if the option should be disabled.
+ */
+ var disabled by refreshOnUpdate(disabled)
+
+ override fun render(): VNode {
+ return if (!divider) {
+ render("option", arrayOf(translate(label) ?: value))
+ } else {
+ render("option")
+ }
+ }
+
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ if (!divider) {
+ value?.let {
+ sn.add("value" to it)
+ }
+ subtext?.let {
+ sn.add("data-subtext" to translate(it))
+ }
+ icon?.let {
+ if (it.startsWith("fa-")) {
+ sn.add("data-icon" to "fa $it")
+ } else {
+ sn.add("data-icon" to "glyphicon-$it")
+ }
+ }
+ if (disabled) {
+ sn.add("disabled" to "disabled")
+ }
+ } else {
+ sn.add("data-divider" to "true")
+ }
+ return sn
+ }
+}
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.de-DE.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.de-DE.min.js
new file mode 100644
index 00000000..08b38207
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.de-DE.min.js
@@ -0,0 +1,22 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.4.3
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2017 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2017-11-15 1:19:45 PM EST
+ */
+!(function ($) {
+/*!
+ * English translation for the "en-US" and "en" language codes.
+ * Tobias Weichart <tobias.weichart@gmail.com>
+ */
+$.fn.ajaxSelectPicker.locale["de-DE"]={currentlySelected:"Momentan ausgewählt",emptyTitle:"Hier klicken und eingeben",errorText:"Ergebnisse konnten nicht abgerufen wurden",searchPlaceholder:"Suche...",statusInitialized:"Suchbegriff eingeben",statusNoResults:"Keine Ergebnisse",statusSearching:"Suche...",statusTooShort:"Der Suchbegriff ist nicht lang genug"},$.fn.ajaxSelectPicker.locale.de=$.fn.ajaxSelectPicker.locale["de-DE"];})(jQuery);
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.en-US.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.en-US.min.js
new file mode 100644
index 00000000..8b130b97
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.en-US.min.js
@@ -0,0 +1,22 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.4.3
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2017 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2017-11-15 1:19:45 PM EST
+ */
+!(function ($) {
+/*!
+ * English translation for the "en-US" and "en" language codes.
+ * Mark Carver <mark.carver@me.com>
+ */
+$.fn.ajaxSelectPicker.locale["en-US"]={currentlySelected:"Currently Selected",emptyTitle:"Select and begin typing",errorText:"Unable to retrieve results",searchPlaceholder:"Search...",statusInitialized:"Start typing a search query",statusNoResults:"No Results",statusSearching:"Searching...",statusTooShort:"Please enter more characters"},$.fn.ajaxSelectPicker.locale.en=$.fn.ajaxSelectPicker.locale["en-US"];})(jQuery);
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.es-ES.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.es-ES.min.js
new file mode 100644
index 00000000..bbe3fe45
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.es-ES.min.js
@@ -0,0 +1,22 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.4.3
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2017 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2017-11-15 1:19:45 PM EST
+ */
+!(function ($) {
+/*!
+ * Spanish translation for the "es-ES" and "es" language codes.
+ * Diomedes Domínguez <diomedes.domimnguez@gmail.com>
+ */
+$.fn.ajaxSelectPicker.locale["es-ES"]={currentlySelected:"Seleccionado",emptyTitle:"Seleccione y comience a escribir",errorText:"No se puede recuperar resultados",searchPlaceholder:"Buscar...",statusInitialized:"Empieza a escribir una consulta de búsqueda",statusNoResults:"Sin Resultados",statusSearching:"Buscando...",statusTooShort:"Introduzca más caracteres"},$.fn.ajaxSelectPicker.locale.es=$.fn.ajaxSelectPicker.locale["es-ES"];})(jQuery);
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.fr-FR.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.fr-FR.min.js
new file mode 100644
index 00000000..1d86582f
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.fr-FR.min.js
@@ -0,0 +1,22 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.4.3
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2017 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2017-11-15 1:19:45 PM EST
+ */
+!(function ($) {
+/*!
+ * French translation for the "fr-FR" and "fr" language codes.
+ * Bastien (https://github.com/lisurc)
+ */
+$.fn.ajaxSelectPicker.locale["fr-FR"]={currentlySelected:"Actuellement sélectionné",emptyTitle:"Sélectionner et commencer à taper",errorText:"Impossible de récupérer les résultats",searchPlaceholder:"Rechercher...",statusInitialized:"Commencer à taper une recherche",statusNoResults:"Aucun résultat",statusSearching:"Recherche en cours...",statusTooShort:"Entrez plus de caractères"},$.fn.ajaxSelectPicker.locale.fr=$.fn.ajaxSelectPicker.locale["fr-FR"];})(jQuery);
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.it-IT.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.it-IT.min.js
new file mode 100644
index 00000000..b30deb3e
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.it-IT.min.js
@@ -0,0 +1,22 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.4.3
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2017 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2017-11-15 1:19:45 PM EST
+ */
+!(function ($) {
+/*!
+ * Italian translation for the "it-IT" and "it" language codes.
+ * Luca Longo <l.longo@ambita.it>
+ */
+$.fn.ajaxSelectPicker.locale["it-IT"]={currentlySelected:"Selezionati",emptyTitle:"Clicca qui e scrivi...",errorText:"Impossibile recuperare dei risultati",searchPlaceholder:"Cerca...",statusInitialized:"Inizia a digitare...",statusNoResults:"Non ci sono risultati",statusSearching:"Ricerca in corso...",statusTooShort:"Inserisci altri caratteri"},$.fn.ajaxSelectPicker.locale.it=$.fn.ajaxSelectPicker.locale["it-IT"];})(jQuery);
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ja-JP.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ja-JP.min.js
new file mode 100644
index 00000000..23a9a348
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ja-JP.min.js
@@ -0,0 +1,22 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.4.3
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2017 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2017-11-15 1:19:45 PM EST
+ */
+!(function ($) {
+/*!
+ * Japanese translation for the "ja-JP" and "ja" language codes.
+ * Haginaga <haginaga@unetworks.jp>
+ */
+$.fn.ajaxSelectPicker.locale["ja-JP"]={currentlySelected:"現在の値",emptyTitle:"未選択",errorText:"検索できません",searchPlaceholder:"検索する",statusInitialized:"選択肢を入力",statusNoResults:"見つかりません",statusSearching:"検索中...",statusTooShort:"入力文字数不足"},$.fn.ajaxSelectPicker.locale.ja=$.fn.ajaxSelectPicker.locale["ja-JP"];})(jQuery);
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ko-KR.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ko-KR.min.js
new file mode 100644
index 00000000..2fd5299b
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ko-KR.min.js
@@ -0,0 +1,22 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.4.3
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2017 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2017-11-15 1:19:45 PM EST
+ */
+!(function ($) {
+/*!
+ * Korean translation for the "ko-KR" and "ko" language codes.
+ * Jo JungLae <ubermenschjo@google.com>
+ */
+$.fn.ajaxSelectPicker.locale["ko-KR"]={currentlySelected:"현재 선택된 항목",emptyTitle:"클릭하고 입력 시작",errorText:"결과를 검색할 수 없습니다",searchPlaceholder:"검색",statusInitialized:"검색어를 입력",statusNoResults:"검색결과가 없습니다",statusSearching:"검색중",statusTooShort:"추가 문자를 입력하십시오."},$.fn.ajaxSelectPicker.locale.ko=$.fn.ajaxSelectPicker.locale["ko-KR"];})(jQuery);
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.nl-NL.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.nl-NL.min.js
new file mode 100644
index 00000000..6c6a16f2
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.nl-NL.min.js
@@ -0,0 +1,22 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.4.3
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2017 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2017-11-15 1:19:45 PM EST
+ */
+!(function ($) {
+/*!
+ * Dutch translation for the "nl-NL" and "nl" language codes.
+ * Arjen Ruiterkamp <arjenruiterkamp@gmail.com>
+ */
+$.fn.ajaxSelectPicker.locale["nl-NL"]={currentlySelected:"Momenteel geselecteerd",emptyTitle:"Selecteer en begin met typen",errorText:"Kon geen resultaten ophalen",searchPlaceholder:"Zoeken...",statusInitialized:"Begin met typen om te zoeken",statusNoResults:"Geen resultaten",statusSearching:"Zoeken...",statusTooShort:"U dient meer karakters in te voeren"},$.fn.ajaxSelectPicker.locale.nl=$.fn.ajaxSelectPicker.locale["nl-NL"];})(jQuery);
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.pl-PL.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.pl-PL.min.js
new file mode 100644
index 00000000..f104f491
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.pl-PL.min.js
@@ -0,0 +1,22 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.4.3
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2017 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2017-11-15 1:19:45 PM EST
+ */
+!(function ($) {
+/*!
+ * Polish translation for the "pl-PL" and "pl" language codes.
+ * Robert Jaros <rjaros@treksoft.pl>
+ */
+$.fn.ajaxSelectPicker.locale["pl-PL"]={currentlySelected:"Aktualny wybór",emptyTitle:"Wybierz i zacznij pisać",errorText:"Nie można pobrać wyników",searchPlaceholder:"Szukaj...",statusInitialized:"Zacznij pisać warunek wyszukiwania",statusNoResults:"Brak wyników",statusSearching:"Szukam...",statusTooShort:"Wprowadź więcej znaków"},$.fn.ajaxSelectPicker.locale.pl=$.fn.ajaxSelectPicker.locale["pl-PL"];})(jQuery);
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.pt-BR.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.pt-BR.min.js
new file mode 100644
index 00000000..2a6e743d
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.pt-BR.min.js
@@ -0,0 +1,22 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.4.3
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2017 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2017-11-15 1:19:45 PM EST
+ */
+!(function ($) {
+/*!
+ * Brazilian portuguese translation for the "pt-BR" and "pt" language codes.
+ * Luan Fonseca <luanfonceca@gmail.com>
+ */
+$.fn.ajaxSelectPicker.locale["pt-BR"]={currentlySelected:"Selecionado Atualmente",emptyTitle:"Clique e comece a digitar",errorText:"Incapaz de encontrar resultados",searchPlaceholder:"Buscar...",statusInitialized:"Comece a digitar",statusNoResults:"Sem Resultados",statusSearching:"Buscando...",statusTooShort:"Digite mais caracteres"},$.fn.ajaxSelectPicker.locale.pt=$.fn.ajaxSelectPicker.locale["pt-BR"];})(jQuery);
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ru-RU.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ru-RU.min.js
new file mode 100644
index 00000000..aa6d4d06
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.ru-RU.min.js
@@ -0,0 +1,22 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.4.3
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2017 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2017-11-15 1:19:45 PM EST
+ */
+!(function ($) {
+/*!
+ * Russian translation for the "ru-RU" and "ru" language codes.
+ * Bercut Stray <bercut497@gmail.com>
+ */
+$.fn.ajaxSelectPicker.locale["ru-RU"]={currentlySelected:"Выбрано",emptyTitle:"Выделите и начните печатать",errorText:"Невозможно получить результат",searchPlaceholder:"Искать...",statusInitialized:"Начните печатать запрос для поиска",statusNoResults:"Нет результатов",statusSearching:"Поиск...",statusTooShort:"Введите еще несколько символов"},$.fn.ajaxSelectPicker.locale.ru=$.fn.ajaxSelectPicker.locale["ru-RU"];})(jQuery);
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.tr-TR.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.tr-TR.min.js
new file mode 100644
index 00000000..6af588f4
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/ajax-bootstrap-select/ajax-bootstrap-select.tr-TR.min.js
@@ -0,0 +1,22 @@
+/*!
+ * Ajax Bootstrap Select
+ *
+ * Extends existing [Bootstrap Select] implementations by adding the ability to search via AJAX requests as you type. Originally for CROSCON.
+ *
+ * @version 1.4.3
+ * @author Adam Heim - https://github.com/truckingsim
+ * @link https://github.com/truckingsim/Ajax-Bootstrap-Select
+ * @copyright 2017 Adam Heim
+ * @license Released under the MIT license.
+ *
+ * Contributors:
+ * Mark Carver - https://github.com/markcarver
+ *
+ * Last build: 2017-11-15 1:19:45 PM EST
+ */
+!(function ($) {
+/*!
+ * Turkish translation for the "tr-TR" and "tr" language codes.
+ * Burak Çakırel <burakcakirel@gmail.com>
+ */
+$.fn.ajaxSelectPicker.locale["tr-TR"]={currentlySelected:"Seçili olanlar",emptyTitle:"Seç ve yazmaya başla",errorText:"Sonuçlar alınamadı",searchPlaceholder:"Ara...",statusInitialized:"Arama için yazmaya başla",statusNoResults:"Sonuç yok",statusSearching:"Aranıyor...",statusTooShort:"Lütfen daha fazla karakter girin"},$.fn.ajaxSelectPicker.locale.tr=$.fn.ajaxSelectPicker.locale["tr-TR"];})(jQuery);
diff --git a/kvision-modules/kvision-select/src/main/resources/js/locales/bootstrap-select/bootstrap-select-i18n.min.js b/kvision-modules/kvision-select/src/main/resources/js/locales/bootstrap-select/bootstrap-select-i18n.min.js
new file mode 100644
index 00000000..4428d3c0
--- /dev/null
+++ b/kvision-modules/kvision-select/src/main/resources/js/locales/bootstrap-select/bootstrap-select-i18n.min.js
@@ -0,0 +1 @@
+!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(a){return b(a)}):"object"==typeof module&&module.exports?module.exports=b(require("jquery")):b(a.jQuery)}(this,function(a){!function(a){a.fn.selectpicker.defaults={noneSelectedText:"",noneResultsText:"",countSelectedText:function(a,b){return 1==a?"... ({n})":"... ({n})"},maxOptionsText:function(a,b){return[1==a?"🛇":"🛇",1==b?"🛇":"🛇"]},selectAllText:"++",deselectAllText:"--",multipleSeparator:", "}}(a)}); \ No newline at end of file
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
new file mode 100644
index 00000000..1da1fe1a
--- /dev/null
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package test.pl.treksoft.kvision
+
+import org.w3c.dom.Element
+import pl.treksoft.jquery.jQuery
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.panel.Root
+import kotlin.browser.document
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+interface TestSpec {
+ fun beforeTest()
+
+ fun afterTest()
+
+ fun run(code: () -> Unit) {
+ beforeTest()
+ code()
+ afterTest()
+ }
+}
+
+interface SimpleSpec : TestSpec {
+
+ override fun beforeTest() {
+ }
+
+ override fun afterTest() {
+ }
+
+}
+
+interface DomSpec : TestSpec {
+
+ override fun beforeTest() {
+ val fixture = "<div style=\"display: none\" id=\"pretest\">" +
+ "<div id=\"test\"></div></div>"
+ document.body?.insertAdjacentHTML("afterbegin", fixture)
+ }
+
+ override fun afterTest() {
+ val div = document.getElementById("pretest")
+ div?.let { jQuery(it).remove() }
+ jQuery(`object` = ".modal-backdrop").remove()
+ }
+
+ fun assertEqualsHtml(expected: String?, actual: String?, message: String?) {
+ if (expected != null && actual != null) {
+ val exp = jQuery(html = expected)
+ val act = jQuery(html = actual)
+ val result = exp[0]?.isEqualNode(act[0])
+ if (result == true) {
+ assertTrue(result == true, message)
+ } else {
+ assertEquals(expected, actual, message)
+ }
+ } else {
+ assertEquals(expected, actual, message)
+ }
+ }
+}
+
+interface WSpec : DomSpec {
+
+ fun runW(code: (widget: Widget, element: Element?) -> Unit) {
+ run {
+ val root = Root("test", true)
+ val widget = Widget()
+ widget.id = "test_id"
+ root.add(widget)
+ val element = document.getElementById("test_id")
+ code(widget, element)
+ }
+ }
+
+}
+
+external fun require(name: String): dynamic
diff --git a/kvision-modules/kvision-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
new file mode 100644
index 00000000..30f42e9c
--- /dev/null
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectInputSpec.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.panel.Root
+import pl.treksoft.kvision.form.select.SelectWidthType
+import pl.treksoft.kvision.form.select.SelectInput
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+class SelectInputSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", true)
+ val selectInput = SelectInput(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", true).apply {
+ liveSearch = true
+ placeholder = "Choose ..."
+ selectWidthType = SelectWidthType.FIT
+ emptyOption = true
+ }
+ root.add(selectInput)
+ val element = document.getElementById("test")
+ assertTrue(
+ true == element?.innerHTML?.endsWith("<select class=\"selectpicker\" multiple=\"multiple\" data-live-search=\"true\" title=\"Choose ...\" data-style=\"btn-default\" data-width=\"fit\" tabindex=\"-98\"><option value=\"#kvnull\"></option><option value=\"test1\">Test 1</option><option value=\"test2\">Test 2</option></select></div>"),
+ "Should render correct select input"
+ )
+ }
+ }
+
+} \ No newline at end of file
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
new file mode 100644
index 00000000..bd88f560
--- /dev/null
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptGroupSpec.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.SelectOptGroup
+import pl.treksoft.kvision.form.select.SelectOption
+import pl.treksoft.kvision.panel.Root
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+
+class SelectOptGroupSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", true)
+ val selectOptGroup = SelectOptGroup("Group", listOf("test1" to "Test 1", "test2" to "Test 2"), 2)
+ root.add(selectOptGroup)
+ val element = document.getElementById("test")
+ assertEqualsHtml(
+ "<optgroup label=\"Group\" data-max-options=\"2\"><option value=\"test1\">Test 1</option><option value=\"test2\">Test 2</option></optgroup>",
+ element?.innerHTML,
+ "Should render correct select option group"
+ )
+ selectOptGroup.add(SelectOption("test3", "Test 3"))
+ assertEqualsHtml(
+ "<optgroup label=\"Group\" data-max-options=\"2\"><option value=\"test1\">Test 1</option><option value=\"test2\">Test 2</option><option value=\"test3\">Test 3</option></optgroup>",
+ element?.innerHTML,
+ "Should render correct select option group with added option"
+ )
+ }
+ }
+
+} \ No newline at end of file
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
new file mode 100644
index 00000000..f7e07d42
--- /dev/null
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectOptionSpec.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.SelectOption
+import pl.treksoft.kvision.panel.Root
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+
+class SelectOptionSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", true)
+ val selectOption = SelectOption("testValue", "testLabel")
+ root.add(selectOption)
+ val element = document.getElementById("test")
+ assertEqualsHtml(
+ "<option value=\"testValue\">testLabel</option>",
+ element?.innerHTML,
+ "Should render correct select option"
+ )
+ selectOption.icon = "fa-flag"
+ assertEqualsHtml(
+ "<option value=\"testValue\" data-icon=\"fa fa-flag\">testLabel</option>",
+ element?.innerHTML,
+ "Should render correct select option with icon"
+ )
+ selectOption.divider = true
+ assertEqualsHtml(
+ "<option data-divider=\"true\"></option>",
+ element?.innerHTML,
+ "Should render correct divider option"
+ )
+ }
+ }
+
+} \ No newline at end of file
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
new file mode 100644
index 00000000..eaccd551
--- /dev/null
+++ b/kvision-modules/kvision-select/src/test/kotlin/test/pl/treksoft/kvision/form/select/SelectSpec.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.panel.Root
+import pl.treksoft.kvision.form.select.SelectWidthType
+import pl.treksoft.kvision.form.select.Select
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+class SelectSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", true)
+ val select = Select(listOf("test1" to "Test 1", "test2" to "Test 2"), "test1", null, true, null, "Label").apply {
+ liveSearch = true
+ placeholder = "Choose ..."
+ selectWidthType = SelectWidthType.FIT
+ emptyOption = true
+ }
+ root.add(select)
+ val element = document.getElementById("test")
+ val id = select.input.id
+ assertTrue(
+ true == element?.innerHTML?.startsWith("<div class=\"form-group\"><label class=\"control-label\" for=\"$id\">Label</label>"),
+ "Should render correct select form control"
+ )
+ assertTrue(
+ true == element?.innerHTML?.endsWith("<select class=\"form-control selectpicker\" id=\"$id\" multiple=\"multiple\" data-live-search=\"true\" title=\"Choose ...\" data-style=\"btn-default\" data-width=\"fit\" tabindex=\"-98\"><option value=\"#kvnull\"></option><option value=\"test1\">Test 1</option><option value=\"test2\">Test 2</option></select></div></div>"),
+ "Should render correct select form control"
+ )
+ }
+ }
+
+} \ No newline at end of file
diff --git a/kvision-modules/kvision-server-jooby/build.gradle b/kvision-modules/kvision-server-jooby/build.gradle
new file mode 100644
index 00000000..952e9a16
--- /dev/null
+++ b/kvision-modules/kvision-server-jooby/build.gradle
@@ -0,0 +1,31 @@
+apply plugin: "io.spring.dependency-management"
+apply plugin: 'kotlin-platform-jvm'
+apply plugin: 'kotlinx-serialization'
+
+dependencyManagement {
+ imports {
+ mavenBom "org.jooby:jooby-bom:${joobyVersion}"
+ }
+}
+
+dependencies {
+ expectedBy project(":kvision-modules:kvision-common")
+ compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+ compile "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion"
+ compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
+ compile "org.jooby:jooby-lang-kotlin"
+ compile "org.jooby:jooby-jackson"
+ compile "org.jooby:jooby-pac4j2"
+ compile "com.github.andrewoma.kwery:mapper:${kweryVersion}"
+ compile "com.fasterxml.jackson.module:jackson-module-kotlin:${jacksonModuleKotlinVersion}"
+ testCompile "org.jetbrains.kotlin:kotlin-test:$kotlinVersion"
+ testCompile project(":kvision-modules:kvision-common")
+}
+
+compileKotlin {
+ targetCompatibility = javaVersion
+ sourceCompatibility = javaVersion
+ kotlinOptions {
+ jvmTarget = javaVersion
+ }
+}
diff --git a/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/Jooby.kt b/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/Jooby.kt
new file mode 100644
index 00000000..928892a2
--- /dev/null
+++ b/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/Jooby.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+@file:Suppress("EXPERIMENTAL_FEATURE_WARNING")
+
+package pl.treksoft.kvision.remote
+
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import org.jooby.Kooby
+import org.jooby.Session
+import org.jooby.json.Jackson
+import org.pac4j.core.profile.CommonProfile
+import kotlinx.coroutines.async as coroutinesAsync
+
+/**
+ * A Jooby based server.
+ */
+actual open class JoobyServer(init: JoobyServer.() -> Unit) : Kooby() {
+ init {
+ @Suppress("LeakingThis")
+ assets("/", "index.html")
+ @Suppress("LeakingThis")
+ assets("/**").onMissing(0)
+ val mapper = jacksonObjectMapper()
+ @Suppress("LeakingThis")
+ use(Jackson(mapper))
+ @Suppress("LeakingThis")
+ init.invoke(this)
+ }
+}
+
+/**
+ * A server request.
+ */
+actual typealias Request = org.jooby.Request
+
+/**
+ * A user profile.
+ */
+actual typealias Profile = CommonProfile
+
+/**
+ * A helper extension function for asynchronous request processing.
+ */
+fun <RESP> Request?.async(block: (Request) -> RESP): Deferred<RESP> = this?.let { req ->
+ GlobalScope.coroutinesAsync(Dispatchers.Unconfined) {
+ block(req)
+ }
+} ?: throw IllegalStateException("Request not set!")
+
+/**
+ * A helper extension function for asynchronous request processing with session.
+ */
+fun <RESP> Request?.async(block: (Request, Session) -> RESP): Deferred<RESP> = this?.let { req ->
+ val session = req.session()
+ GlobalScope.coroutinesAsync(Dispatchers.Unconfined) {
+ block(req, session)
+ }
+} ?: throw IllegalStateException("Request not set!")
+
+/**
+ * A helper extension function for asynchronous request processing with session and user profile.
+ */
+fun <RESP> Request?.async(block: (Request, Session, Profile) -> RESP): Deferred<RESP> = this?.let { req ->
+ val session = req.session()
+ val profile = req.require(CommonProfile::class.java)
+ GlobalScope.coroutinesAsync(Dispatchers.Unconfined) {
+ block(req, session, profile)
+ }
+} ?: throw IllegalStateException("Request not set!")
diff --git a/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt b/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt
new file mode 100644
index 00000000..edaa9ba3
--- /dev/null
+++ b/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/remote/ServiceManager.kt
@@ -0,0 +1,338 @@
+/*
+ * 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.remote
+
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.runBlocking
+import org.jooby.Response
+import org.jooby.Status
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * Multiplatform service manager.
+ */
+@Suppress("EXPERIMENTAL_FEATURE_WARNING")
+actual open class ServiceManager<out T> actual constructor(val service: T) {
+
+ companion object {
+ val LOG: Logger = LoggerFactory.getLogger(ServiceManager::class.java.name)
+ }
+
+ protected val routes: MutableList<JoobyServer.() -> Unit> = mutableListOf()
+ val mapper = jacksonObjectMapper()
+ var counter: Int = 0
+
+ /**
+ * Binds a given route with a function of the receiver.
+ * @param function a function of the receiver
+ * @param route a route
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
+ */
+ protected actual inline fun <reified RET> bind(
+ noinline function: T.(Request?) -> Deferred<RET>,
+ route: String?, method: RpcHttpMethod, prefix: String
+ ) {
+ val routeDef = route ?: "route${this::class.simpleName}${counter++}"
+ routes.add({
+ call(method, "$prefix$routeDef") { req, res ->
+ if (service != null) {
+ val jsonRpcRequest = req.body(JsonRpcRequest::class.java)
+ try {
+ val result = runBlocking { function.invoke(service, req).await() }
+ res.send(
+ JsonRpcResponse(
+ id = jsonRpcRequest.id,
+ result = mapper.writeValueAsString(result)
+ )
+ )
+ } catch (e: Exception) {
+ LOG.error(e.message, e)
+ res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = e.message ?: "Error"))
+ }
+ } else {
+ res.status(Status.SERVER_ERROR)
+ }
+ }.invoke(this)
+ })
+ }
+
+ /**
+ * Binds a given route with a function of the receiver.
+ * @param function a function of the receiver
+ * @param route a route
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
+ */
+ protected actual inline fun <reified PAR, reified RET> bind(
+ noinline function: T.(PAR, Request?) -> Deferred<RET>,
+ route: String?, method: RpcHttpMethod, prefix: String
+ ) {
+ val routeDef = route ?: "route${this::class.simpleName}${counter++}"
+ routes.add({
+ call(method, "$prefix$routeDef") { req, res ->
+ if (service != null) {
+ val jsonRpcRequest = req.body(JsonRpcRequest::class.java)
+ if (jsonRpcRequest.params.size == 1) {
+ val param = getParameter<PAR>(jsonRpcRequest.params[0])
+ try {
+ val result = runBlocking { function.invoke(service, param, req).await() }
+ res.send(
+ JsonRpcResponse(
+ id = jsonRpcRequest.id,
+ result = mapper.writeValueAsString(result)
+ )
+ )
+ } catch (e: Exception) {
+ LOG.error(e.message, e)
+ res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = e.message ?: "Error"))
+ }
+ } else {
+ res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = "Invalid parameters"))
+ }
+ } else {
+ res.status(Status.SERVER_ERROR)
+ }
+ }.invoke(this)
+ })
+ }
+
+ /**
+ * Binds a given route with a function of the receiver.
+ * @param function a function of the receiver
+ * @param route a route
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
+ */
+ protected actual inline fun <reified PAR1, reified PAR2, reified RET> bind(
+ noinline function: T.(PAR1, PAR2, Request?) -> Deferred<RET>,
+ route: String?, method: RpcHttpMethod, prefix: String
+ ) {
+ val routeDef = route ?: "route${this::class.simpleName}${counter++}"
+ routes.add({
+ call(method, "$prefix$routeDef") { req, res ->
+ if (service != null) {
+ val jsonRpcRequest = req.body(JsonRpcRequest::class.java)
+ if (jsonRpcRequest.params.size == 2) {
+ val param1 = getParameter<PAR1>(jsonRpcRequest.params[0])
+ val param2 = getParameter<PAR2>(jsonRpcRequest.params[1])
+ try {
+ val result = runBlocking { function.invoke(service, param1, param2, req).await() }
+ res.send(
+ JsonRpcResponse(
+ id = jsonRpcRequest.id,
+ result = mapper.writeValueAsString(result)
+ )
+ )
+ } catch (e: Exception) {
+ LOG.error(e.message, e)
+ res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = e.message ?: "Error"))
+ }
+ } else {
+ res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = "Invalid parameters"))
+ }
+ } else {
+ res.status(Status.SERVER_ERROR)
+ }
+ }.invoke(this)
+ })
+ }
+
+ /**
+ * Binds a given route with a function of the receiver.
+ * @param function a function of the receiver
+ * @param route a route
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
+ */
+ protected actual inline fun <reified PAR1, reified PAR2, reified PAR3, reified RET> bind(
+ noinline function: T.(PAR1, PAR2, PAR3, Request?) -> Deferred<RET>,
+ route: String?, method: RpcHttpMethod, prefix: String
+ ) {
+ val routeDef = route ?: "route${this::class.simpleName}${counter++}"
+ routes.add({
+ call(method, "$prefix$routeDef") { req, res ->
+ if (service != null) {
+ val jsonRpcRequest = req.body(JsonRpcRequest::class.java)
+ if (jsonRpcRequest.params.size == 3) {
+ val param1 = getParameter<PAR1>(jsonRpcRequest.params[0])
+ val param2 = getParameter<PAR2>(jsonRpcRequest.params[1])
+ val param3 = getParameter<PAR3>(jsonRpcRequest.params[2])
+ try {
+ val result = runBlocking { function.invoke(service, param1, param2, param3, req).await() }
+ res.send(
+ JsonRpcResponse(
+ id = jsonRpcRequest.id,
+ result = mapper.writeValueAsString(result)
+ )
+ )
+ } catch (e: Exception) {
+ LOG.error(e.message, e)
+ res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = e.message ?: "Error"))
+ }
+ } else {
+ res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = "Invalid parameters"))
+ }
+ } else {
+ res.status(Status.SERVER_ERROR)
+ }
+ }.invoke(this)
+ })
+ }
+
+ /**
+ * Binds a given route with a function of the receiver.
+ * @param function a function of the receiver
+ * @param route a route
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
+ */
+ protected actual inline fun <reified PAR1, reified PAR2, reified PAR3, reified PAR4, reified RET> bind(
+ noinline function: T.(PAR1, PAR2, PAR3, PAR4, Request?) -> Deferred<RET>,
+ route: String?, method: RpcHttpMethod, prefix: String
+ ) {
+ val routeDef = route ?: "route${this::class.simpleName}${counter++}"
+ routes.add({
+ call(method, "$prefix$routeDef") { req, res ->
+ if (service != null) {
+ val jsonRpcRequest = req.body(JsonRpcRequest::class.java)
+ if (jsonRpcRequest.params.size == 4) {
+ val param1 = getParameter<PAR1>(jsonRpcRequest.params[0])
+ val param2 = getParameter<PAR2>(jsonRpcRequest.params[1])
+ val param3 = getParameter<PAR3>(jsonRpcRequest.params[2])
+ val param4 = getParameter<PAR4>(jsonRpcRequest.params[3])
+ try {
+ val result =
+ runBlocking { function.invoke(service, param1, param2, param3, param4, req).await() }
+ res.send(
+ JsonRpcResponse(
+ id = jsonRpcRequest.id,
+ result = mapper.writeValueAsString(result)
+ )
+ )
+ } catch (e: Exception) {
+ LOG.error(e.message, e)
+ res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = e.message ?: "Error"))
+ }
+ } else {
+ res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = "Invalid parameters"))
+ }
+ } else {
+ res.status(Status.SERVER_ERROR)
+ }
+ }.invoke(this)
+ })
+ }
+
+ /**
+ * Binds a given route with a function of the receiver.
+ * @param function a function of the receiver
+ * @param route a route
+ * @param method a HTTP method
+ * @param prefix an URL address prefix
+ */
+ protected actual inline fun <reified PAR1, reified PAR2, reified PAR3,
+ reified PAR4, reified PAR5, reified RET> bind(
+ noinline function: T.(PAR1, PAR2, PAR3, PAR4, PAR5, Request?) -> Deferred<RET>,
+ route: String?,
+ method: RpcHttpMethod,
+ prefix: String
+ ) {
+ val routeDef = route ?: "route${this::class.simpleName}${counter++}"
+ routes.add({
+ call(method, "$prefix$routeDef") { req, res ->
+ if (service != null) {
+ val jsonRpcRequest = req.body(JsonRpcRequest::class.java)
+ if (jsonRpcRequest.params.size == 5) {
+ val param1 = getParameter<PAR1>(jsonRpcRequest.params[0])
+ val param2 = getParameter<PAR2>(jsonRpcRequest.params[1])
+ val param3 = getParameter<PAR3>(jsonRpcRequest.params[2])
+ val param4 = getParameter<PAR4>(jsonRpcRequest.params[3])
+ val param5 = getParameter<PAR5>(jsonRpcRequest.params[4])
+ try {
+ val result =
+ runBlocking {
+ function.invoke(service, param1, param2, param3, param4, param5, req).await()
+ }
+ res.send(
+ JsonRpcResponse(
+ id = jsonRpcRequest.id,
+ result = mapper.writeValueAsString(result)
+ )
+ )
+ } catch (e: Exception) {
+ LOG.error(e.message, e)
+ res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = e.message ?: "Error"))
+ }
+ } else {
+ res.send(JsonRpcResponse(id = jsonRpcRequest.id, error = "Invalid parameters"))
+ }
+ } else {
+ res.status(Status.SERVER_ERROR)
+ }
+ }.invoke(this)
+ })
+ }
+
+ fun call(
+ method: RpcHttpMethod,
+ path: String,
+ handler: (Request, Response) -> Unit
+ ): JoobyServer.() -> Unit {
+ return {
+ when (method) {
+ RpcHttpMethod.POST -> post(path, handler)
+ RpcHttpMethod.PUT -> put(path, handler)
+ RpcHttpMethod.DELETE -> delete(path, handler)
+ RpcHttpMethod.OPTIONS -> options(path, handler)
+ }
+ }
+ }
+
+ protected inline fun <reified T> getParameter(str: String?): T {
+ return str?.let {
+ if (T::class == String::class) {
+ str as T
+ } else {
+ mapper.readValue(str, T::class.java)
+ }
+ } ?: null as T
+ }
+
+ /**
+ * Applies all defined routes to the given server.
+ * @param k a Jooby server
+ */
+ actual fun applyRoutes(k: JoobyServer) {
+ routes.forEach {
+ it.invoke(k)
+ }
+ }
+
+ /**
+ * Returns the list of defined bindings.
+ * Not used on the jvm platform.
+ */
+ actual fun getCalls(): Map<String, Pair<String, RpcHttpMethod>> = mapOf()
+}
diff --git a/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/types/KDate.kt b/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/types/KDate.kt
new file mode 100644
index 00000000..9fc534c4
--- /dev/null
+++ b/kvision-modules/kvision-server-jooby/src/main/kotlin/pl/treksoft/kvision/types/KDate.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.types
+
+import com.github.andrewoma.kwery.mapper.SimpleConverter
+import com.github.andrewoma.kwery.mapper.TableConfiguration
+import com.github.andrewoma.kwery.mapper.reifiedConverter
+import com.github.andrewoma.kwery.mapper.standardConverters
+import com.github.andrewoma.kwery.mapper.util.camelToLowerUnderscore
+import java.sql.Timestamp
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * A serializable wrapper for a multiplatform Date type.
+ */
+@Suppress("MayBeConstant")
+actual val KDATE_FORMAT = "yyyy-MM-dd HH:mm:ss"
+
+actual fun nowDate(): KDate =
+ KDate(Date().time)
+
+actual fun String.toKDateF(format: String): KDate =
+ KDate(SimpleDateFormat(format).parse(this).time)
+
+actual fun KDate.toStringF(format: String) =
+ SimpleDateFormat(format).format(this.toJava())
+
+fun KDate.toJava(): java.util.Date = java.util.Date(this.time)
+
+object KDateConverter : SimpleConverter<KDate>(
+ { row, c -> KDate(row.timestamp(c).time) },
+ { Timestamp(it.time) }
+)
+
+val kvTableConfig = TableConfiguration(
+ converters = standardConverters + reifiedConverter(KDateConverter),
+ namingConvention = camelToLowerUnderscore
+)
diff --git a/kvision-modules/kvision-spinner/build.gradle b/kvision-modules/kvision-spinner/build.gradle
new file mode 100644
index 00000000..ce88c117
--- /dev/null
+++ b/kvision-modules/kvision-spinner/build.gradle
@@ -0,0 +1,9 @@
+apply from: "../shared.gradle"
+
+kotlinFrontend {
+
+ npm {
+ dependency("bootstrap-touchspin", "3.1.1")
+ }
+
+}
diff --git a/kvision-modules/kvision-spinner/package.json.d/project.info b/kvision-modules/kvision-spinner/package.json.d/project.info
new file mode 100644
index 00000000..fb0c7956
--- /dev/null
+++ b/kvision-modules/kvision-spinner/package.json.d/project.info
@@ -0,0 +1,3 @@
+{
+ "description": "KVision Spinner module"
+}
diff --git a/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/KVManagerSpinner.kt b/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/KVManagerSpinner.kt
new file mode 100644
index 00000000..0de06c8c
--- /dev/null
+++ b/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/KVManagerSpinner.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision
+
+internal val KVManagerSpinnerInit = KVManagerSpinner.init()
+
+/**
+ * Internal singleton object which initializes and configures KVision spinner module.
+ */
+@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught")
+internal object KVManagerSpinner {
+ fun init() {}
+
+ private val bootstrapTouchspinCss = try {
+ require("bootstrap-touchspin/dist/jquery.bootstrap-touchspin.min.css")
+ } catch (e: Throwable) {
+ }
+ private val bootstrapTouchspin = try {
+ require("bootstrap-touchspin/dist/jquery.bootstrap-touchspin.min.js")
+ } catch (e: Throwable) {
+ }
+
+}
diff --git a/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt b/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt
new file mode 100644
index 00000000..4fa68e47
--- /dev/null
+++ b/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/Spinner.kt
@@ -0,0 +1,263 @@
+/*
+ * 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.spinner
+
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.StringBoolPair
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.form.FieldLabel
+import pl.treksoft.kvision.form.HelpBlock
+import pl.treksoft.kvision.form.NumberFormControl
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.utils.SnOn
+
+/**
+ * The form field component for spinner control.
+ *
+ * @constructor
+ * @param value spinner value
+ * @param name the name attribute of the generated HTML input element
+ * @param min minimal value (default 0)
+ * @param max maximal value (default 100)
+ * @param step step value (default 1)
+ * @param decimals number of decimal digits (default 0)
+ * @param buttonsType spinner buttons type
+ * @param forceType spinner force rounding type
+ * @param label label text bound to the input element
+ * @param rich determines if [label] can contain HTML code
+ */
+open class Spinner(
+ value: Number? = null, name: String? = null, min: Int = 0, max: Int = DEFAULT_MAX, step: Double = DEFAULT_STEP,
+ decimals: Int = 0, buttonsType: ButtonsType = ButtonsType.VERTICAL,
+ forceType: ForceType = ForceType.NONE, label: String? = null,
+ rich: Boolean = false
+) : SimplePanel(setOf("form-group")), NumberFormControl {
+
+ /**
+ * Spinner value.
+ */
+ override var value
+ get() = input.value
+ set(value) {
+ input.value = value
+ }
+ /**
+ * The value attribute of the generated HTML input element.
+ *
+ * This value is placed directly in generated HTML code, while the [value] property is dynamically
+ * bound to the spinner input value.
+ */
+ var startValue
+ get() = input.startValue
+ set(value) {
+ input.startValue = value
+ }
+ /**
+ * Minimal value.
+ */
+ var min
+ get() = input.min
+ set(value) {
+ input.min = value
+ }
+ /**
+ * Maximal value.
+ */
+ var max
+ get() = input.max
+ set(value) {
+ input.max = value
+ }
+ /**
+ * Step value.
+ */
+ var step
+ get() = input.step
+ set(value) {
+ input.step = value
+ }
+ /**
+ * Number of decimal digits value.
+ */
+ var decimals
+ get() = input.decimals
+ set(value) {
+ input.decimals = value
+ }
+ /**
+ * Spinner buttons type.
+ */
+ var buttonsType
+ get() = input.buttonsType
+ set(value) {
+ input.buttonsType = value
+ }
+ /**
+ * Spinner force rounding type.
+ */
+ var forceType
+ get() = input.forceType
+ set(value) {
+ input.forceType = value
+ }
+ /**
+ * The placeholder for the spinner input.
+ */
+ var placeholder
+ get() = input.placeholder
+ set(value) {
+ input.placeholder = value
+ }
+ /**
+ * Determines if the spinner is automatically focused.
+ */
+ var autofocus
+ get() = input.autofocus
+ set(value) {
+ input.autofocus = value
+ }
+ /**
+ * Determines if the spinner is read-only.
+ */
+ var readonly
+ get() = input.readonly
+ set(value) {
+ input.readonly = value
+ }
+ /**
+ * The label text bound to the spinner input 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
+ }
+
+ protected val idc = "kv_form_spinner_$counter"
+ final override val input: SpinnerInput = SpinnerInput(value, min, max, step, decimals, buttonsType, forceType)
+ .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 getValueAsString(): String? {
+ return input.getValueAsString()
+ }
+
+ /**
+ * Change value in plus.
+ */
+ open fun spinUp(): Spinner {
+ input.spinUp()
+ return this
+ }
+
+ /**
+ * Change value in minus.
+ */
+ open fun spinDown(): Spinner {
+ input.spinDown()
+ return this
+ }
+
+ 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.spinner(
+ value: Number? = null,
+ name: String? = null,
+ min: Int = 0,
+ max: Int = DEFAULT_MAX,
+ step: Double = DEFAULT_STEP,
+ decimals: Int = 0,
+ buttonsType: ButtonsType = ButtonsType.VERTICAL,
+ forceType: ForceType = ForceType.NONE,
+ label: String? = null,
+ rich: Boolean = false,
+ init: (Spinner.() -> Unit)? = null
+ ): Spinner {
+ val spinner = Spinner(value, name, min, max, step, decimals, buttonsType, forceType, label, rich).apply {
+ init?.invoke(
+ this
+ )
+ }
+ this.add(spinner)
+ return spinner
+ }
+ }
+}
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
new file mode 100644
index 00000000..7d3af684
--- /dev/null
+++ b/kvision-modules/kvision-spinner/src/main/kotlin/pl/treksoft/kvision/form/spinner/SpinnerInput.kt
@@ -0,0 +1,323 @@
+/*
+ * 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.spinner
+
+import com.github.snabbdom.VNode
+import pl.treksoft.jquery.JQuery
+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.FormInput
+import pl.treksoft.kvision.form.InputSize
+import pl.treksoft.kvision.utils.obj
+
+/**
+ * Spinner buttons layout types.
+ */
+enum class ButtonsType {
+ NONE,
+ HORIZONTAL,
+ VERTICAL
+}
+
+/**
+ * Spinner force rounding types.
+ */
+enum class ForceType(internal val value: String) {
+ NONE("none"),
+ ROUND("round"),
+ FLOOR("floor"),
+ CEIL("cail")
+}
+
+internal const val DEFAULT_STEP = 1.0
+internal const val DEFAULT_MAX = 100
+
+/**
+ * The basic component for spinner control.
+ *
+ * @constructor
+ * @param value spinner value
+ * @param min minimal value (default 0)
+ * @param max maximal value (default 100)
+ * @param step step value (default 1)
+ * @param decimals number of decimal digits (default 0)
+ * @param buttonsType spinner buttons type
+ * @param forceType spinner force rounding type
+ * @param classes a set of CSS class names
+ */
+@Suppress("TooManyFunctions")
+open class SpinnerInput(
+ value: Number? = null, min: Int = 0, max: Int = DEFAULT_MAX, step: Double = DEFAULT_STEP,
+ decimals: Int = 0, buttonsType: ButtonsType = ButtonsType.VERTICAL,
+ forceType: ForceType = ForceType.NONE,
+ classes: Set<String> = setOf()
+) : Widget(classes + "form-control"), FormInput {
+
+ init {
+ this.addSurroundingCssClass("input-group")
+ if (buttonsType == ButtonsType.NONE) {
+ this.addSurroundingCssClass("kv-spinner-btn-none")
+ } else {
+ this.removeSurroundingCssClass("kv-spinner-btn-none")
+ }
+ if (buttonsType == ButtonsType.VERTICAL) {
+ this.addSurroundingCssClass("kv-spinner-btn-vertical")
+ } else {
+ this.removeSurroundingCssClass("kv-spinner-btn-vertical")
+ }
+ this.surroundingSpan = true
+ this.refreshSpinner()
+ this.setInternalEventListener<SpinnerInput> {
+ change = {
+ self.changeValue()
+ }
+ }
+ }
+
+ /**
+ * Spinner value.
+ */
+ var value by refreshOnUpdate(value, { refreshState() })
+ /**
+ * The value attribute of the generated HTML input element.
+ *
+ * This value is placed directly in generated HTML code, while the [value] property is dynamically
+ * bound to the spinner input value.
+ */
+ var startValue by refreshOnUpdate(value, { this.value = it; refresh() })
+ /**
+ * Minimal value.
+ */
+ var min by refreshOnUpdate(min, { refreshSpinner() })
+ /**
+ * Maximal value.
+ */
+ var max by refreshOnUpdate(max, { refreshSpinner() })
+ /**
+ * Step value.
+ */
+ var step by refreshOnUpdate(step, { refreshSpinner() })
+ /**
+ * Number of decimal digits value.
+ */
+ var decimals by refreshOnUpdate(decimals, { refreshSpinner() })
+ /**
+ * Spinner buttons type.
+ */
+ var buttonsType by refreshOnUpdate(buttonsType, { refreshSpinner() })
+ /**
+ * Spinner force rounding type.
+ */
+ var forceType by refreshOnUpdate(forceType, { refreshSpinner() })
+ /**
+ * The placeholder for the spinner input.
+ */
+ var placeholder: String? by refreshOnUpdate()
+ /**
+ * 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 spinner is automatically focused.
+ */
+ var autofocus: Boolean? by refreshOnUpdate()
+ /**
+ * Determines if the spinner is read-only.
+ */
+ var readonly: Boolean? by refreshOnUpdate()
+ /**
+ * The size of the input.
+ */
+ override var size: InputSize? by refreshOnUpdate()
+
+ private var siblings: JQuery? = null
+
+ override fun render(): VNode {
+ return render("input")
+ }
+
+ override fun getSnClass(): List<StringBoolPair> {
+ val cl = super.getSnClass().toMutableList()
+ size?.let {
+ cl.add(it.className to true)
+ }
+ return cl
+ }
+
+ @Suppress("ComplexMethod")
+ override fun getSnAttrs(): List<StringPair> {
+ val sn = super.getSnAttrs().toMutableList()
+ sn.add("type" to "text")
+ startValue?.let {
+ sn.add("value" to it.toString())
+ }
+ placeholder?.let {
+ sn.add("placeholder" to translate(it))
+ }
+ name?.let {
+ sn.add("name" to it)
+ }
+ autofocus?.let {
+ if (it) {
+ sn.add("autofocus" to "autofocus")
+ }
+ }
+ readonly?.let {
+ if (it) {
+ sn.add("readonly" to "readonly")
+ }
+ }
+ if (disabled) {
+ sn.add("disabled" to "disabled")
+ value?.let {
+ sn.add("value" to it.toString())
+ }
+ }
+ return sn
+ }
+
+ protected open fun changeValue() {
+ val v = getElementJQuery()?.`val`() as String?
+ if (v != null && v.isNotEmpty()) {
+ this.value = v.toDoubleOrNull()
+ } else {
+ this.value = null
+ }
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ override fun afterInsert(node: VNode) {
+ getElementJQueryD()?.TouchSpin(getSettingsObj())
+ siblings = getElementJQuery()?.parent(".bootstrap-touchspin")?.children("span")
+ size?.let {
+ siblings?.find("button")?.addClass(it.className)
+ }
+ this.getElementJQuery()?.on("change", { e, _ ->
+ if (e.asDynamic().isTrigger != null) {
+ val event = org.w3c.dom.events.Event("change")
+ this.getElement()?.dispatchEvent(event)
+ }
+ })
+ this.getElementJQuery()?.on("touchspin.on.min", { e, _ ->
+ this.dispatchEvent("onMinBsSpinner", obj { detail = e })
+ })
+ this.getElementJQuery()?.on("touchspin.on.max", { e, _ ->
+ this.dispatchEvent("onMaxBsSpinner", obj { detail = e })
+ })
+ refreshState()
+ }
+
+ override fun afterDestroy() {
+ siblings?.remove()
+ siblings = null
+ }
+
+ /**
+ * Returns the value of the spinner as a String.
+ * @return value as a String
+ */
+ fun getValueAsString(): String? {
+ return value?.toString()
+ }
+
+ /**
+ * Change value in plus.
+ */
+ fun spinUp(): SpinnerInput {
+ getElementJQueryD()?.trigger("touchspin.uponce")
+ return this
+ }
+
+ /**
+ * Change value in minus.
+ */
+ fun spinDown(): SpinnerInput {
+ getElementJQueryD()?.trigger("touchspin.downonce")
+ return this
+ }
+
+ private fun refreshState() {
+ value?.let {
+ getElementJQuery()?.`val`(it.toString())
+ } ?: getElementJQueryD()?.`val`(null)
+ }
+
+ private fun refreshSpinner() {
+ getElementJQueryD()?.trigger("touchspin.updatesettings", getSettingsObj())
+ }
+
+ private fun getSettingsObj(): dynamic {
+ val verticalbuttons = buttonsType == ButtonsType.VERTICAL || buttonsType == ButtonsType.NONE
+ return obj {
+ this.min = min
+ this.max = max
+ this.step = step
+ this.decimals = decimals
+ this.verticalbuttons = verticalbuttons
+ this.forcestepdivisibility = forceType.value
+ }
+ }
+
+ /**
+ * Makes the input element focused.
+ */
+ open fun focus() {
+ getElementJQuery()?.focus()
+ }
+
+ /**
+ * Makes the input element blur.
+ */
+ open 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.spinnerInput(
+ value: Number? = null, min: Int = 0, max: Int = DEFAULT_MAX, step: Double = DEFAULT_STEP,
+ decimals: Int = 0, buttonsType: ButtonsType = ButtonsType.VERTICAL,
+ forceType: ForceType = ForceType.NONE, classes: Set<String> = setOf(),
+ init: (SpinnerInput.() -> Unit)? = null
+ ): SpinnerInput {
+ val spinnerInput = SpinnerInput(value, min, max, step, decimals, buttonsType, forceType, classes).apply {
+ init?.invoke(
+ this
+ )
+ }
+ this.add(spinnerInput)
+ return spinnerInput
+ }
+ }
+}
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
new file mode 100644
index 00000000..1da1fe1a
--- /dev/null
+++ b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package test.pl.treksoft.kvision
+
+import org.w3c.dom.Element
+import pl.treksoft.jquery.jQuery
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.panel.Root
+import kotlin.browser.document
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+interface TestSpec {
+ fun beforeTest()
+
+ fun afterTest()
+
+ fun run(code: () -> Unit) {
+ beforeTest()
+ code()
+ afterTest()
+ }
+}
+
+interface SimpleSpec : TestSpec {
+
+ override fun beforeTest() {
+ }
+
+ override fun afterTest() {
+ }
+
+}
+
+interface DomSpec : TestSpec {
+
+ override fun beforeTest() {
+ val fixture = "<div style=\"display: none\" id=\"pretest\">" +
+ "<div id=\"test\"></div></div>"
+ document.body?.insertAdjacentHTML("afterbegin", fixture)
+ }
+
+ override fun afterTest() {
+ val div = document.getElementById("pretest")
+ div?.let { jQuery(it).remove() }
+ jQuery(`object` = ".modal-backdrop").remove()
+ }
+
+ fun assertEqualsHtml(expected: String?, actual: String?, message: String?) {
+ if (expected != null && actual != null) {
+ val exp = jQuery(html = expected)
+ val act = jQuery(html = actual)
+ val result = exp[0]?.isEqualNode(act[0])
+ if (result == true) {
+ assertTrue(result == true, message)
+ } else {
+ assertEquals(expected, actual, message)
+ }
+ } else {
+ assertEquals(expected, actual, message)
+ }
+ }
+}
+
+interface WSpec : DomSpec {
+
+ fun runW(code: (widget: Widget, element: Element?) -> Unit) {
+ run {
+ val root = Root("test", true)
+ val widget = Widget()
+ widget.id = "test_id"
+ root.add(widget)
+ val element = document.getElementById("test_id")
+ code(widget, element)
+ }
+ }
+
+}
+
+external fun require(name: String): dynamic
diff --git a/kvision-modules/kvision-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
new file mode 100644
index 00000000..a240bfd8
--- /dev/null
+++ b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerInputSpec.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.spinner
+
+import pl.treksoft.kvision.panel.Root
+import pl.treksoft.kvision.form.spinner.SpinnerInput
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class SpinnerInputSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", true)
+ val si = SpinnerInput(value = 13).apply {
+ placeholder = "place"
+ id = "idti"
+ }
+ root.add(si)
+ val value = si.getElementJQuery()?.`val`()
+ assertEquals("13", value, "Should render spinner input with correct value")
+ }
+ }
+
+ @Test
+ fun spinUp() {
+ run {
+ val root = Root("test", true)
+ val si = SpinnerInput(value = 13).apply {
+ placeholder = "place"
+ id = "idti"
+ }
+ root.add(si)
+ assertEquals(13, si.value, "Should return initial value before spinUp")
+ si.spinUp()
+ assertEquals(14, si.value, "Should return changed value after spinUp")
+ }
+ }
+
+ @Test
+ fun spinDown() {
+ run {
+ val root = Root("test", true)
+ val si = SpinnerInput(value = 13).apply {
+ placeholder = "place"
+ id = "idti"
+ }
+ root.add(si)
+ assertEquals(13, si.value, "Should return initial value before spinDown")
+ si.spinDown()
+ assertEquals(12, si.value, "Should return changed value after spinDown")
+ }
+ }
+} \ No newline at end of file
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
new file mode 100644
index 00000000..e2eec3f9
--- /dev/null
+++ b/kvision-modules/kvision-spinner/src/test/kotlin/test/pl/treksoft/kvision/form/spinner/SpinnerSpec.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.spinner
+
+import pl.treksoft.kvision.form.spinner.Spinner
+import pl.treksoft.kvision.panel.Root
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class SpinnerSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", true)
+ val ti = Spinner(value = 13, label = "Label").apply {
+ placeholder = "place"
+ name = "name"
+ disabled = true
+ }
+ root.add(ti)
+ val element = document.getElementById("test")
+ val id = ti.input.id
+ assertEqualsHtml(
+ "<div class=\"form-group\"><label class=\"control-label\" for=\"$id\">Label</label><div class=\"input-group kv-spinner-btn-vertical\"><span><div class=\"input-group bootstrap-touchspin\"><span class=\"input-group-addon bootstrap-touchspin-prefix\" style=\"display: none;\"></span><input class=\"form-control\" id=\"$id\" type=\"text\" value=\"13\" placeholder=\"place\" name=\"name\" disabled=\"disabled\" style=\"display: block;\"><span class=\"input-group-addon bootstrap-touchspin-postfix\" style=\"display: none;\"></span><span class=\"input-group-btn-vertical\"><button class=\"btn btn-default bootstrap-touchspin-up\" type=\"button\"><i class=\"glyphicon glyphicon-chevron-up\"></i></button><button class=\"btn btn-default bootstrap-touchspin-down\" type=\"button\"><i class=\"glyphicon glyphicon-chevron-down\"></i></button></span></div></span></div></div>",
+ element?.innerHTML,
+ "Should render correct spinner input form control"
+ )
+ ti.validatorError = "Validation Error"
+ assertEqualsHtml(
+ "<div class=\"form-group has-error\"><label class=\"control-label\" for=\"$id\">Label</label><div class=\"input-group kv-spinner-btn-vertical\"><span><div class=\"input-group bootstrap-touchspin\"><span class=\"input-group-addon bootstrap-touchspin-prefix\" style=\"display: none;\"></span><input class=\"form-control\" id=\"$id\" type=\"text\" value=\"13\" placeholder=\"place\" name=\"name\" disabled=\"disabled\" style=\"display: block;\"><span class=\"input-group-addon bootstrap-touchspin-postfix\" style=\"display: none;\"></span><span class=\"input-group-btn-vertical\"><button class=\"btn btn-default bootstrap-touchspin-up\" type=\"button\"><i class=\"glyphicon glyphicon-chevron-up\"></i></button><button class=\"btn btn-default bootstrap-touchspin-down\" type=\"button\"><i class=\"glyphicon glyphicon-chevron-down\"></i></button></span></div></span></div><span class=\"help-block small\">Validation Error</span></div>",
+ element?.innerHTML,
+ "Should render correct spinner input form control with validation error"
+ )
+ }
+ }
+
+ @Test
+ fun spinUp() {
+ run {
+ val root = Root("test", true)
+ val si = Spinner(value = 13)
+ root.add(si)
+ assertEquals(13, si.value, "Should return initial value before spinUp")
+ si.spinUp()
+ assertEquals(14, si.value, "Should return changed value after spinUp")
+ }
+ }
+
+ @Test
+ fun spinDown() {
+ run {
+ val root = Root("test", true)
+ val si = Spinner(value = 13)
+ root.add(si)
+ assertEquals(13, si.value, "Should return initial value before spinDown")
+ si.spinDown()
+ assertEquals(12, si.value, "Should return changed value after spinDown")
+ }
+ }
+} \ No newline at end of file
diff --git a/kvision-modules/kvision-upload/build.gradle b/kvision-modules/kvision-upload/build.gradle
new file mode 100644
index 00000000..e45b9a3b
--- /dev/null
+++ b/kvision-modules/kvision-upload/build.gradle
@@ -0,0 +1,9 @@
+apply from: "../shared.gradle"
+
+kotlinFrontend {
+
+ npm {
+ dependency("bootstrap-fileinput", "4.4.7")
+ }
+
+}
diff --git a/kvision-modules/kvision-upload/package.json.d/project.info b/kvision-modules/kvision-upload/package.json.d/project.info
new file mode 100644
index 00000000..d789d81b
--- /dev/null
+++ b/kvision-modules/kvision-upload/package.json.d/project.info
@@ -0,0 +1,3 @@
+{
+ "description": "KVision Upload module"
+}
diff --git a/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/KVManagerUpload.kt b/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/KVManagerUpload.kt
new file mode 100644
index 00000000..64a25545
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/KVManagerUpload.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision
+
+internal val KVManagerUploadInit = KVManagerUpload.init()
+
+/**
+ * Internal singleton object which initializes and configures KVision upload module.
+ */
+@Suppress("EmptyCatchBlock", "TooGenericExceptionCaught")
+internal object KVManagerUpload {
+ fun init() {}
+
+ private val bootstrapFileinputCss = try {
+ require("bootstrap-fileinput/css/fileinput.min.css")
+ } catch (e: Throwable) {
+ }
+ private val bootstrapFileinputCssFa = try {
+ require("bootstrap-fileinput/themes/explorer-fa/theme.min.css")
+ } catch (e: Throwable) {
+ }
+ private val bootstrapFileinput = try {
+ require("bootstrap-fileinput")
+ require("./js/locales/bootstrap-fileinput/ar.js")
+ require("./js/locales/bootstrap-fileinput/az.js")
+ require("./js/locales/bootstrap-fileinput/bg.js")
+ require("./js/locales/bootstrap-fileinput/ca.js")
+ require("./js/locales/bootstrap-fileinput/cr.js")
+ require("./js/locales/bootstrap-fileinput/cs.js")
+ require("./js/locales/bootstrap-fileinput/da.js")
+ require("./js/locales/bootstrap-fileinput/de.js")
+ require("./js/locales/bootstrap-fileinput/el.js")
+ require("./js/locales/bootstrap-fileinput/es.js")
+ require("./js/locales/bootstrap-fileinput/et.js")
+ require("./js/locales/bootstrap-fileinput/fa.js")
+ require("./js/locales/bootstrap-fileinput/fi.js")
+ require("./js/locales/bootstrap-fileinput/fr.js")
+ require("./js/locales/bootstrap-fileinput/gl.js")
+ require("./js/locales/bootstrap-fileinput/id.js")
+ require("./js/locales/bootstrap-fileinput/it.js")
+ require("./js/locales/bootstrap-fileinput/ja.js")
+ require("./js/locales/bootstrap-fileinput/ka.js")
+ require("./js/locales/bootstrap-fileinput/ko.js")
+ require("./js/locales/bootstrap-fileinput/kz.js")
+ require("./js/locales/bootstrap-fileinput/lt.js")
+ require("./js/locales/bootstrap-fileinput/nl.js")
+ require("./js/locales/bootstrap-fileinput/no.js")
+ require("./js/locales/bootstrap-fileinput/pl.js")
+ require("./js/locales/bootstrap-fileinput/pt.js")
+ require("./js/locales/bootstrap-fileinput/ro.js")
+ require("./js/locales/bootstrap-fileinput/ru.js")
+ require("./js/locales/bootstrap-fileinput/sk.js")
+ require("./js/locales/bootstrap-fileinput/sl.js")
+ require("./js/locales/bootstrap-fileinput/sv.js")
+ require("./js/locales/bootstrap-fileinput/th.js")
+ require("./js/locales/bootstrap-fileinput/tr.js")
+ require("./js/locales/bootstrap-fileinput/uk.js")
+ require("./js/locales/bootstrap-fileinput/vi.js")
+ require("./js/locales/bootstrap-fileinput/zh.js")
+ } catch (e: Throwable) {
+ }
+ private val bootstrapFileinputFa = try {
+ require("bootstrap-fileinput/themes/explorer-fa/theme.min.js")
+ } catch (e: Throwable) {
+ }
+
+}
diff --git a/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/Upload.kt b/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/Upload.kt
new file mode 100644
index 00000000..314c9904
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/Upload.kt
@@ -0,0 +1,333 @@
+/*
+ * 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.upload
+
+import org.w3c.files.File
+import pl.treksoft.kvision.core.Container
+import pl.treksoft.kvision.core.StringBoolPair
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.form.FieldLabel
+import pl.treksoft.kvision.form.HelpBlock
+import pl.treksoft.kvision.form.KFilesFormControl
+import pl.treksoft.kvision.panel.SimplePanel
+import pl.treksoft.kvision.types.KFile
+import pl.treksoft.kvision.utils.SnOn
+
+/**
+ * The form field file upload component.
+ *
+ * @constructor
+ * @param uploadUrl the optional URL for the upload processing action
+ * @param multiple determines if multiple file upload is supported
+ * @param label label text bound to the input element
+ * @param rich determines if [label] can contain HTML code
+ */
+@Suppress("TooManyFunctions")
+open class Upload(
+ uploadUrl: String? = null, multiple: Boolean = false, label: String? = null,
+ rich: Boolean = false
+) : SimplePanel(setOf("form-group")), KFilesFormControl {
+
+ /**
+ * File input value.
+ */
+ override var value
+ get() = input.value
+ set(value) {
+ input.value = value
+ }
+ /**
+ * The optional URL for the upload processing action.
+ * If not set the upload button action will default to form submission.
+ */
+ var uploadUrl
+ get() = input.uploadUrl
+ set(value) {
+ input.uploadUrl = value
+ }
+ /**
+ * Determines if multiple file upload is supported.
+ */
+ var multiple
+ get() = input.multiple
+ set(value) {
+ input.multiple = value
+ }
+ /**
+ * The extra data that will be passed as data to the AJAX server call via POST.
+ */
+ var uploadExtraData
+ get() = input.uploadExtraData
+ set(value) {
+ input.uploadExtraData = value
+ }
+ /**
+ * Determines if the explorer theme is used.
+ */
+ var explorerTheme
+ get() = input.explorerTheme
+ set(value) {
+ input.explorerTheme = value
+ }
+ /**
+ * Determines if the input selection is required.
+ */
+ var required
+ get() = input.required
+ set(value) {
+ input.required = value
+ }
+ /**
+ * Determines if the caption is shown.
+ */
+ var showCaption
+ get() = input.showCaption
+ set(value) {
+ input.showCaption = value
+ }
+ /**
+ * Determines if the preview is shown.
+ */
+ var showPreview
+ get() = input.showPreview
+ set(value) {
+ input.showPreview = value
+ }
+ /**
+ * Determines if the remove button is shown.
+ */
+ var showRemove
+ get() = input.showRemove
+ set(value) {
+ input.showRemove = value
+ }
+ /**
+ * Determines if the upload button is shown.
+ */
+ var showUpload
+ get() = input.showUpload
+ set(value) {
+ input.showUpload = value
+ }
+ /**
+ * Determines if the cancel button is shown.
+ */
+ var showCancel
+ get() = input.showCancel
+ set(value) {
+ input.showCancel = value
+ }
+ /**
+ * Determines if the file browse button is shown.
+ */
+ var showBrowse
+ get() = input.showBrowse
+ set(value) {
+ input.showBrowse = value
+ }
+ /**
+ * Determines if the click on the preview zone opens file browse window.
+ */
+ var browseOnZoneClick
+ get() = input.browseOnZoneClick
+ set(value) {
+ input.browseOnZoneClick = value
+ }
+ /**
+ * Determines if the iconic preview is prefered.
+ */
+ var preferIconicPreview
+ get() = input.preferIconicPreview
+ set(value) {
+ input.preferIconicPreview = value
+ }
+ /**
+ * Allowed file types.
+ */
+ var allowedFileTypes
+ get() = input.allowedFileTypes
+ set(value) {
+ input.allowedFileTypes = value
+ }
+ /**
+ * Allowed file extensions.
+ */
+ var allowedFileExtensions
+ get() = input.allowedFileExtensions
+ set(value) {
+ input.allowedFileExtensions = value
+ }
+ /**
+ * Determines if Drag&Drop zone is enabled.
+ */
+ var dropZoneEnabled
+ get() = input.dropZoneEnabled
+ set(value) {
+ input.dropZoneEnabled = value
+ }
+ /**
+ * The label text bound to the spinner input 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
+ }
+
+ protected val idc = "kv_form_upload_$counter"
+ final override val input: UploadInput = UploadInput(uploadUrl, multiple)
+ .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 getValueAsString(): String? {
+ return input.getValueAsString()
+ }
+
+ /**
+ * Returns the native JavaScript File object.
+ * @param kFile KFile object
+ * @return File object
+ */
+ fun getNativeFile(kFile: KFile): File? {
+ return input.getNativeFile(kFile)
+ }
+
+ /**
+ * Resets the file input control.
+ */
+ open fun resetInput() {
+ input.resetInput()
+ }
+
+ /**
+ * Clears the file input control (including the native input).
+ */
+ open fun clearInput() {
+ input.clearInput()
+ }
+
+ /**
+ * Trigger ajax upload (only for ajax mode).
+ */
+ open fun upload() {
+ input.upload()
+ }
+
+ /**
+ * Cancel an ongoing ajax upload (only for ajax mode).
+ */
+ open fun cancel() {
+ input.cancel()
+ }
+
+ /**
+ * Locks the file input (disabling all buttons except a cancel button).
+ */
+ open fun lock() {
+ input.lock()
+ }
+
+ /**
+ * Unlocks the file input.
+ */
+ open fun unlock() {
+ input.unlock()
+ }
+
+ 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.upload(
+ uploadUrl: String? = null,
+ multiple: Boolean = false,
+ label: String? = null,
+ rich: Boolean = false,
+ init: (Upload.() -> Unit)? = null
+ ): Upload {
+ val upload = Upload(uploadUrl, multiple, label, rich).apply {
+ init?.invoke(
+ this
+ )
+ }
+ this.add(upload)
+ return upload
+ }
+ }
+}
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
new file mode 100644
index 00000000..51b73aa1
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt
@@ -0,0 +1,364 @@
+/*
+ * 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.upload
+
+import com.github.snabbdom.VNode
+import org.w3c.files.File
+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.Form
+import pl.treksoft.kvision.form.FormInput
+import pl.treksoft.kvision.form.FormPanel
+import pl.treksoft.kvision.form.InputSize
+import pl.treksoft.kvision.i18n.I18n
+import pl.treksoft.kvision.types.KFile
+import pl.treksoft.kvision.utils.getContent
+import pl.treksoft.kvision.utils.obj
+import kotlin.reflect.KProperty1
+
+/**
+ * The file upload component.
+ *
+ * @constructor
+ * @param uploadUrl the optional URL for the upload processing action
+ * @param multiple determines if multiple file upload is supported
+ * @param classes a set of CSS class names
+ */
+@Suppress("TooManyFunctions")
+open class UploadInput(uploadUrl: String? = null, multiple: Boolean = false, classes: Set<String> = setOf()) :
+ Widget(classes + "form-control"), FormInput {
+
+ /**
+ * File input value.
+ */
+ var value: List<KFile>?
+ get() = getValue()
+ set(value) {
+ if (value == null) resetInput()
+ }
+
+ /**
+ * The optional URL for the upload processing action.
+ * If not set the upload button action will default to form submission.
+ */
+ var uploadUrl: String? by refreshOnUpdate(uploadUrl, { refreshUploadInput() })
+ /**
+ * Determines if multiple file upload is supported.
+ */
+ var multiple: Boolean by refreshOnUpdate(multiple, { refresh(); refreshUploadInput() })
+ /**
+ * The extra data that will be passed as data to the AJAX server call via POST.
+ */
+ var uploadExtraData: ((String, Int) -> dynamic)? by refreshOnUpdate({ refreshUploadInput() })
+ /**
+ * Determines if the explorer theme is used.
+ */
+ var explorerTheme: Boolean by refreshOnUpdate(false, { refreshUploadInput() })
+ /**
+ * Determines if the input selection is required.
+ */
+ var required: Boolean by refreshOnUpdate(false, { refreshUploadInput() })
+ /**
+ * Determines if the caption is shown.
+ */
+ var showCaption: Boolean by refreshOnUpdate(true, { refreshUploadInput() })
+ /**
+ * Determines if the preview is shown.
+ */
+ var showPreview: Boolean by refreshOnUpdate(true, { refreshUploadInput() })
+ /**
+ * Determines if the remove button is shown.
+ */
+ var showRemove: Boolean by refreshOnUpdate(true, { refreshUploadInput() })
+ /**
+ * Determines if the upload button is shown.
+ */
+ var showUpload: Boolean by refreshOnUpdate(true, { refreshUploadInput() })
+ /**
+ * Determines if the cancel button is shown.
+ */
+ var showCancel: Boolean by refreshOnUpdate(true, { refreshUploadInput() })
+ /**
+ * Determines if the file browse button is shown.
+ */
+ var showBrowse: Boolean by refreshOnUpdate(true, { refreshUploadInput() })
+ /**
+ * Determines if the click on the preview zone opens file browse window.
+ */
+ var browseOnZoneClick: Boolean by refreshOnUpdate(true, { refreshUploadInput() })
+ /**
+ * Determines if the iconic preview is prefered.
+ */
+ var preferIconicPreview: Boolean by refreshOnUpdate(false, { refreshUploadInput() })
+ /**
+ * Allowed file types.
+ */
+ var allowedFileTypes: Set<String>? by refreshOnUpdate({ refreshUploadInput() })
+ /**
+ * Allowed file extensions.
+ */
+ var allowedFileExtensions: Set<String>? by refreshOnUpdate({ refreshUploadInput() })
+ /**
+ * Determines if Drag&Drop zone is enabled.
+ */
+ var dropZoneEnabled: Boolean by refreshOnUpdate(true, { refreshUploadInput() })
+ /**
+ * 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, { refresh(); refreshUploadInput() })
+ /**
+ * The size of the input (currently not working)
+ */
+ override var size: InputSize? by refreshOnUpdate()
+
+ private val nativeFiles: MutableMap<KFile, File> = mutableMapOf()
+
+ override fun render(): VNode {
+ return render("input")
+ }
+
+ 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()
+ sn.add("type" to "file")
+ name?.let {
+ sn.add("name" to it)
+ }
+ if (multiple) {
+ sn.add("multiple" to "true")
+ }
+ if (disabled) {
+ sn.add("disabled" to "disabled")
+ }
+ return sn
+ }
+
+ private fun getValue(): List<KFile>? {
+ val v = getFiles()
+ return if (v.isNotEmpty()) v else null
+ }
+
+ @Suppress("UnsafeCastFromDynamic")
+ override fun afterInsert(node: VNode) {
+ getElementJQueryD()?.fileinput(getSettingsObj())
+ this.getElementJQuery()?.on("fileselect", { e, _ ->
+ this.dispatchEvent("fileSelectUpload", obj { detail = e })
+ })
+ this.getElementJQuery()?.on("fileclear", { e, _ ->
+ this.dispatchEvent("fileClearUpload", obj { detail = e })
+ })
+ this.getElementJQuery()?.on("filereset", { e, _ ->
+ this.dispatchEvent("fileResetUpload", obj { detail = e })
+ })
+ this.getElementJQuery()?.on("filebrowse", { e, _ ->
+ this.dispatchEvent("fileBrowseUpload", obj { detail = e })
+ })
+ this.getElementJQueryD()?.on("filepreupload", lambda@{ _, data, previewId, index ->
+ data["previewId"] = previewId
+ data["index"] = index
+ this.dispatchEvent("filePreUpload", obj { detail = data })
+ return@lambda null
+ })
+ }
+
+ override fun afterDestroy() {
+ getElementJQueryD()?.fileinput("destroy")
+ }
+
+ private fun refreshUploadInput() {
+ getElementJQueryD()?.fileinput("refresh", getSettingsObj())
+ }
+
+ /**
+ * Resets the file input control.
+ */
+ open fun resetInput() {
+ getElementJQueryD()?.fileinput("reset")
+ }
+
+ /**
+ * Clears the file input control (including the native input).
+ */
+ open fun clearInput() {
+ getElementJQueryD()?.fileinput("clear")
+ }
+
+ /**
+ * Trigger ajax upload (only for ajax mode).
+ */
+ open fun upload() {
+ getElementJQueryD()?.fileinput("upload")
+ }
+
+ /**
+ * Cancel an ongoing ajax upload (only for ajax mode).
+ */
+ open fun cancel() {
+ getElementJQueryD()?.fileinput("cancel")
+ }
+
+ /**
+ * Locks the file input (disabling all buttons except a cancel button).
+ */
+ open fun lock() {
+ getElementJQueryD()?.fileinput("lock")
+ }
+
+ /**
+ * Unlocks the file input.
+ */
+ open fun unlock() {
+ getElementJQueryD()?.fileinput("unlock")
+ }
+
+ /**
+ * Returns the native JavaScript File object.
+ * @param kFile KFile object
+ * @return File object
+ */
+ fun getNativeFile(kFile: KFile): File? {
+ return nativeFiles[kFile]
+ }
+
+ private fun getFiles(): List<KFile> {
+ nativeFiles.clear()
+ return (getElementJQueryD()?.fileinput("getFileStack") as Array<File>).toList().map {
+ val kFile = KFile(it.name, it.size, null)
+ nativeFiles[kFile] = it
+ kFile
+ }
+ }
+
+ /**
+ * Returns the value of the file input control as a String.
+ * @return value as a String
+ */
+ fun getValueAsString(): String? {
+ return value?.joinToString { it.name }
+ }
+
+ /**
+ * Makes the input element focused.
+ */
+ open fun focus() {
+ getElementJQuery()?.focus()
+ }
+
+ /**
+ * Makes the input element blur.
+ */
+ open fun blur() {
+ getElementJQuery()?.blur()
+ }
+
+ private fun getSettingsObj(): dynamic {
+ val language = I18n.language
+ return obj {
+ this.uploadUrl = uploadUrl
+ this.uploadExtraData = uploadExtraData ?: undefined
+ this.theme = if (explorerTheme) "explorer-fa" else null
+ this.required = required
+ this.showCaption = showCaption
+ this.showPreview = showPreview
+ this.showRemove = showRemove
+ this.showUpload = showUpload
+ this.showCancel = showCancel
+ this.showBrowse = showBrowse
+ this.browseOnZoneClick = browseOnZoneClick
+ this.preferIconicPreview = preferIconicPreview
+ this.allowedFileTypes = allowedFileTypes?.toTypedArray()
+ this.allowedFileExtensions = allowedFileExtensions?.toTypedArray()
+ this.dropZoneEnabled = dropZoneEnabled
+ this.language = language
+ }
+ }
+
+ companion object {
+ internal var counter = 0
+
+ /**
+ * DSL builder extension function.
+ *
+ * It takes the same parameters as the constructor of the built component.
+ */
+ fun Container.uploadInput(
+ uploadUrl: String? = null,
+ multiple: Boolean = false,
+ classes: Set<String> = setOf(),
+ init: (UploadInput.() -> Unit)? = null
+ ): UploadInput {
+ val uploadInput = UploadInput(uploadUrl, multiple, classes).apply {
+ init?.invoke(
+ this
+ )
+ }
+ this.add(uploadInput)
+ return uploadInput
+ }
+
+ /**
+ * Returns file with the content read.
+ * @param key key identifier of the control
+ * @param kFile object identifying the file
+ * @return KFile object
+ */
+ @Suppress("EXPERIMENTAL_FEATURE_WARNING")
+ suspend fun <K : Any> Form<K>.getContent(
+ key: KProperty1<K, List<KFile>?>,
+ kFile: KFile
+ ): KFile {
+ val control = getControl(key) as Upload
+ val content = control.getNativeFile(kFile)?.getContent()
+ return kFile.copy(content = content)
+ }
+
+
+ /**
+ * Returns file with the content read.
+ * @param key key identifier of the control
+ * @param kFile object identifying the file
+ * @return KFile object
+ */
+ @Suppress("EXPERIMENTAL_FEATURE_WARNING")
+ suspend fun <K : Any> FormPanel<K>.getContent(
+ key: KProperty1<K, List<KFile>?>,
+ kFile: KFile
+ ): KFile {
+ val control = getControl(key) as Upload
+ val content = control.getNativeFile(kFile)?.getContent()
+ return kFile.copy(content = content)
+ }
+ }
+}
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ar.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ar.js
new file mode 100644
index 00000000..92d32d28
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ar.js
@@ -0,0 +1,101 @@
+/*!
+ * FileInput Arabic Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author Yasser Lotfy <y_l@live.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['ar'] = {
+ fileSingle: 'ملف',
+ filePlural: 'ملفات',
+ browseLabel: 'تصفح &hellip;',
+ removeLabel: 'إزالة',
+ removeTitle: 'إزالة الملفات المختارة',
+ cancelLabel: 'إلغاء',
+ cancelTitle: 'إنهاء الرفع الحالي',
+ uploadLabel: 'رفع',
+ uploadTitle: 'رفع الملفات المختارة',
+ msgNo: 'لا',
+ msgNoFilesSelected: '',
+ msgCancelled: 'ألغيت',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'معاينة تفصيلية',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'الملف "{name}" (<b>{size} ك.ب</b>) تعدى الحد الأقصى المسموح للرفع <b>{maxSize} ك.ب</b>.',
+ msgFilesTooLess: 'يجب عليك اختيار <b>{n}</b> {files} على الأقل للرفع.',
+ msgFilesTooMany: 'عدد الملفات المختارة للرفع <b>({n})</b> تعدت الحد الأقصى المسموح به لعدد <b>{m}</b>.',
+ msgFileNotFound: 'الملف "{name}" غير موجود!',
+ msgFileSecured: 'قيود أمنية تمنع قراءة الملف "{name}".',
+ msgFileNotReadable: 'الملف "{name}" غير قابل للقراءة.',
+ msgFilePreviewAborted: 'تم إلغاء معاينة الملف "{name}".',
+ msgFilePreviewError: 'حدث خطأ أثناء قراءة الملف "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'نوعية غير صالحة للملف "{name}". فقط هذه النوعيات مدعومة "{types}".',
+ msgInvalidFileExtension: 'امتداد غير صالح للملف "{name}". فقط هذه الملفات مدعومة "{extensions}".',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'تم إلغاء رفع الملف',
+ msgUploadThreshold: 'Processing...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'خطأ التحقق من صحة',
+ msgLoading: 'تحميل ملف {index} من {files} &hellip;',
+ msgProgress: 'تحميل ملف {index} من {files} - {name} - {percent}% منتهي.',
+ msgSelected: '{n} {files} مختار(ة)',
+ msgFoldersNotAllowed: 'اسحب وأفلت الملفات فقط! تم تخطي {n} مجلد(ات).',
+ msgImageWidthSmall: 'عرض ملف الصورة "{name}" يجب أن يكون على الأقل {size} px.',
+ msgImageHeightSmall: 'طول ملف الصورة "{name}" يجب أن يكون على الأقل {size} px.',
+ msgImageWidthLarge: 'عرض ملف الصورة "{name}" لا يمكن أن يتعدى {size} px.',
+ msgImageHeightLarge: 'طول ملف الصورة "{name}" لا يمكن أن يتعدى {size} px.',
+ msgImageResizeError: 'لم يتمكن من معرفة أبعاد الصورة لتغييرها.',
+ msgImageResizeException: 'حدث خطأ أثناء تغيير أبعاد الصورة.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'اسحب وأفلت الملفات هنا &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: 'إزالة الملف',
+ uploadTitle: 'رفع الملف',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'مشاهدة التفاصيل',
+ dragTitle: 'Move / Rearrange',
+ indicatorNewTitle: 'لم يتم الرفع بعد',
+ indicatorSuccessTitle: 'تم الرفع',
+ indicatorErrorTitle: 'خطأ بالرفع',
+ indicatorLoadingTitle: 'جارٍ الرفع ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'View previous file',
+ next: 'View next file',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/az.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/az.js
new file mode 100644
index 00000000..5a9c6440
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/az.js
@@ -0,0 +1,101 @@
+/*!
+ * FileInput Azerbaijan Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author Elbrus <elbrusnt@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['az'] = {
+ fileSingle: 'fayl',
+ filePlural: 'fayl',
+ browseLabel: 'Seç &hellip;',
+ removeLabel: 'Sil',
+ removeTitle: 'Seçilmiş faylları təmizlə',
+ cancelLabel: 'İmtina et',
+ cancelTitle: 'Cari yükləməni dayandır',
+ uploadLabel: 'Yüklə',
+ uploadTitle: 'Seçilmiş faylları yüklə',
+ msgNo: 'xeyir',
+ msgNoFilesSelected: 'Heç bir fayl seçilməmişdir',
+ msgCancelled: 'İmtina edildi',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'İlkin baxış',
+ msgFileRequired: 'Yükləmə üçün fayl seçməlisiniz.',
+ msgSizeTooSmall: 'Seçdiyiniz "{name}" faylının həcmi (<b>{size} KB</b>)-dır, minimum <b>{minSize} KB</b> olmalıdır.',
+ msgSizeTooLarge: 'Seçdiyiniz "{name}" faylının həcmi (<b>{size} KB</b>)-dır, maksimum <b>{maxSize} KB</b> olmalıdır.',
+ msgFilesTooLess: 'Yükləmə üçün minimum <b>{n}</b> {files} seçməlisiniz.',
+ msgFilesTooMany: 'Seçilmiş fayl sayı <b>({n})</b>. Maksimum <b>{m}</b> fayl seçmək mümkündür.',
+ msgFileNotFound: 'Fayl "{name}" tapılmadı!',
+ msgFileSecured: '"{name}" faylının istifadəsinə yetginiz yoxdur.',
+ msgFileNotReadable: '"{name}" faylının istifadəsi mümkün deyil.',
+ msgFilePreviewAborted: '"{name}" faylı üçün ilkin baxış ləğv olunub.',
+ msgFilePreviewError: '"{name}" faylının oxunması mümkün olmadı.',
+ msgInvalidFileName: '"{name}" faylının adında qadağan olunmuş simvollardan istifadə olunmuşdur.',
+ msgInvalidFileType: '"{name}" faylının tipi dəstəklənmir. Yalnız "{types}" tipli faylları yükləmək mümkündür.',
+ msgInvalidFileExtension: '"{name}" faylının genişlənməsi yanlışdır. Yalnız "{extensions}" fayl genişlənmə(si / ləri) qəbul olunur.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Yükləmə dayandırılmışdır',
+ msgUploadThreshold: 'Yükləmə...',
+ msgUploadBegin: 'Yoxlama...',
+ msgUploadEnd: 'Fayl(lar) yükləndi',
+ msgUploadEmpty: 'Yükləmə üçün verilmiş məlumatlar yanlışdır',
+ msgUploadError: 'Error',
+ msgValidationError: 'Yoxlama nəticəsi səhvir',
+ msgLoading: '{files} fayldan {index} yüklənir &hellip;',
+ msgProgress: '{files} fayldan {index} - {name} - {percent}% yükləndi.',
+ msgSelected: 'Faylların sayı: {n}',
+ msgFoldersNotAllowed: 'Ancaq faylların daşınmasına icazə verilir! {n} qovluq yüklənmədi.',
+ msgImageWidthSmall: '{name} faylının eni {size} px -dən kiçik olmamalıdır.',
+ msgImageHeightSmall: '{name} faylının hündürlüyü {size} px -dən kiçik olmamalıdır.',
+ msgImageWidthLarge: '"{name}" faylının eni {size} px -dən böyük olmamalıdır.',
+ msgImageHeightLarge: '"{name}" faylının hündürlüyü {size} px -dən böyük olmamalıdır.',
+ msgImageResizeError: 'Faylın ölçülərini dəyişmək üçün ölçüləri hesablamaq mümkün olmadı.',
+ msgImageResizeException: 'Faylın ölçülərini dəyişmək mümkün olmadı.<pre>{errors}</pre>',
+ msgAjaxError: '{operation} əməliyyatı zamanı səhv baş verdi. Təkrar yoxlayın!',
+ msgAjaxProgressError: '{operation} əməliyyatı yerinə yetirmək mümkün olmadı.',
+ ajaxOperations: {
+ deleteThumb: 'faylı sil',
+ uploadThumb: 'faylı yüklə',
+ uploadBatch: 'bir neçə faylı yüklə',
+ uploadExtra: 'məlumatların yüklənməsi'
+ },
+ dropZoneTitle: 'Faylları bura daşıyın &hellip;',
+ dropZoneClickTitle: '<br>(Və ya seçin {files})',
+ fileActionSettings: {
+ removeTitle: 'Faylı sil',
+ uploadTitle: 'Faylı yüklə',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'məlumatlara bax',
+ dragTitle: 'Yerini dəyiş və ya sırala',
+ indicatorNewTitle: 'Davam edir',
+ indicatorSuccessTitle: 'Tamamlandı',
+ indicatorErrorTitle: 'Yükləmə xətası',
+ indicatorLoadingTitle: 'Yükləmə ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Əvvəlki fayla bax',
+ next: 'Növbəti fayla bax',
+ toggleheader: 'Başlığı dəyiş',
+ fullscreen: 'Tam ekranı dəyiş',
+ borderless: 'Bölmələrsiz rejimi dəyiş',
+ close: 'Ətraflı baxışı bağla'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/bg.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/bg.js
new file mode 100644
index 00000000..cf75d1ab
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/bg.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Bulgarian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['bg'] = {
+ fileSingle: 'файл',
+ filePlural: 'файла',
+ browseLabel: 'Избери &hellip;',
+ removeLabel: 'Премахни',
+ removeTitle: 'Изчисти избраните',
+ cancelLabel: 'Откажи',
+ cancelTitle: 'Откажи качването',
+ uploadLabel: 'Качи',
+ uploadTitle: 'Качи избраните файлове',
+ msgNo: 'Не',
+ msgNoFilesSelected: '',
+ msgCancelled: 'Отменен',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Детайлен преглед',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Файла "{name}" (<b>{size} KB</b>) надвишава максималните разрешени <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Трябва да изберете поне <b>{n}</b> {files} файла.',
+ msgFilesTooMany: 'Броя файлове избрани за качване <b>({n})</b> надвишава ограниченито от максимум <b>{m}</b>.',
+ msgFileNotFound: 'Файлът "{name}" не може да бъде намерен!',
+ msgFileSecured: 'От съображения за сигурност не може да прочетем файла "{name}".',
+ msgFileNotReadable: 'Файлът "{name}" не е четим.',
+ msgFilePreviewAborted: 'Прегледа на файла е прекратен за "{name}".',
+ msgFilePreviewError: 'Грешка при опит за четене на файла "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'Невалиден тип на файла "{name}". Разрешени са само "{types}".',
+ msgInvalidFileExtension: 'Невалидно разрешение на "{name}". Разрешени са само "{extensions}".',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Качите файла, бе прекратена',
+ msgUploadThreshold: 'Processing...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'утвърждаване грешка',
+ msgLoading: 'Зареждане на файл {index} от общо {files} &hellip;',
+ msgProgress: 'Зареждане на файл {index} от общо {files} - {name} - {percent}% завършени.',
+ msgSelected: '{n} {files} избрани',
+ msgFoldersNotAllowed: 'Само пуснати файлове! Пропуснати {n} пуснати папки.',
+ msgImageWidthSmall: 'Широчината на изображението "{name}" трябва да е поне {size} px.',
+ msgImageHeightSmall: 'Височината на изображението "{name}" трябва да е поне {size} px.',
+ msgImageWidthLarge: 'Широчината на изображението "{name}" не може да е по-голяма от {size} px.',
+ msgImageHeightLarge: 'Височината на изображението "{name}" нее може да е по-голяма от {size} px.',
+ msgImageResizeError: 'Не може да размерите на изображението, за да промените размера.',
+ msgImageResizeException: 'Грешка при промяна на размера на изображението.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Пуснете файловете тук &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: 'Махни файл',
+ uploadTitle: 'Качване на файл',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Вижте детайли',
+ dragTitle: 'Move / Rearrange',
+ indicatorNewTitle: 'Все още не е качил',
+ indicatorSuccessTitle: 'Качено',
+ indicatorErrorTitle: 'Качи Error',
+ indicatorLoadingTitle: 'Качва се ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'View previous file',
+ next: 'View next file',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ca.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ca.js
new file mode 100644
index 00000000..16514535
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ca.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Català Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['ca'] = {
+ fileSingle: 'arxiu',
+ filePlural: 'arxius',
+ browseLabel: 'Examinar &hellip;',
+ removeLabel: 'Treure',
+ removeTitle: 'Treure arxius seleccionats',
+ cancelLabel: 'Cancel',
+ cancelTitle: 'Avortar la pujada en curs',
+ uploadLabel: 'Pujar arxiu',
+ uploadTitle: 'Pujar arxius seleccionats',
+ msgNo: 'No',
+ msgNoFilesSelected: '',
+ msgCancelled: 'cancel·lat',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Vista prèvia detallada',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Arxiu "{name}" (<b>{size} KB</b>) excedeix la mida màxima permès de <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Heu de seleccionar almenys <b>{n}</b> {files} a carregar.',
+ msgFilesTooMany: 'El nombre d\'arxius seleccionats a carregar <b>({n})</b> excedeix el límit màxim permès de <b>{m}</b>.',
+ msgFileNotFound: 'Arxiu "{name}" no trobat.',
+ msgFileSecured: 'No es pot accedir a l\'arxiu "{name}" perquè estarà sent usat per una altra aplicació o no tinguem permisos de lectura.',
+ msgFileNotReadable: 'No es pot accedir a l\'arxiu "{name}".',
+ msgFilePreviewAborted: 'Previsualització de l\'arxiu "{name}" cancel·lada.',
+ msgFilePreviewError: 'S\'ha produït un error mentre es llegia el fitxer "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'Tipus de fitxer no vàlid per a "{name}". Només arxius "{types}" són permesos.',
+ msgInvalidFileExtension: 'Extensió de fitxer no vàlid per a "{name}". Només arxius "{extensions}" són permesos.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'La càrrega d\'arxius s\'ha cancel·lat',
+ msgUploadThreshold: 'Processing...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Error de validació',
+ msgLoading: 'Pujant fitxer {index} de {files} &hellip;',
+ msgProgress: 'Pujant fitxer {index} de {files} - {name} - {percent}% completat.',
+ msgSelected: '{n} {files} seleccionat(s)',
+ msgFoldersNotAllowed: 'Arrossegueu i deixeu anar únicament arxius. Omesa(es) {n} carpeta(es).',
+ msgImageWidthSmall: 'L\'ample de la imatge "{name}" ha de ser almenys {size} px.',
+ msgImageHeightSmall: 'L\'alçada de la imatge "{name}" ha de ser almenys {size} px.',
+ msgImageWidthLarge: 'L\'ample de la imatge "{name}" no pot excedir de {size} px.',
+ msgImageHeightLarge: 'L\'alçada de la imatge "{name}" no pot excedir de {size} px.',
+ msgImageResizeError: 'No s\'ha pogut obtenir les dimensions d\'imatge per canviar la mida.',
+ msgImageResizeException: 'Error en canviar la mida de la imatge.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Arrossegueu i deixeu anar aquí els arxius &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: 'Eliminar arxiu',
+ uploadTitle: 'Pujar arxiu',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Veure detalls',
+ dragTitle: 'Move / Rearrange',
+ indicatorNewTitle: 'No pujat encara',
+ indicatorSuccessTitle: 'Subido',
+ indicatorErrorTitle: 'Pujar Error',
+ indicatorLoadingTitle: 'Pujant ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'View previous file',
+ next: 'View next file',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/cr.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/cr.js
new file mode 100644
index 00000000..685da85d
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/cr.js
@@ -0,0 +1,101 @@
+/*!
+ * FileInput Croatian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author Milos Stojanovic <stojanovic.loshmi@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['cr'] = {
+ fileSingle: 'datoteka',
+ filePlural: 'datoteke',
+ browseLabel: 'Izaberi &hellip;',
+ removeLabel: 'Ukloni',
+ removeTitle: 'Ukloni označene datoteke',
+ cancelLabel: 'Odustani',
+ cancelTitle: 'Prekini trenutno otpremanje',
+ uploadLabel: 'Otpremi',
+ uploadTitle: 'Otpremi označene datoteke',
+ msgNo: 'Ne',
+ msgNoFilesSelected: '',
+ msgCancelled: 'Otkazan',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Detaljni pregled',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Datoteka "{name}" (<b>{size} KB</b>) prekoračuje maksimalnu dozvoljenu veličinu datoteke od <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Morate odabrati najmanje <b>{n}</b> {files} za otpremanje.',
+ msgFilesTooMany: 'Broj datoteka označenih za otpremanje <b>({n})</b> prekoračuje maksimalni dozvoljeni limit od <b>{m}</b>.',
+ msgFileNotFound: 'Datoteka "{name}" nije pronađena!',
+ msgFileSecured: 'Datoteku "{name}" nije moguće pročitati zbog bezbednosnih ograničenja.',
+ msgFileNotReadable: 'Datoteku "{name}" nije moguće pročitati.',
+ msgFilePreviewAborted: 'Generisanje prikaza nije moguće za "{name}".',
+ msgFilePreviewError: 'Došlo je do greške prilikom čitanja datoteke "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'Datoteka "{name}" je pogrešnog formata. Dozvoljeni formati su "{types}".',
+ msgInvalidFileExtension: 'Ekstenzija datoteke "{name}" nije dozvoljena. Dozvoljene ekstenzije su "{extensions}".',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Prijenos datoteka je prekinut',
+ msgUploadThreshold: 'Processing...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Provjera pogrešaka',
+ msgLoading: 'Učitavanje datoteke {index} od {files} &hellip;',
+ msgProgress: 'Učitavanje datoteke {index} od {files} - {name} - {percent}% završeno.',
+ msgSelected: '{n} {files} je označeno',
+ msgFoldersNotAllowed: 'Moguće je prevlačiti samo datoteke! Preskočeno je {n} fascikla.',
+ msgImageWidthSmall: 'Širina slikovnu datoteku "{name}" moraju biti najmanje {size} px.',
+ msgImageHeightSmall: 'Visina slikovnu datoteku "{name}" moraju biti najmanje {size} px.',
+ msgImageWidthLarge: 'Širina slikovnu datoteku "{name}" ne može prelaziti {size} px.',
+ msgImageHeightLarge: 'Visina slikovnu datoteku "{name}" ne može prelaziti {size} px.',
+ msgImageResizeError: 'Nije mogao dobiti dimenzije slike na veličinu.',
+ msgImageResizeException: 'Greška prilikom promjene veličine slike.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Prevucite datoteke ovde &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: 'Uklonite datoteku',
+ uploadTitle: 'Postavi datoteku',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Pregledavati pojedinosti',
+ dragTitle: 'Move / Rearrange',
+ indicatorNewTitle: 'Još nije učitao',
+ indicatorSuccessTitle: 'Preneseno',
+ indicatorErrorTitle: 'Postavi Greška',
+ indicatorLoadingTitle: 'Prijenos ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'View previous file',
+ next: 'View next file',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/cs.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/cs.js
new file mode 100644
index 00000000..f5e8b723
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/cs.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Czech Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['cs'] = {
+ fileSingle: 'soubor',
+ filePlural: 'soubory',
+ browseLabel: 'Vybrat &hellip;',
+ removeLabel: 'Odstranit',
+ removeTitle: 'Vyčistit vybrané soubory',
+ cancelLabel: 'Storno',
+ cancelTitle: 'Přerušit nahrávání',
+ uploadLabel: 'Nahrát',
+ uploadTitle: 'Nahrát vybrané soubory',
+ msgNo: 'Ne',
+ msgNoFilesSelected: 'Nevybrány žádné soubory',
+ msgCancelled: 'Zrušeno',
+ msgPlaceholder: 'Vybrat {files}...',
+ msgZoomModalHeading: 'Detailní náhled',
+ msgFileRequired: 'Musíte vybrat soubor, který chcete nahrát.',
+ msgSizeTooSmall: 'Soubor "{name}" (<b>{size} KB</b>) je příliš malý, musí mít velikost nejméně <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Soubor "{name}" (<b>{size} KB</b>) je příliš velký, maximální povolená velikost <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Musíte vybrat nejméně <b>{n}</b> {files} souborů.',
+ msgFilesTooMany: 'Počet vybraných souborů <b>({n})</b> překročil maximální povolený limit <b>{m}</b>.',
+ msgFileNotFound: 'Soubor "{name}" nebyl nalezen!',
+ msgFileSecured: 'Zabezpečení souboru znemožnilo číst soubor "{name}".',
+ msgFileNotReadable: 'Soubor "{name}" není čitelný.',
+ msgFilePreviewAborted: 'Náhled souboru byl přerušen pro "{name}".',
+ msgFilePreviewError: 'Nastala chyba při načtení souboru "{name}".',
+ msgInvalidFileName: 'Neplatné nebo nepovolené znaky ve jménu souboru "{name}".',
+ msgInvalidFileType: 'Neplatný typ souboru "{name}". Pouze "{types}" souborů jsou podporovány.',
+ msgInvalidFileExtension: 'Neplatná extenze souboru "{name}". Pouze "{extensions}" souborů jsou podporovány.',
+ msgFileTypes: {
+ 'image': 'obrázek',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Nahrávání souboru bylo přerušeno',
+ msgUploadThreshold: 'Zpracovávám...',
+ msgUploadBegin: 'Inicializujem...',
+ msgUploadEnd: 'Hotovo',
+ msgUploadEmpty: 'Pro nahrávání nejsou k dispozici žádné platné údaje.',
+ msgUploadError: 'Chyba',
+ msgValidationError: 'Chyba ověření',
+ msgLoading: 'Nahrávání souboru {index} z {files} &hellip;',
+ msgProgress: 'Nahrávání souboru {index} z {files} - {name} - {percent}% dokončeno.',
+ msgSelected: '{n} {files} vybráno',
+ msgFoldersNotAllowed: 'Táhni a pusť pouze soubory! Vynechané {n} pustěné složk(y).',
+ msgImageWidthSmall: 'Šířka obrázku "{name}", musí být alespoň {size} px.',
+ msgImageHeightSmall: 'Výška obrázku "{name}", musí být alespoň {size} px.',
+ msgImageWidthLarge: 'Šířka obrázku "{name}" nesmí být větší než {size} px.',
+ msgImageHeightLarge: 'Výška obrázku "{name}" nesmí být větší než {size} px.',
+ msgImageResizeError: 'Nelze získat rozměry obrázku pro změnu velikosti.',
+ msgImageResizeException: 'Chyba při změně velikosti obrázku.<pre>{errors}</pre>',
+ msgAjaxError: 'Došlo k chybě v {operation}. Prosím zkuste to znovu později!',
+ msgAjaxProgressError: '{operation} - neúspěšné',
+ ajaxOperations: {
+ deleteThumb: 'odstranit soubor',
+ uploadThumb: 'nahrát soubor',
+ uploadBatch: 'nahrát várku souborů',
+ uploadExtra: 'odesílání dat formuláře'
+ },
+ dropZoneTitle: 'Přetáhni soubory sem &hellip;',
+ dropZoneClickTitle: '<br>(nebo klikni sem a vyber je)',
+ fileActionSettings: {
+ removeTitle: 'Odstranit soubor',
+ uploadTitle: 'Nahrát soubor',
+ uploadRetryTitle: 'Opakovat nahrávání',
+ downloadTitle: 'Stáhnout soubor',
+ zoomTitle: 'Zobrazit podrobnosti',
+ dragTitle: 'Posunout / Přeskládat',
+ indicatorNewTitle: 'Ještě nenahrál',
+ indicatorSuccessTitle: 'Nahraný',
+ indicatorErrorTitle: 'Chyba nahrávání',
+ indicatorLoadingTitle: 'Nahrávání ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Zobrazit předchozí soubor',
+ next: 'Zobrazit následující soubor',
+ toggleheader: 'Přepnout záhlaví',
+ fullscreen: 'Přepnout celoobrazovkové zobrazení',
+ borderless: 'Přepnout bezrámečkové zobrazení',
+ close: 'Zavřít detailní náhled'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/da.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/da.js
new file mode 100644
index 00000000..613defec
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/da.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Danish Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['da'] = {
+ fileSingle: 'fil',
+ filePlural: 'filer',
+ browseLabel: 'Browse &hellip;',
+ removeLabel: 'Fjern',
+ removeTitle: 'Fjern valgte filer',
+ cancelLabel: 'Fortryd',
+ cancelTitle: 'Afbryd nuv&aelig;rende upload',
+ uploadLabel: 'Upload',
+ uploadTitle: 'Upload valgte filer',
+ msgNo: 'Ingen',
+ msgNoFilesSelected: '',
+ msgCancelled: 'aflyst',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Detaljeret visning',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Fil "{name}" (<b>{size} KB</b>) er st&oslash;rre end de tilladte <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Du skal mindst v&aelig;lge <b>{n}</b> {files} til upload.',
+ msgFilesTooMany: '<b>({n})</b> filer valgt til upload, men maks. <b>{m}</b> er tilladt.',
+ msgFileNotFound: 'Filen "{name}" blev ikke fundet!',
+ msgFileSecured: 'Sikkerhedsrestriktioner forhindrer l&aelig;sning af "{name}".',
+ msgFileNotReadable: 'Filen "{name}" kan ikke indl&aelig;ses.',
+ msgFilePreviewAborted: 'Filpreview annulleret for "{name}".',
+ msgFilePreviewError: 'Der skete en fejl under l&aelig;sningen af filen "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'Ukendt type for filen "{name}". Kun "{types}" kan bruges.',
+ msgInvalidFileExtension: 'Ukendt filtype for filen "{name}". Kun "{extensions}" filer kan bruges.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Filupload annulleret',
+ msgUploadThreshold: 'Processing...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Validering Fejl',
+ msgLoading: 'Henter fil {index} af {files} &hellip;',
+ msgProgress: 'Henter fil {index} af {files} - {name} - {percent}% f&aelig;rdiggjort.',
+ msgSelected: '{n} {files} valgt',
+ msgFoldersNotAllowed: 'Drag & drop kun filer! {n} mappe(r) sprunget over.',
+ msgImageWidthSmall: 'Bredden af billedet "{name}" skal v&aelig;re p&aring; mindst {size} px.',
+ msgImageHeightSmall: 'H&oslash;jden af billedet "{name}" skal v&aelig;re p&aring; mindst {size} px.',
+ msgImageWidthLarge: 'Bredden af billedet "{name}" m&aring; ikke v&aelig;re over {size} px.',
+ msgImageHeightLarge: 'H&oslash;jden af billedet "{name}" m&aring; ikke v&aelig;re over {size} px.',
+ msgImageResizeError: 'Kunne ikke få billedets dimensioner for at ændre størrelsen.',
+ msgImageResizeException: 'Fejl ved at ændre størrelsen på billedet.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Drag & drop filer her &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: 'Fjern fil',
+ uploadTitle: 'Upload fil',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Se detaljer',
+ dragTitle: 'Move / Rearrange',
+ indicatorNewTitle: 'Ikke uploadet endnu',
+ indicatorSuccessTitle: 'Uploadet',
+ indicatorErrorTitle: 'Upload fejl',
+ indicatorLoadingTitle: 'Uploader ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'View previous file',
+ next: 'View next file',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery); \ No newline at end of file
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/de.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/de.js
new file mode 100644
index 00000000..56ee854b
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/de.js
@@ -0,0 +1,98 @@
+/*!
+ * FileInput German Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['de'] = {
+ fileSingle: 'Datei',
+ filePlural: 'Dateien',
+ browseLabel: 'Auswählen &hellip;',
+ removeLabel: 'Löschen',
+ removeTitle: 'Ausgewählte löschen',
+ cancelLabel: 'Abbrechen',
+ cancelTitle: 'Hochladen abbrechen',
+ uploadLabel: 'Hochladen',
+ uploadTitle: 'Hochladen der ausgewählten Dateien',
+ msgNo: 'Keine',
+ msgNoFilesSelected: 'Keine Dateien ausgewählt',
+ msgCancelled: 'Abgebrochen',
+ msgPlaceholder: '{files} auswählen...',
+ msgZoomModalHeading: 'ausführliche Vorschau',
+ msgFileRequired: 'Sie müssen eine Datei zum Hochladen auswählen.',
+ msgSizeTooSmall: 'Datei "{name}" (<b>{size} KB</b>) unterschreitet mindestens notwendige Upload-Größe von <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Datei "{name}" (<b>{size} KB</b>) überschreitet maximal zulässige Upload-Größe von <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Sie müssen mindestens <b>{n}</b> {files} zum Hochladen auswählen.',
+ msgFilesTooMany: 'Anzahl der zum Hochladen ausgewählten Dateien <b>({n})</b>, überschreitet maximal zulässige Grenze von <b>{m}</b> Stück.',
+ msgFileNotFound: 'Datei "{name}" wurde nicht gefunden!',
+ msgFileSecured: 'Sicherheitseinstellungen verhindern das Lesen der Datei "{name}".',
+ msgFileNotReadable: 'Die Datei "{name}" ist nicht lesbar.',
+ msgFilePreviewAborted: 'Dateivorschau abgebrochen für "{name}".',
+ msgFilePreviewError: 'Beim Lesen der Datei "{name}" ein Fehler aufgetreten.',
+ msgInvalidFileName: 'Ungültige oder nicht unterstützte Zeichen im Dateinamen "{name}".',
+ msgInvalidFileType: 'Ungültiger Typ für Datei "{name}". Nur Dateien der Typen "{types}" werden unterstützt.',
+ msgInvalidFileExtension: 'Ungültige Erweiterung für Datei "{name}". Nur Dateien mit der Endung "{extensions}" werden unterstützt.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Der Datei-Upload wurde abgebrochen',
+ msgUploadThreshold: 'Wird bearbeitet ...',
+ msgUploadBegin: 'Wird initialisiert ...',
+ msgUploadEnd: 'Erledigt',
+ msgUploadEmpty: 'Keine gültigen Daten zum Hochladen verfügbar.',
+ msgUploadError: 'Fehler',
+ msgValidationError: 'Validierungsfehler',
+ msgLoading: 'Lade Datei {index} von {files} hoch&hellip;',
+ msgProgress: 'Datei {index} von {files} - {name} - zu {percent}% fertiggestellt.',
+ msgSelected: '{n} {files} ausgewählt',
+ msgFoldersNotAllowed: 'Drag & Drop funktioniert nur bei Dateien! {n} Ordner übersprungen.',
+ msgImageWidthSmall: 'Breite der Bilddatei "{name}" muss mindestens {size} px betragen.',
+ msgImageHeightSmall: 'Höhe der Bilddatei "{name}" muss mindestens {size} px betragen.',
+ msgImageWidthLarge: 'Breite der Bilddatei "{name}" nicht überschreiten {size} px.',
+ msgImageHeightLarge: 'Höhe der Bilddatei "{name}" nicht überschreiten {size} px.',
+ msgImageResizeError: 'Konnte nicht die Bildabmessungen zu ändern.',
+ msgImageResizeException: 'Fehler beim Ändern der Größe des Bildes.<pre>{errors}</pre>',
+ msgAjaxError: 'Bei der Aktion {operation} ist ein Fehler aufgetreten. Bitte versuche es später noch einmal!',
+ msgAjaxProgressError: '{operation} fehlgeschlagen',
+ ajaxOperations: {
+ deleteThumb: 'Datei löschen',
+ uploadThumb: 'Datei hochladen',
+ uploadBatch: 'Batch-Datei-Upload',
+ uploadExtra: 'Formular-Datei-Upload'
+ },
+ dropZoneTitle: 'Dateien hierher ziehen &hellip;',
+ dropZoneClickTitle: '<br>(oder klicken um {files} auszuwählen)',
+ fileActionSettings: {
+ removeTitle: 'Datei entfernen',
+ uploadTitle: 'Datei hochladen',
+ uploadRetryTitle: 'Upload erneut versuchen',
+ downloadTitle: 'Datei herunterladen',
+ zoomTitle: 'Details anzeigen',
+ dragTitle: 'Verschieben / Neuordnen',
+ indicatorNewTitle: 'Noch nicht hochgeladen',
+ indicatorSuccessTitle: 'Hochgeladen',
+ indicatorErrorTitle: 'Upload Fehler',
+ indicatorLoadingTitle: 'Hochladen ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Vorherige Datei anzeigen',
+ next: 'Nächste Datei anzeigen',
+ toggleheader: 'Header umschalten',
+ fullscreen: 'Vollbildmodus umschalten',
+ borderless: 'Randlosen Modus umschalten',
+ close: 'Detailansicht schließen'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/el.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/el.js
new file mode 100644
index 00000000..170eba1f
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/el.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Greek Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['el'] = {
+ fileSingle: 'αρχείο',
+ filePlural: 'αρχεία',
+ browseLabel: 'Αναζήτηση &hellip;',
+ removeLabel: 'Διαγραφή',
+ removeTitle: 'Εκκαθάριση αρχείων',
+ cancelLabel: 'Ακύρωση',
+ cancelTitle: 'Ακύρωση μεταφόρτωσης',
+ uploadLabel: 'Μεταφόρτωση',
+ uploadTitle: 'Μεταφόρτωση επιλεγμένων αρχείων',
+ msgNo: 'Όχι',
+ msgNoFilesSelected: 'Δεν επιλέχθηκαν αρχεία',
+ msgCancelled: 'Ακυρώθηκε',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Λεπτομερής Προεπισκόπηση',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'Το "{name}" (<b>{size} KB</b>) είναι πολύ μικρό, πρέπει να είναι μεγαλύτερο από <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Το αρχείο "{name}" (<b>{size} KB</b>) υπερβαίνει το μέγιστο επιτρεπόμενο μέγεθος μεταφόρτωσης <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Πρέπει να επιλέξετε τουλάχιστον <b>{n}</b> {files} για να ξεκινήσει η μεταφόρτωση.',
+ msgFilesTooMany: 'Ο αριθμός των αρχείων που έχουν επιλεγεί για μεταφόρτωση <b>({n})</b> υπερβαίνει το μέγιστο επιτρεπόμενο αριθμό <b>{m}</b>.',
+ msgFileNotFound: 'Το αρχείο "{name}" δεν βρέθηκε!',
+ msgFileSecured: 'Περιορισμοί ασφαλείας εμπόδισαν την ανάγνωση του αρχείου "{name}".',
+ msgFileNotReadable: 'Το αρχείο "{name}" δεν είναι αναγνώσιμο.',
+ msgFilePreviewAborted: 'Η προεπισκόπηση του αρχείου "{name}" ακυρώθηκε.',
+ msgFilePreviewError: 'Παρουσιάστηκε σφάλμα κατά την ανάγνωση του αρχείου "{name}".',
+ msgInvalidFileName: 'Μη έγκυροι χαρακτήρες στο όνομα του αρχείου "{name}".',
+ msgInvalidFileType: 'Μη έγκυρος ο τύπος του αρχείου "{name}". Οι τύποι αρχείων που υποστηρίζονται είναι : "{types}".',
+ msgInvalidFileExtension: 'Μη έγκυρη η επέκταση του αρχείου "{name}". Οι επεκτάσεις που υποστηρίζονται είναι : "{extensions}".',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Η μεταφόρτωση του αρχείου ματαιώθηκε',
+ msgUploadThreshold: 'Μεταφόρτωση ...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Σφάλμα Επικύρωσης',
+ msgLoading: 'Φόρτωση αρχείου {index} από {files} &hellip;',
+ msgProgress: 'Φόρτωση αρχείου {index} απο {files} - {name} - {percent}% ολοκληρώθηκε.',
+ msgSelected: '{n} {files} επιλέχθηκαν',
+ msgFoldersNotAllowed: 'Μπορείτε να σύρετε μόνο αρχεία! Παραβλέφθηκαν {n} φάκελος(οι).',
+ msgImageWidthSmall: 'Το πλάτος του αρχείου εικόνας "{name}" πρέπει να είναι τουλάχιστον {size} px.',
+ msgImageHeightSmall: 'Το ύψος του αρχείου εικόνας "{name}" πρέπει να είναι τουλάχιστον {size} px.',
+ msgImageWidthLarge: 'Το πλάτος του αρχείου εικόνας "{name}" δεν μπορεί να υπερβαίνει το {size} px.',
+ msgImageHeightLarge: 'Το ύψος του αρχείου εικόνας "{name}" δεν μπορεί να υπερβαίνει το {size} px.',
+ msgImageResizeError: 'Δεν μπορούν να βρεθούν οι διαστάσεις της εικόνας για να αλλάγή μεγέθους.',
+ msgImageResizeException: 'Σφάλμα κατά την αλλαγή μεγέθους της εικόνας. <pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Σύρετε τα αρχεία εδώ &hellip;',
+ dropZoneClickTitle: '<br>(ή πατήστε για επιλογή {files})',
+ fileActionSettings: {
+ removeTitle: 'Αφαιρέστε το αρχείο',
+ uploadTitle: 'Μεταφορτώστε το αρχείο',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Δείτε λεπτομέρειες',
+ dragTitle: 'Μετακίνηση/Προσπαρμογή',
+ indicatorNewTitle: 'Δεν μεταφορτώθηκε ακόμα',
+ indicatorSuccessTitle: 'Μεταφορτώθηκε',
+ indicatorErrorTitle: 'Σφάλμα Μεταφόρτωσης',
+ indicatorLoadingTitle: 'Μεταφόρτωση ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Προηγούμενο αρχείο',
+ next: 'Επόμενο αρχείο',
+ toggleheader: 'Εμφάνιση/Απόκρυψη τίτλου',
+ fullscreen: 'Εναλλαγή πλήρους οθόνης',
+ borderless: 'Με ή χωρίς πλαίσιο',
+ close: 'Κλείσιμο προβολής'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/es.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/es.js
new file mode 100644
index 00000000..569e7ee5
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/es.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Spanish Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['es'] = {
+ fileSingle: 'archivo',
+ filePlural: 'archivos',
+ browseLabel: 'Examinar &hellip;',
+ removeLabel: 'Quitar',
+ removeTitle: 'Quitar archivos seleccionados',
+ cancelLabel: 'Cancelar',
+ cancelTitle: 'Abortar la subida en curso',
+ uploadLabel: 'Subir archivo',
+ uploadTitle: 'Subir archivos seleccionados',
+ msgNo: 'No',
+ msgNoFilesSelected: 'No hay archivos seleccionados',
+ msgCancelled: 'Cancelado',
+ msgPlaceholder: 'Seleccionar {files}...',
+ msgZoomModalHeading: 'Vista previa detallada',
+ msgFileRequired: 'Debes seleccionar un archivo para subir.',
+ msgSizeTooSmall: 'El archivo "{name}" (<b>{size} KB</b>) es demasiado pequeño y debe ser mayor de <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'El archivo "{name}" (<b>{size} KB</b>) excede el tamaño máximo permitido de <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Debe seleccionar al menos <b>{n}</b> {files} a cargar.',
+ msgFilesTooMany: 'El número de archivos seleccionados a cargar <b>({n})</b> excede el límite máximo permitido de <b>{m}</b>.',
+ msgFileNotFound: 'Archivo "{name}" no encontrado.',
+ msgFileSecured: 'No es posible acceder al archivo "{name}" porque está siendo usado por otra aplicación o no tiene permisos de lectura.',
+ msgFileNotReadable: 'No es posible acceder al archivo "{name}".',
+ msgFilePreviewAborted: 'Previsualización del archivo "{name}" cancelada.',
+ msgFilePreviewError: 'Ocurrió un error mientras se leía el archivo "{name}".',
+ msgInvalidFileName: 'Caracteres no válidos o no soportados en el nombre del archivo "{name}".',
+ msgInvalidFileType: 'Tipo de archivo no válido para "{name}". Sólo se permiten archivos de tipo "{types}".',
+ msgInvalidFileExtension: 'Extensión de archivo no válida para "{name}". Sólo se permiten archivos "{extensions}".',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'La carga de archivos se ha cancelado',
+ msgUploadThreshold: 'Procesando...',
+ msgUploadBegin: 'Inicializando...',
+ msgUploadEnd: 'Hecho',
+ msgUploadEmpty: 'No existen datos válidos para el envío.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Error de validación',
+ msgLoading: 'Subiendo archivo {index} de {files} &hellip;',
+ msgProgress: 'Subiendo archivo {index} de {files} - {name} - {percent}% completado.',
+ msgSelected: '{n} {files} seleccionado(s)',
+ msgFoldersNotAllowed: 'Arrastre y suelte únicamente archivos. Omitida(s) {n} carpeta(s).',
+ msgImageWidthSmall: 'El ancho de la imagen "{name}" debe ser de al menos {size} px.',
+ msgImageHeightSmall: 'La altura de la imagen "{name}" debe ser de al menos {size} px.',
+ msgImageWidthLarge: 'El ancho de la imagen "{name}" no puede exceder de {size} px.',
+ msgImageHeightLarge: 'La altura de la imagen "{name}" no puede exceder de {size} px.',
+ msgImageResizeError: 'No se pudieron obtener las dimensiones de la imagen para cambiar el tamaño.',
+ msgImageResizeException: 'Error al cambiar el tamaño de la imagen.<pre>{errors}</pre>',
+ msgAjaxError: 'Algo ha ido mal con la operación {operation}. Por favor, inténtelo de nuevo mas tarde.',
+ msgAjaxProgressError: 'La operación {operation} ha fallado',
+ ajaxOperations: {
+ deleteThumb: 'Archivo borrado',
+ uploadThumb: 'Archivo subido',
+ uploadBatch: 'Datos subidos en lote',
+ uploadExtra: 'Datos del formulario subidos '
+ },
+ dropZoneTitle: 'Arrastre y suelte aquí los archivos &hellip;',
+ dropZoneClickTitle: '<br>(o haga clic para seleccionar {files})',
+ fileActionSettings: {
+ removeTitle: 'Eliminar archivo',
+ uploadTitle: 'Subir archivo',
+ uploadRetryTitle: 'Reintentar subir',
+ downloadTitle: 'Descargar archivo',
+ zoomTitle: 'Ver detalles',
+ dragTitle: 'Mover / Reordenar',
+ indicatorNewTitle: 'No subido todavía',
+ indicatorSuccessTitle: 'Subido',
+ indicatorErrorTitle: 'Error al subir',
+ indicatorLoadingTitle: 'Subiendo...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Anterior',
+ next: 'Siguiente',
+ toggleheader: 'Mostrar encabezado',
+ fullscreen: 'Pantalla completa',
+ borderless: 'Modo sin bordes',
+ close: 'Cerrar vista detallada'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/et.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/et.js
new file mode 100644
index 00000000..50b15477
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/et.js
@@ -0,0 +1,99 @@
+/*!
+ * FileInput Estonian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['et'] = {
+ fileSingle: 'fail',
+ filePlural: 'failid',
+ browseLabel: 'Sirvi &hellip;',
+ removeLabel: 'Eemalda',
+ removeTitle: 'Clear selected files',
+ cancelLabel: 'Tühista',
+ cancelTitle: 'Abort ongoing upload',
+ uploadLabel: 'Salvesta',
+ uploadTitle: 'Salvesta valitud failid',
+ msgNo: 'No',
+ msgNoFilesSelected: 'No files selected',
+ msgCancelled: 'Cancelled',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Detailed Preview',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Fail "{name}" (<b>{size} KB</b>) ületab lubatu suuruse <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'You must select at least <b>{n}</b> {files} to upload.',
+ msgFilesTooMany: 'Number of files selected for upload <b>({n})</b> exceeds maximum allowed limit of <b>{m}</b>.',
+ msgFileNotFound: 'File "{name}" not found!',
+ msgFileSecured: 'Security restrictions prevent reading the file "{name}".',
+ msgFileNotReadable: 'File "{name}" is not readable.',
+ msgFilePreviewAborted: 'File preview aborted for "{name}".',
+ msgFilePreviewError: 'An error occurred while reading the file "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: '"{name}" on vale tüüpi. Ainult "{types}" on lubatud.',
+ msgInvalidFileExtension: 'Invalid extension for file "{name}". Only "{extensions}" files are supported.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'The file upload was aborted',
+ msgUploadThreshold: 'Processing...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Validation Error',
+ msgLoading: 'Loading file {index} of {files} &hellip;',
+ msgProgress: 'Loading file {index} of {files} - {name} - {percent}% completed.',
+ msgSelected: '{n} {files} selected',
+ msgFoldersNotAllowed: 'Drag & drop files only! Skipped {n} dropped folder(s).',
+ msgImageWidthSmall: 'Pildi laius peab olema vähemalt {size} px.',
+ msgImageHeightSmall: 'Pildi kõrgus peab olema vähemalt {size} px.',
+ msgImageWidthLarge: 'Width of image file "{name}" cannot exceed {size} px.',
+ msgImageHeightLarge: 'Height of image file "{name}" cannot exceed {size} px.',
+ msgImageResizeError: 'Could not get the image dimensions to resize.',
+ msgImageResizeException: 'Error while resizing the image.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Lohista failid siia &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: 'Eemalda fail',
+ uploadTitle: 'Salvesta fail',
+ uploadRetryTitle: 'Retry upload',
+ zoomTitle: 'Vaata detaile',
+ dragTitle: 'Liiguta / Korralda',
+ indicatorNewTitle: 'Pole veel salvestatud',
+ indicatorSuccessTitle: 'Uploaded',
+ indicatorErrorTitle: 'Salvestamise viga',
+ indicatorLoadingTitle: 'Salvestan ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'View previous file',
+ next: 'View next file',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery); \ No newline at end of file
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/fa.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/fa.js
new file mode 100644
index 00000000..ca59bbb8
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/fa.js
@@ -0,0 +1,101 @@
+/*!
+ * FileInput Persian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author Milad Nekofar <milad@nekofar.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['fa'] = {
+ fileSingle: 'فایل',
+ filePlural: 'فایل‌ها',
+ browseLabel: 'مرور &hellip;',
+ removeLabel: 'حذف',
+ removeTitle: 'پاکسازی فایل‌های انتخاب شده',
+ cancelLabel: 'لغو',
+ cancelTitle: 'لغو بارگزاری جاری',
+ uploadLabel: 'بارگذاری',
+ uploadTitle: 'بارگذاری فایل‌های انتخاب شده',
+ msgNo: 'نه',
+ msgNoFilesSelected: 'هیچ فایلی انتخاب نشده است',
+ msgCancelled: 'لغو شد',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'نمایش با جزییات',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'فایل "{name}" (<b>{size} کیلوبایت</b>) خیلی کوچک است و باید از <b>{minSize} کیلوبایت بزرگتر باشد</b>.',
+ msgSizeTooLarge: 'فایل "{name}" (<b>{size} کیلوبایت</b>) از حداکثر مجاز <b>{maxSize} کیلوبایت</b> بزرگتر است.',
+ msgFilesTooLess: 'شما باید حداقل <b>{n}</b> {files} فایل برای بارگذاری انتخاب کنید.',
+ msgFilesTooMany: 'تعداد فایل‌های انتخاب شده برای بارگذاری <b>({n})</b> از حداکثر مجاز عبور کرده است <b>{m}</b>.',
+ msgFileNotFound: 'فایل "{name}" یافت نشد!',
+ msgFileSecured: 'محدودیت های امنیتی مانع خواندن فایل "{name}" است.',
+ msgFileNotReadable: 'فایل "{name}" قابل نوشتن نیست.',
+ msgFilePreviewAborted: 'پیش نمایش فایل "{name}". به مشکل خورد',
+ msgFilePreviewError: 'در هنگام خواندن فایل "{name}" خطایی رخ داد.',
+ msgInvalidFileName: 'کاراکترهای غیرمجاز و یا ناشناخته در نام فایل "{name}".',
+ msgInvalidFileType: 'نوع فایل "{name}" معتبر نیست. فقط "{types}" پشیبانی می‌شوند.',
+ msgInvalidFileExtension: 'پسوند فایل "{name}" معتبر نیست. فقط "{extensions}" پشتیبانی می‌شوند.',
+ msgFileTypes: {
+ 'image': 'عکس',
+ 'html': 'اچ تا ام ال',
+ 'text': 'متن',
+ 'video': 'ویدئو',
+ 'audio': 'صدا',
+ 'flash': 'فلش',
+ 'pdf': 'پی دی اف',
+ 'object': 'دیگر'
+ },
+ msgUploadAborted: 'بارگذاری فایل به مشکل خورد.',
+ msgUploadThreshold: 'در حال پردازش...',
+ msgUploadBegin: 'در حال شروع...',
+ msgUploadEnd: 'انجام شد',
+ msgUploadEmpty: 'هیچ داده معتبری برای بارگذاری موجود نیست.',
+ msgUploadError: 'Error',
+ msgValidationError: 'خطای اعتبار سنجی',
+ msgLoading: 'بارگیری فایل {index} از {files} &hellip;',
+ msgProgress: 'بارگیری فایل {index} از {files} - {name} - {percent}% تمام شد.',
+ msgSelected: '{n} {files} انتخاب شده',
+ msgFoldersNotAllowed: 'فقط فایل‌ها را بکشید و رها کنید! {n} پوشه نادیده گرفته شد.',
+ msgImageWidthSmall: 'عرض فایل تصویر "{name}" باید حداقل {size} پیکسل باشد.',
+ msgImageHeightSmall: 'ارتفاع فایل تصویر "{name}" باید حداقل {size} پیکسل باشد.',
+ msgImageWidthLarge: 'عرض فایل تصویر "{name}" نمیتواند از {size} پیکسل بیشتر باشد.',
+ msgImageHeightLarge: 'ارتفاع فایل تصویر "{name}" نمی‌تواند از {size} پیکسل بیشتر باشد.',
+ msgImageResizeError: 'یافت نشد ابعاد تصویر را برای تغییر اندازه.',
+ msgImageResizeException: 'خطا در هنگام تغییر اندازه تصویر.<pre>{errors}</pre>',
+ msgAjaxError: 'به نظر مشکلی در حین {operation} روی داده است. لطفا دوباره تلاش کنید!',
+ msgAjaxProgressError: '{operation} لغو شد',
+ ajaxOperations: {
+ deleteThumb: 'حذف فایل',
+ uploadThumb: 'بارگذاری فایل',
+ uploadBatch: 'بارگذاری جمعی فایلها',
+ uploadExtra: 'بارگذاری با کمک فُرم'
+ },
+ dropZoneTitle: 'فایل‌ها را بکشید و در اینجا رها کنید &hellip;',
+ dropZoneClickTitle: '<br>(یا برای انتخاب {files} کلیک کنید)',
+ fileActionSettings: {
+ removeTitle: 'حذف فایل',
+ uploadTitle: 'آپلود فایل',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'دیدن جزئیات',
+ dragTitle: 'جابجایی / چیدمان',
+ indicatorNewTitle: 'آپلود نشده است',
+ indicatorSuccessTitle: 'آپلود شده',
+ indicatorErrorTitle: 'بارگذاری خطا',
+ indicatorLoadingTitle: 'آپلود ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'مشاهده فایل قبلی',
+ next: 'مشاهده فایل بعدی',
+ toggleheader: 'نمایش عنوان',
+ fullscreen: 'نمایش تمام صفحه',
+ borderless: 'نمایش حاشیه',
+ close: 'بستن نمایش با جزییات'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/fi.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/fi.js
new file mode 100644
index 00000000..19317b54
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/fi.js
@@ -0,0 +1,91 @@
+/*!
+ * FileInput Finnish Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales.fi = {
+ fileSingle: 'tiedosto',
+ filePlural: 'tiedostot',
+ browseLabel: 'Selaa &hellip;',
+ removeLabel: 'Poista',
+ removeTitle: 'Tyhj&auml;nn&auml; valitut tiedostot',
+ cancelLabel: 'Peruuta',
+ cancelTitle: 'Peruuta lataus',
+ uploadLabel: 'Lataa',
+ uploadTitle: 'Lataa valitut tiedostot',
+ msgNoFilesSelected: '',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Tiedosto "{name}" (<b>{size} Kt</b>) ylitt&auml;&auml; suurimman sallitun tiedoston koon, joka on <b>{maxSize} Kt</b>. Yrit&auml; uudelleen!',
+ msgFilesTooLess: 'V&auml;hint&auml;&auml;n <b>{n}</b> {files} tiedostoa on valittava ladattavaksi. Ole hyv&auml; ja yrit&auml; uudelleen!',
+ msgFilesTooMany: 'Valittujen tiedostojen lukum&auml;&auml;r&auml; <b>({n})</b> ylitt&auml;&auml; suurimman sallitun m&auml;&auml;r&auml;n <b>{m}</b>. Ole hyv&auml; ja yrit&auml; uudelleen!',
+ msgFileNotFound: 'Tiedostoa "{name}" ei l&ouml;ydy!',
+ msgFileSecured: 'Tietoturvarajoitukset est&auml;v&auml;t tiedoston "{name}" lukemisen.',
+ msgFileNotReadable: 'Tiedosto "{name}" ei ole luettavissa.',
+ msgFilePreviewAborted: 'Tiedoston "{name}" esikatselu keskeytetty.',
+ msgFilePreviewError: 'Virhe on tapahtunut luettaessa tiedostoa "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'Tiedosto "{name}" on v&auml;&auml;r&auml;n tyyppinen. Ainoastaan tiedostot tyyppi&auml; "{types}" ovat tuettuja.',
+ msgInvalidFileExtension: 'Tiedoston "{name}" tarkenne on ep&auml;kelpo. Ainoastaan tarkenteet "{extensions}" ovat tuettuja.',
+ msgFileTypes: {
+ 'image': 'Kuva',
+ 'html': 'HTML',
+ 'text': 'Teksti',
+ 'video': 'Video',
+ 'audio': 'Ääni',
+ 'flash': 'Flash',
+ 'pdf': 'PDF',
+ 'object': 'Olio'
+ },
+ msgUploadThreshold: 'Käsitellään...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'Ei ladattavaa dataa.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Tiedoston latausvirhe',
+ msgLoading: 'Ladataan tiedostoa {index} / {files} &hellip;',
+ msgProgress: 'Ladataan tiedostoa {index} / {files} - {name} - {percent}% valmistunut.',
+ msgSelected: '{n} tiedostoa valittu',
+ msgFoldersNotAllowed: 'Raahaa ja pudota ainoastaan tiedostoja! Ohitettu {n} raahattua kansiota.',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Raahaa ja pudota tiedostot t&auml;h&auml;n &hellip;',
+ dropZoneClickTitle: '<br>(tai valitse hiirellä {files})',
+ fileActionSettings: {
+ removeTitle: 'Poista tiedosto',
+ uploadTitle: 'Upload file',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Yksityiskohdat',
+ dragTitle: 'Siirrä / Järjestele',
+ indicatorNewTitle: 'Ei ladattu',
+ indicatorSuccessTitle: 'Ladattu',
+ indicatorErrorTitle: 'Lataus epäonnistui',
+ indicatorLoadingTitle: 'Ladataan ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Seuraava tiedosto',
+ next: 'Edellinen tiedosto',
+ toggleheader: 'Näytä otsikko',
+ fullscreen: 'Kokonäytön tila',
+ borderless: 'Rajaton tila',
+ close: 'Sulje esikatselu'
+ }
+ };
+
+ $.extend($.fn.fileinput.defaults, $.fn.fileinputLocales.fi);
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/fr.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/fr.js
new file mode 100644
index 00000000..81a77042
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/fr.js
@@ -0,0 +1,99 @@
+/*!
+ * FileInput French Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['fr'] = {
+ fileSingle: 'fichier',
+ filePlural: 'fichiers',
+ browseLabel: 'Parcourir&hellip;',
+ removeLabel: 'Retirer',
+ removeTitle: 'Retirer les fichiers sélectionnés',
+ cancelLabel: 'Annuler',
+ cancelTitle: "Annuler l'envoi en cours",
+ uploadLabel: 'Transférer',
+ uploadTitle: 'Transférer les fichiers sélectionnés',
+ msgNo: 'Non',
+ msgNoFilesSelected: '',
+ msgCancelled: 'Annulé',
+ msgPlaceholder: 'Sélectionner le(s) {files}...',
+ msgZoomModalHeading: 'Aperçu détaillé',
+ msgFileRequired: 'Vous devez sélectionner un fichier à uploader.',
+ msgSizeTooSmall: 'Le fichier "{name}" (<b>{size} KB</b>) est inférieur à la taille minimale de <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Le fichier "{name}" (<b>{size} Ko</b>) dépasse la taille maximale autorisée qui est de <b>{maxSize} Ko</b>.',
+ msgFilesTooLess: 'Vous devez sélectionner au moins <b>{n}</b> {files} à transmettre.',
+ msgFilesTooMany: 'Le nombre de fichier sélectionné <b>({n})</b> dépasse la quantité maximale autorisée qui est de <b>{m}</b>.',
+ msgFileNotFound: 'Le fichier "{name}" est introuvable !',
+ msgFileSecured: "Des restrictions de sécurité vous empêchent d'accéder au fichier \"{name}\".",
+ msgFileNotReadable: 'Le fichier "{name}" est illisible.',
+ msgFilePreviewAborted: 'Prévisualisation du fichier "{name}" annulée.',
+ msgFilePreviewError: 'Une erreur est survenue lors de la lecture du fichier "{name}".',
+ msgInvalidFileName: 'Caractères invalides ou non supportés dans le nom de fichier "{name}".',
+ msgInvalidFileType: 'Type de document invalide pour "{name}". Seulement les documents de type "{types}" sont autorisés.',
+ msgInvalidFileExtension: 'Extension invalide pour le fichier "{name}". Seules les extensions "{extensions}" sont autorisées.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Le transfert du fichier a été interrompu',
+ msgUploadThreshold: 'En cours...',
+ msgUploadBegin: 'Initialisation...',
+ msgUploadEnd: 'Terminé',
+ msgUploadEmpty: 'Aucune donnée valide disponible pour transmission.',
+ msgUploadError: 'Erreur',
+ msgValidationError: 'Erreur de validation',
+ msgLoading: 'Transmission du fichier {index} sur {files}&hellip;',
+ msgProgress: 'Transmission du fichier {index} sur {files} - {name} - {percent}%.',
+ msgSelected: '{n} {files} sélectionné(s)',
+ msgFoldersNotAllowed: 'Glissez et déposez uniquement des fichiers ! {n} répertoire(s) exclu(s).',
+ msgImageWidthSmall: 'La largeur de l\'image "{name}" doit être d\'au moins {size} px.',
+ msgImageHeightSmall: 'La hauteur de l\'image "{name}" doit être d\'au moins {size} px.',
+ msgImageWidthLarge: 'La largeur de l\'image "{name}" ne peut pas dépasser {size} px.',
+ msgImageHeightLarge: 'La hauteur de l\'image "{name}" ne peut pas dépasser {size} px.',
+ msgImageResizeError: "Impossible d'obtenir les dimensions de l'image à redimensionner.",
+ msgImageResizeException: "Erreur lors du redimensionnement de l'image.<pre>{errors}</pre>",
+ msgAjaxError: "Une erreur s'est produite pendant l'opération de {operation}. Veuillez réessayer plus tard.",
+ msgAjaxProgressError: 'L\'opération "{operation}" a échoué',
+ ajaxOperations: {
+ deleteThumb: 'suppression du fichier',
+ uploadThumb: 'transfert du fichier',
+ uploadBatch: 'transfert des fichiers',
+ uploadExtra: 'soumission des données de formulaire'
+ },
+ dropZoneTitle: 'Glissez et déposez les fichiers ici&hellip;',
+ dropZoneClickTitle: '<br>(ou cliquez pour sélectionner manuellement)',
+ fileActionSettings: {
+ removeTitle: 'Supprimer le fichier',
+ uploadTitle: 'Transférer le fichier',
+ uploadRetryTitle: 'Relancer le transfert',
+ zoomTitle: 'Voir les détails',
+ dragTitle: 'Déplacer / Réarranger',
+ indicatorNewTitle: 'Pas encore transféré',
+ indicatorSuccessTitle: 'Posté',
+ indicatorErrorTitle: 'Ajouter erreur',
+ indicatorLoadingTitle: 'En cours...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Voir le fichier précédent',
+ next: 'Voir le fichier suivant',
+ toggleheader: 'Masquer le titre',
+ fullscreen: 'Mode plein écran',
+ borderless: 'Mode cinéma',
+ close: "Fermer l'aperçu"
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/gl.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/gl.js
new file mode 100644
index 00000000..6efc4f61
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/gl.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Galician Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['gl'] = {
+ fileSingle: 'arquivo',
+ filePlural: 'arquivos',
+ browseLabel: 'Examinar &hellip;',
+ removeLabel: 'Quitar',
+ removeTitle: 'Quitar aquivos seleccionados',
+ cancelLabel: 'Cancelar',
+ cancelTitle: 'Abortar a subida en curso',
+ uploadLabel: 'Subir arquivo',
+ uploadTitle: 'Subir arquivos seleccionados',
+ msgNo: 'Non',
+ msgNoFilesSelected: 'Non hay arquivos seleccionados',
+ msgCancelled: 'Cancelado',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Vista previa detallada',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'O arquivo "{name}" (<b>{size} KB</b>) é demasiado pequeño e debe ser maior de <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'El arquivo "{name}" (<b>{size} KB</b>) excede o tamaño máximo permitido de <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Debe seleccionar al menos <b>{n}</b> {files} a cargar.',
+ msgFilesTooMany: 'O número de arquivos seleccionados a cargar <b>({n})</b> excede do límite máximo permitido de <b>{m}</b>.',
+ msgFileNotFound: 'Arquivo "{name}" non encontrado.',
+ msgFileSecured: 'Non é posible acceder o arquivo "{name}" porque estará sendo usado por outra aplicación ou non teñamos permisos de lectura.',
+ msgFileNotReadable: 'Non é posible acceder o archivo "{name}".',
+ msgFilePreviewAborted: 'Previsualización do arquivo "{name}" cancelada.',
+ msgFilePreviewError: 'Ocurriu un erro mentras se lía o arquivo "{name}".',
+ msgInvalidFileName: 'Caracteres non válidos o no soportados no nome do arquivos "{name}".',
+ msgInvalidFileType: 'Tipo de archivo no válido para "{name}". Sólo se permiten arquivos do tipo "{types}".',
+ msgInvalidFileExtension: 'Extensión de arquivo non válido para "{name}". Só se permiten arquivos "{extensions}".',
+ msgFileTypes: {
+ 'image': 'imaxe',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'A carga de arquivos cancelouse',
+ msgUploadThreshold: 'Procesando...',
+ msgUploadBegin: 'Inicialicando...',
+ msgUploadEnd: 'Feito',
+ msgUploadEmpty: 'Non existen datos válidos para o envío.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Erro de validación',
+ msgLoading: 'Subindo arquivo {index} de {files} &hellip;',
+ msgProgress: 'Subiendo arquivo {index} de {files} - {name} - {percent}% completado.',
+ msgSelected: '{n} {files} seleccionado(s)',
+ msgFoldersNotAllowed: 'Arrastra e solta únicamente arquivoa. Omitida(s) {n} carpeta(s).',
+ msgImageWidthSmall: 'O ancho da imaxe "{name}" debe ser de al menos {size} px.',
+ msgImageHeightSmall: 'A altura de la imaxe "{name}" debe ser de al menos {size} px.',
+ msgImageWidthLarge: 'El ancho de la imaxe "{name}" no puede exceder de {size} px.',
+ msgImageHeightLarge: 'La altura de la imaxe "{name}" no puede exceder de {size} px.',
+ msgImageResizeError: 'No se pudieron obtener las dimensiones de la imaxe para cambiar el tamaño.',
+ msgImageResizeException: 'Erro o cambiar o tamaño da imaxe.<pre>{errors}</pre>',
+ msgAjaxError: 'Algo foi mal ca operación {operation}. Por favor, intentao de novo mais tarde.',
+ msgAjaxProgressError: 'A operación {operation} fallou',
+ ajaxOperations: {
+ deleteThumb: 'Arquivo borrado',
+ uploadThumb: 'Arquivo subido',
+ uploadBatch: 'Datos subidos en lote',
+ uploadExtra: 'Datos do formulario subidos'
+ },
+ dropZoneTitle: 'Arrasta e solte aquí os arquivos &hellip;',
+ dropZoneClickTitle: '<br>(ou fai clic para seleccionar {files})',
+ fileActionSettings: {
+ removeTitle: 'Eliminar arquivo',
+ uploadTitle: 'Subir arquivo',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Ver detalles',
+ dragTitle: 'Mover / Reordenar',
+ indicatorNewTitle: 'Non subido todavía',
+ indicatorSuccessTitle: 'Subido',
+ indicatorErrorTitle: 'Erro o subir',
+ indicatorLoadingTitle: 'Subiendo...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Ver arquivo anterior',
+ next: 'Ver arquivo siguinte',
+ toggleheader: 'Mostrar encabezado',
+ fullscreen: 'Mostrar a pantalla completa',
+ borderless: 'Activar o modo sen bordes',
+ close: 'Cerrar vista detallada'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/hu.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/hu.js
new file mode 100644
index 00000000..f9ef93ae
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/hu.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Hungarian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['hu'] = {
+ fileSingle: 'fájl',
+ filePlural: 'fájlok',
+ browseLabel: 'Tallóz &hellip;',
+ removeLabel: 'Eltávolít',
+ removeTitle: 'Kijelölt fájlok törlése',
+ cancelLabel: 'Mégse',
+ cancelTitle: 'Feltöltés megszakítása',
+ uploadLabel: 'Feltöltés',
+ uploadTitle: 'Kijelölt fájlok feltöltése',
+ msgNo: 'Nem',
+ msgNoFilesSelected: 'Nincs fájl kiválasztva',
+ msgCancelled: 'Megszakítva',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Részletes Előnézet',
+ msgFileRequired: 'Kötelező fájlt kiválasztani a feltöltéshez.',
+ msgSizeTooSmall: 'A fájl: "{name}" (<b>{size} KB</b>) mérete túl kicsi, nagyobbnak kell lennie, mint <b>{minSize} KB</b>.',
+ msgSizeTooLarge: '"{name}" fájl (<b>{size} KB</b>) mérete nagyobb a megengedettnél <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Legalább <b>{n}</b> {files} ki kell választania a feltöltéshez.',
+ msgFilesTooMany: 'A feltölteni kívánt fájlok száma <b>({n})</b> elérte a megengedett maximumot <b>{m}</b>.',
+ msgFileNotFound: '"{name}" fájl nem található!',
+ msgFileSecured: 'Biztonsági beállítások nem engedik olvasni a fájlt "{name}".',
+ msgFileNotReadable: '"{name}" fájl nem olvasható.',
+ msgFilePreviewAborted: '"{name}" fájl feltöltése megszakítva.',
+ msgFilePreviewError: 'Hiba lépett fel a "{name}" fájl olvasása közben.',
+ msgInvalidFileName: 'Hibás vagy nem támogatott karakterek a fájl nevében "{name}".',
+ msgInvalidFileType: 'Nem megengedett fájl "{name}". Csak a "{types}" fájl típusok támogatottak.',
+ msgInvalidFileExtension: 'Nem megengedett kiterjesztés / fájltípus "{name}". Csak a "{extensions}" kiterjesztés(ek) / fájltípus(ok) támogatottak.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'A fájl feltöltés megszakítva',
+ msgUploadThreshold: 'Folyamatban...',
+ msgUploadBegin: 'Inicializálás...',
+ msgUploadEnd: 'Kész',
+ msgUploadEmpty: 'Nincs érvényes adat a feltöltéshez.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Érvényesítés hiba',
+ msgLoading: '{index} / {files} töltése &hellip;',
+ msgProgress: 'Feltöltés: {index} / {files} - {name} - {percent}% kész.',
+ msgSelected: '{n} {files} kiválasztva.',
+ msgFoldersNotAllowed: 'Csak fájlokat húzzon ide! Kihagyva {n} könyvtár.',
+ msgImageWidthSmall: 'A kép szélességének "{name}" legalább {size} pixelnek kell lennie.',
+ msgImageHeightSmall: 'A kép magasságának "{name}" legalább {size} pixelnek kell lennie.',
+ msgImageWidthLarge: 'A kép szélessége "{name}" nem haladhatja meg a {size} pixelt.',
+ msgImageHeightLarge: 'A kép magassága "{name}" nem haladhatja meg a {size} pixelt.',
+ msgImageResizeError: 'Nem lehet megállapítani a kép méreteit az átméretezéshez.',
+ msgImageResizeException: 'Hiba történt a méretezés közben.<pre>{errors}</pre>',
+ msgAjaxError: 'Hiba történt a művelet közben ({operation}). Kérjük, próbálja később!',
+ msgAjaxProgressError: 'Hiba! ({operation})',
+ ajaxOperations: {
+ deleteThumb: 'fájl törlés',
+ uploadThumb: 'fájl feltöltés',
+ uploadBatch: 'csoportos fájl feltöltés',
+ uploadExtra: 'űrlap adat feltöltés'
+ },
+ dropZoneTitle: 'Húzzon ide fájlokat &hellip;',
+ dropZoneClickTitle: '<br>(vagy kattintson ide a {files} tallózásához...)',
+ fileActionSettings: {
+ removeTitle: 'A fájl eltávolítása',
+ uploadTitle: 'fájl feltöltése',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Részletek megtekintése',
+ dragTitle: 'Mozgatás / Átrendezés',
+ indicatorNewTitle: 'Nem feltöltött',
+ indicatorSuccessTitle: 'Feltöltött',
+ indicatorErrorTitle: 'Feltöltés hiba',
+ indicatorLoadingTitle: 'Feltöltés ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Elöző fájl megnézése',
+ next: 'Következő fájl megnézése',
+ toggleheader: 'Fejléc mutatása',
+ fullscreen: 'Teljes képernyős mód bekapcsolása',
+ borderless: 'Keret nélküli ablak mód bekapcsolása',
+ close: 'Részletes előnézet bezárása'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/id.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/id.js
new file mode 100644
index 00000000..06c416b3
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/id.js
@@ -0,0 +1,101 @@
+/*!
+ * FileInput Indonesian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author Bambang Riswanto <bamz3r@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['id'] = {
+ fileSingle: 'berkas',
+ filePlural: 'berkas',
+ browseLabel: 'Pilih File &hellip;',
+ removeLabel: 'Hapus',
+ removeTitle: 'Hapus berkas terpilih',
+ cancelLabel: 'Batal',
+ cancelTitle: 'Batalkan proses pengunggahan',
+ uploadLabel: 'Unggah',
+ uploadTitle: 'Unggah berkas terpilih',
+ msgNo: 'Tidak',
+ msgNoFilesSelected: '',
+ msgCancelled: 'Dibatalkan',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Pratinjau terperinci',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Berkas "{name}" (<b>{size} KB</b>) melebihi ukuran upload maksimal yaitu <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Anda harus memilih setidaknya <b>{n}</b> {files} untuk diunggah.',
+ msgFilesTooMany: '<b>({n})</b> berkas yang dipilih untuk diunggah melebihi ukuran upload maksimal yaitu <b>{m}</b>.',
+ msgFileNotFound: 'Berkas "{name}" tak ditemukan!',
+ msgFileSecured: 'Sistem keamanan mencegah untuk membaca berkas "{name}".',
+ msgFileNotReadable: 'Berkas "{name}" tak dapat dibaca.',
+ msgFilePreviewAborted: 'Pratinjau untuk berkas "{name}" dibatalkan.',
+ msgFilePreviewError: 'Kesalahan saat membaca berkas "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'Jenis berkas "{name}" tidak sah. Hanya berkas "{types}" yang didukung.',
+ msgInvalidFileExtension: 'Ekstensi berkas "{name}" tidak sah. Hanya ekstensi "{extensions}" yang didukung.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Pengunggahan berkas dibatalkan',
+ msgUploadThreshold: 'Processing...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Kesalahan validasi',
+ msgLoading: 'Memuat {index} dari {files} berkas &hellip;',
+ msgProgress: 'Memuat {index} dari {files} berkas - {name} - {percent}% selesai.',
+ msgSelected: '{n} {files} dipilih',
+ msgFoldersNotAllowed: 'Hanya tahan dan lepas file saja! {n} folder diabaikan.',
+ msgImageWidthSmall: 'Lebar dari gambar "{name}" harus sekurangnya {size} px.',
+ msgImageHeightSmall: 'Tinggi dari gambar "{name}" harus sekurangnya {size} px.',
+ msgImageWidthLarge: 'Lebar dari gambar "{name}" tak boleh melebihi {size} px.',
+ msgImageHeightLarge: 'Tinggi dari gambar "{name}" tak boleh melebihi {size} px.',
+ msgImageResizeError: 'Tak dapat menentukan dimensi gambar untuk mengubah ukuran.',
+ msgImageResizeException: 'Kesalahan saat mengubah ukuran gambar.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Tarik dan lepaskan berkas disini &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: 'Hapus berkas',
+ uploadTitle: 'Unggah berkas',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Tampilkan Rincian',
+ dragTitle: 'Move / Rearrange',
+ indicatorNewTitle: 'Belum diunggah',
+ indicatorSuccessTitle: 'Sudah diunggah',
+ indicatorErrorTitle: 'Kesalahan pengunggahan',
+ indicatorLoadingTitle: 'Mengunggah ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'View previous file',
+ next: 'View next file',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/it.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/it.js
new file mode 100644
index 00000000..73ee7663
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/it.js
@@ -0,0 +1,102 @@
+/*!
+ * FileInput Italian Translation
+ *
+ * Author: Lorenzo Milesi <maxxer@yetopen.it>
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['it'] = {
+ fileSingle: 'file',
+ filePlural: 'file',
+ browseLabel: 'Sfoglia&hellip;',
+ removeLabel: 'Rimuovi',
+ removeTitle: 'Rimuovi i file selezionati',
+ cancelLabel: 'Annulla',
+ cancelTitle: 'Annulla i caricamenti in corso',
+ uploadLabel: 'Carica',
+ uploadTitle: 'Carica i file selezionati',
+ msgNo: 'No',
+ msgNoFilesSelected: 'Nessun file selezionato',
+ msgCancelled: 'Annullato',
+ msgPlaceholder: 'Seleziona {files}...',
+ msgZoomModalHeading: 'Anteprima dettagliata',
+ msgFileRequired: 'Devi selezionare un file da caricare.',
+ msgSizeTooSmall: 'Il file "{name}" (<b>{size} KB</b>) è troppo piccolo, deve essere almeno di <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Il file "{name}" (<b>{size} KB</b>) eccede la dimensione massima di caricamento di <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Devi selezionare almeno <b>{n}</b> {files} da caricare.',
+ msgFilesTooMany: 'Il numero di file selezionati per il caricamento <b>({n})</b> eccede il numero massimo di file accettati <b>{m}</b>.',
+ msgFileNotFound: 'File "{name}" non trovato!',
+ msgFileSecured: 'Restrizioni di sicurezza impediscono la lettura del file "{name}".',
+ msgFileNotReadable: 'Il file "{name}" non è leggibile.',
+ msgFilePreviewAborted: 'Generazione anteprima per "{name}" annullata.',
+ msgFilePreviewError: 'Errore durante la lettura del file "{name}".',
+ msgInvalidFileName: 'Carattere non valido o non supportato nel file "{name}".',
+ msgInvalidFileType: 'Tipo non valido per il file "{name}". Sono ammessi solo file di tipo "{types}".',
+ msgInvalidFileExtension: 'Estensione non valida per il file "{name}". Sono ammessi solo file con estensione "{extensions}".',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Il caricamento del file è stato interrotto',
+ msgUploadThreshold: 'In lavorazione...',
+ msgUploadBegin: 'Inizializzazione...',
+ msgUploadEnd: 'Fatto',
+ msgUploadEmpty: 'Dati non disponibili',
+ msgUploadError: 'Errore',
+ msgValidationError: 'Errore di convalida',
+ msgLoading: 'Caricamento file {index} di {files}&hellip;',
+ msgProgress: 'Caricamento file {index} di {files} - {name} - {percent}% completato.',
+ msgSelected: '{n} {files} selezionati',
+ msgFoldersNotAllowed: 'Trascina solo file! Ignorata/e {n} cartella/e.',
+ msgImageWidthSmall: 'La larghezza dell\'immagine "{name}" deve essere di almeno {size} px.',
+ msgImageHeightSmall: 'L\'altezza dell\'immagine "{name}" deve essere di almeno {size} px.',
+ msgImageWidthLarge: 'La larghezza dell\'immagine "{name}" non può superare {size} px.',
+ msgImageHeightLarge: 'L\'altezza dell\'immagine "{name}" non può superare {size} px.',
+ msgImageResizeError: 'Impossibile ottenere le dimensioni dell\'immagine per ridimensionare.',
+ msgImageResizeException: 'Errore durante il ridimensionamento dell\'immagine.<pre>{errors}</pre>',
+ msgAjaxError: 'Qualcosa non ha funzionato con l\'operazione {operation}. Per favore riprova più tardi!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'eliminazione file',
+ uploadThumb: 'caricamento file',
+ uploadBatch: 'caricamento file in batch',
+ uploadExtra: 'upload dati del form'
+ },
+ dropZoneTitle: 'Trascina i file qui&hellip;',
+ dropZoneClickTitle: '<br>(o clicca per selezionare {files})',
+ fileActionSettings: {
+ removeTitle: 'Rimuovere il file',
+ uploadTitle: 'Caricare un file',
+ uploadRetryTitle: 'Riprova il caricamento',
+ downloadTitle: 'Scarica file',
+ zoomTitle: 'Guarda i dettagli',
+ dragTitle: 'Muovi / Riordina',
+ indicatorNewTitle: 'Non ancora caricato',
+ indicatorSuccessTitle: 'Caricati',
+ indicatorErrorTitle: 'Carica Errore',
+ indicatorLoadingTitle: 'Caricamento ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Vedi il file precedente',
+ next: 'Vedi il file seguente',
+ toggleheader: 'Attiva header',
+ fullscreen: 'Attiva full screen',
+ borderless: 'Abilita modalità senza bordi',
+ close: 'Chiudi'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ja.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ja.js
new file mode 100644
index 00000000..3decd7fa
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ja.js
@@ -0,0 +1,109 @@
+/*!
+ * FileInput Japanese Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author Yuta Hoshina <hoshina@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ * slugCallback
+ * \u4e00-\u9fa5 : Kanji (Chinese characters)
+ * \u3040-\u309f : Hiragana (Japanese syllabary)
+ * \u30a0-\u30ff\u31f0-\u31ff : Katakana (including phonetic extension)
+ * \u3200-\u32ff : Enclosed CJK Letters and Months
+ * \uff00-\uffef : Halfwidth and Fullwidth Forms
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['ja'] = {
+ fileSingle: 'ファイル',
+ filePlural: 'ファイル',
+ browseLabel: 'ファイルを選択&hellip;',
+ removeLabel: '削除',
+ removeTitle: '選択したファイルを削除',
+ cancelLabel: 'キャンセル',
+ cancelTitle: 'アップロードをキャンセル',
+ uploadLabel: 'アップロード',
+ uploadTitle: '選択したファイルをアップロード',
+ msgNo: 'いいえ',
+ msgNoFilesSelected: 'ファイルが選択されていません',
+ msgCancelled: 'キャンセル',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'プレビュー',
+ msgFileRequired: 'ファイルを選択してください',
+ msgSizeTooSmall: 'ファイル"{name}" (<b>{size} KB</b>)はアップロード可能な下限容量<b>{minSize} KB</b>より小さいです',
+ msgSizeTooLarge: 'ファイル"{name}" (<b>{size} KB</b>)はアップロード可能な上限容量<b>{maxSize} KB</b>を超えています',
+ msgFilesTooLess: '最低<b>{n}</b>個の{files}を選択してください',
+ msgFilesTooMany: '選択したファイルの数<b>({n}個)</b>はアップロード可能な上限数<b>({m}個)</b>を超えています',
+ msgFileNotFound: 'ファイル"{name}"はありませんでした',
+ msgFileSecured: 'ファイル"{name}"は読み取り権限がないため取得できません',
+ msgFileNotReadable: 'ファイル"{name}"は読み込めません',
+ msgFilePreviewAborted: 'ファイル"{name}"のプレビューを中止しました',
+ msgFilePreviewError: 'ファイル"{name}"の読み込み中にエラーが発生しました',
+ msgInvalidFileName: 'ファイル名に無効な文字が含まれています "{name}".',
+ msgInvalidFileType: '"{name}"は無効なファイル形式です。"{types}"形式のファイルのみサポートしています',
+ msgInvalidFileExtension: '"{name}"は無効な拡張子です。拡張子が"{extensions}"のファイルのみサポートしています',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'ファイルのアップロードが中止されました',
+ msgUploadThreshold: '処理中...',
+ msgUploadBegin: '初期化中...',
+ msgUploadEnd: '完了',
+ msgUploadEmpty: 'アップロードに有効なデータがありません',
+ msgUploadError: 'エラー',
+ msgValidationError: '検証エラー',
+ msgLoading: '{files}個中{index}個目のファイルを読み込み中&hellip;',
+ msgProgress: '{files}個中{index}個のファイルを読み込み中 - {name} - {percent}% 完了',
+ msgSelected: '{n}個の{files}を選択',
+ msgFoldersNotAllowed: 'ドラッグ&ドロップが可能なのはファイルのみです。{n}個のフォルダ-は無視されました',
+ msgImageWidthSmall: '画像ファイル"{name}"の幅が小さすぎます。画像サイズの幅は少なくとも{size}px必要です',
+ msgImageHeightSmall: '画像ファイル"{name}"の高さが小さすぎます。画像サイズの高さは少なくとも{size}px必要です',
+ msgImageWidthLarge: '画像ファイル"{name}"の幅がアップロード可能な画像サイズ({size}px)を超えています',
+ msgImageHeightLarge: '画像ファイル"{name}"の高さがアップロード可能な画像サイズ({size}px)を超えています',
+ msgImageResizeError: 'リサイズ時に画像サイズが取得できませんでした',
+ msgImageResizeException: '画像のリサイズ時にエラーが発生しました。<pre>{errors}</pre>',
+ msgAjaxError: '{operation}実行中にエラーが発生しました。時間をおいてもう一度お試しください。',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'ファイル削除',
+ uploadThumb: 'ファイルアップロード',
+ uploadBatch: '一括ファイルアップロード',
+ uploadExtra: 'フォームデータアップロード'
+ },
+ dropZoneTitle: 'ファイルをドラッグ&ドロップ&hellip;',
+ dropZoneClickTitle: '<br>(または クリックして{files}を選択 )',
+ slugCallback: function(text) {
+ return text ? text.split(/(\\|\/)/g).pop().replace(/[^\w\u4e00-\u9fa5\u3040-\u309f\u30a0-\u30ff\u31f0-\u31ff\u3200-\u32ff\uff00-\uffef\-.\\\/ ]+/g, '') : '';
+ },
+ fileActionSettings: {
+ removeTitle: 'ファイルを削除',
+ uploadTitle: 'ファイルをアップロード',
+ uploadRetryTitle: '再アップロード',
+ zoomTitle: 'プレビュー',
+ dragTitle: '移動 / 再配置',
+ indicatorNewTitle: 'まだアップロードされていません',
+ indicatorSuccessTitle: 'アップロード済み',
+ indicatorErrorTitle: 'アップロード失敗',
+ indicatorLoadingTitle: 'アップロード中...'
+ },
+ previewZoomButtonTitles: {
+ prev: '前のファイルを表示',
+ next: '次のファイルを表示',
+ toggleheader: 'ファイル情報の表示/非表示',
+ fullscreen: 'フルスクリーン表示の開始/終了',
+ borderless: 'フルウィンドウ表示の開始/終了',
+ close: 'プレビューを閉じる'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ka.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ka.js
new file mode 100644
index 00000000..c6c0a230
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ka.js
@@ -0,0 +1,101 @@
+/*!
+ * FileInput Georgian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author Avtandil Kikabidze aka LONGMAN <akalongman@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['ka'] = {
+ fileSingle: 'ფაილი',
+ filePlural: 'ფაილები',
+ browseLabel: 'არჩევა &hellip;',
+ removeLabel: 'წაშლა',
+ removeTitle: 'არჩეული ფაილების წაშლა',
+ cancelLabel: 'გაუქმება',
+ cancelTitle: 'მიმდინარე ატვირთვის გაუქმება',
+ uploadLabel: 'ატვირთვა',
+ uploadTitle: 'არჩეული ფაილების ატვირთვა',
+ msgNo: 'არა',
+ msgNoFilesSelected: 'ფაილები არ არის არჩეული',
+ msgCancelled: 'გაუქმებულია',
+ msgPlaceholder: 'აირჩიეთ {files}...',
+ msgZoomModalHeading: 'დეტალურად ნახვა',
+ msgFileRequired: 'ატვირთვისთვის აუცილებელია ფაილის არჩევა.',
+ msgSizeTooSmall: 'ფაილი "{name}" (<b>{size} KB</b>) არის ძალიან პატარა. მისი ზომა უნდა იყოს არანაკლებ <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'ფაილი "{name}" (<b>{size} KB</b>) აჭარბებს მაქსიმალურ დასაშვებ ზომას <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'უნდა აირჩიოთ მინიმუმ <b>{n}</b> {file} ატვირთვისთვის.',
+ msgFilesTooMany: 'არჩეული ფაილების რაოდენობა <b>({n})</b> აჭარბებს დასაშვებ ლიმიტს <b>{m}</b>.',
+ msgFileNotFound: 'ფაილი "{name}" არ მოიძებნა!',
+ msgFileSecured: 'უსაფრთხოებით გამოწვეული შეზღუდვები კრძალავს ფაილის "{name}" წაკითხვას.',
+ msgFileNotReadable: 'ფაილის "{name}" წაკითხვა შეუძლებელია.',
+ msgFilePreviewAborted: 'პრევიუ გაუქმებულია ფაილისათვის "{name}".',
+ msgFilePreviewError: 'დაფიქსირდა შეცდომა ფაილის "{name}" კითხვისას.',
+ msgInvalidFileName: 'ნაპოვნია დაუშვებელი სიმბოლოები ფაილის "{name}" სახელში.',
+ msgInvalidFileType: 'ფაილს "{name}" გააჩნია დაუშვებელი ტიპი. მხოლოდ "{types}" ტიპის ფაილები არის დაშვებული.',
+ msgInvalidFileExtension: 'ფაილს "{name}" გააჩნია დაუშვებელი გაფართოება. მხოლოდ "{extensions}" გაფართოების ფაილები არის დაშვებული.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'ფაილის ატვირთვა შეწყდა',
+ msgUploadThreshold: 'მუშავდება...',
+ msgUploadBegin: 'ინიციალიზაცია...',
+ msgUploadEnd: 'დასრულებულია',
+ msgUploadEmpty: 'ატვირთვისთვის დაუშვებელი მონაცემები.',
+ msgUploadError: 'ატვირთვის შეცდომა',
+ msgValidationError: 'ვალიდაციის შეცდომა',
+ msgLoading: 'ატვირთვა {index} / {files} &hellip;',
+ msgProgress: 'ფაილის ატვირთვა დასრულებულია {index} / {files} - {name} - {percent}%.',
+ msgSelected: 'არჩეულია {n} {file}',
+ msgFoldersNotAllowed: 'დაშვებულია მხოლოდ ფაილების გადმოთრევა! გამოტოვებულია {n} გადმოთრეული ფოლდერი.',
+ msgImageWidthSmall: 'სურათის "{name}" სიგანე უნდა იყოს არანაკლებ {size} px.',
+ msgImageHeightSmall: 'სურათის "{name}" სიმაღლე უნდა იყოს არანაკლებ {size} px.',
+ msgImageWidthLarge: 'სურათის "{name}" სიგანე არ უნდა აღემატებოდეს {size} px-ს.',
+ msgImageHeightLarge: 'სურათის "{name}" სიმაღლე არ უნდა აღემატებოდეს {size} px-ს.',
+ msgImageResizeError: 'ვერ მოხერხდა სურათის ზომის შეცვლისთვის საჭირო მონაცემების გარკვევა.',
+ msgImageResizeException: 'შეცდომა სურათის ზომის შეცვლისას.<pre>{errors}</pre>',
+ msgAjaxError: 'დაფიქსირდა შეცდომა ოპერაციის {operation} შესრულებისას. ცადეთ მოგვიანებით!',
+ msgAjaxProgressError: 'ვერ მოხერხდა ოპერაციის {operation} შესრულება',
+ ajaxOperations: {
+ deleteThumb: 'ფაილის წაშლა',
+ uploadThumb: 'ფაილის ატვირთვა',
+ uploadBatch: 'ფაილების ატვირთვა',
+ uploadExtra: 'მონაცემების გაგზავნა ფორმიდან'
+ },
+ dropZoneTitle: 'გადმოათრიეთ ფაილები აქ &hellip;',
+ dropZoneClickTitle: '<br>(ან დააჭირეთ რათა აირჩიოთ {files})',
+ fileActionSettings: {
+ removeTitle: 'ფაილის წაშლა',
+ uploadTitle: 'ფაილის ატვირთვა',
+ uploadRetryTitle: 'ატვირთვის გამეორება',
+ downloadTitle: 'ფაილის ჩამოტვირთვა',
+ zoomTitle: 'დეტალურად ნახვა',
+ dragTitle: 'გადაადგილება / მიმდევრობის შეცვლა',
+ indicatorNewTitle: 'ჯერ არ ატვირთულა',
+ indicatorSuccessTitle: 'ატვირთულია',
+ indicatorErrorTitle: 'ატვირთვის შეცდომა',
+ indicatorLoadingTitle: 'ატვირთვა ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'წინა ფაილის ნახვა',
+ next: 'შემდეგი ფაილის ნახვა',
+ toggleheader: 'სათაურის დამალვა',
+ fullscreen: 'მთელ ეკრანზე გაშლა',
+ borderless: 'მთელ გვერდზე გაშლა',
+ close: 'დახურვა'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ko.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ko.js
new file mode 100644
index 00000000..0236124e
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ko.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Korean Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['ko'] = {
+ fileSingle: '파일',
+ filePlural: '파일들',
+ browseLabel: '찾기 &hellip;',
+ removeLabel: '지우기',
+ removeTitle: '선택한 파일들 지우기',
+ cancelLabel: '취소',
+ cancelTitle: '업로드 중단하기',
+ uploadLabel: '업로드',
+ uploadTitle: '선택한 파일 업로드하기',
+ msgNo: '아니요',
+ msgNoFilesSelected: '선택한 파일이 없습니다.',
+ msgCancelled: '취소되었습니다.',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: '자세한 미리보기',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: '파일 "{name}" (<b>{size} KB</b>)이 너무 작습니다. <b>{minSize} KB</b>보다 용량이 커야 합니다..',
+ msgSizeTooLarge: '파일 "{name}" (<b>{size} KB</b>)이 너무 큽니다. 허용 파일 사이즈는 <b>{maxSize} KB</b>.입니다.',
+ msgFilesTooLess: '업로드하기 위해 최소 <b>{n}</b> {files}개의 파일을 선택해야 합니다.',
+ msgFilesTooMany: '선택한 파일의 수 <b>({n})</b>가 업로드 허용 최고치인 <b>{m}</b>를 넘었습니다..',
+ msgFileNotFound: '파일 "{name}"을 찾을 수 없습니다.!',
+ msgFileSecured: '보안상의 이유로 파일 "{name}"을/를 읽을 수 없습니다..',
+ msgFileNotReadable: '파일 "{name}"은/는 읽을 수 없습니다.',
+ msgFilePreviewAborted: '파일 "{name}"의 미리보기가 중단되었습니다.',
+ msgFilePreviewError: '파일 "{name}"을/를 읽다가 에러가 발생했습니다.',
+ msgInvalidFileName: '파일 "{name}" 중 지원 불가능한 문자가 포함되어 있습니다.',
+ msgInvalidFileType: '파일 "{name}"의 타입은 지원하지 않습니다. "{types}" 타입의 파일을 선택해 주십시요.',
+ msgInvalidFileExtension: '파일 "{name}"의 익스텐션은 지원하지 않습니다. "{extensions}" 타입의 익스텐션을 선택해 주십시요.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: '파일 업로드가 중단되었습니다.',
+ msgUploadThreshold: '업로드 중...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: '업로드 가능 데이터가 존재하지 않습니다.',
+ msgUploadError: 'Error',
+ msgValidationError: '유효성 오류',
+ msgLoading: '파일 {files} 중 {index}번째를 로딩하고 있습니다. &hellip;',
+ msgProgress: '파일 {files}의 {name}이 {percent}% 로딩되었습니다. ',
+ msgSelected: '{n} {files}이 선택 되었습니다.',
+ msgFoldersNotAllowed: '드래그 앤 드랍 파일만 가능합니다! 드랍한 {n}번째 폴더를 건너 뛰었습니다.',
+ msgImageWidthSmall: '이미지 파일 "{name}"의 가로는 최소 {size} px가 되어야 합니다.',
+ msgImageHeightSmall: '이미지 파일 "{name}"의 세로는 최소 {size} px가 되어야 합니다.',
+ msgImageWidthLarge: '이미지 파일 "{name}"의 가로는 최대 {size} px를 넘을수 없습니다.',
+ msgImageHeightLarge: '이미지 파일 "{name}"의 세로는 최대 {size} px를 넘을수 없습니다.',
+ msgImageResizeError: '이미지의 사이즈를 재조정을 위한 이미지 사이즈를 가져올 수 없습니다.',
+ msgImageResizeException: '이미지 사이즈 재조정이 다음 이유로 실패했습니다.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: '파일을 여기에 드래그인 드랍을 하십시요 &hellip;',
+ dropZoneClickTitle: '<br>(또는 {files} 선택을 위해 클릭하십시요)',
+ fileActionSettings: {
+ removeTitle: '파일 지우기',
+ uploadTitle: '파일 업로드 하기',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: '세부 정보 보기',
+ dragTitle: '옭기기 / 재배열하기',
+ indicatorNewTitle: '아직 업로드가 안되었습니다.',
+ indicatorSuccessTitle: '업로드가 성공하였습니다.',
+ indicatorErrorTitle: '업로드 중 에러가 발행했습니다.',
+ indicatorLoadingTitle: '업로드 중 ...'
+ },
+ previewZoomButtonTitles: {
+ prev: '전 파일 보기',
+ next: '다음 파일 보기',
+ toggleheader: '머릿글 토글하기',
+ fullscreen: '전채화면 토글하기',
+ borderless: '무 테두리 토글하기',
+ close: '세부 정보 미리보기 토글하기'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/kz.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/kz.js
new file mode 100644
index 00000000..82c34cc4
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/kz.js
@@ -0,0 +1,88 @@
+/*!
+ * FileInput Kazakh Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author Kali Toleugazy <almatytol@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['kz'] = {
+ fileSingle: 'файл',
+ filePlural: 'файлдар',
+ browseLabel: 'Таңдау &hellip;',
+ removeLabel: 'Жою',
+ removeTitle: 'Таңдалған файлдарды жою',
+ cancelLabel: 'Күшін жою',
+ cancelTitle: 'Ағымдағы жүктеуді болдырмау',
+ uploadLabel: 'Жүктеу',
+ uploadTitle: 'Таңдалған файлдарды жүктеу',
+ msgNo: 'жоқ',
+ msgNoFilesSelected: 'Файл таңдалмады',
+ msgCancelled: 'Күші жойылған',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Алдын ала толық көру',
+ msgSizeTooLarge: 'Файл "{name}" (<b>{size} KB</b>) ең үлкен <b>{maxSize} KB</b> өлшемінен асады.',
+ msgFilesTooLess: 'Жүктеу үшіy кемінде <b>{n}</b> {files} таңдау керек.',
+ msgFilesTooMany: 'Таңдалған <b>({n})</b> файлдардың саны берілген <b>{m}</b> саннан асып кетті.',
+ msgFileNotFound: 'Файл "{name}" табылмады!',
+ msgFileSecured: 'Шектеу қауіпсіздігі "{name}" файлын оқуға тыйым салады.',
+ msgFileNotReadable: '"{name}" файлды оқу мүмкін емес.',
+ msgFilePreviewAborted: '"{name}" файл үшін алдын ала қарап көру тыйым салынған.',
+ msgFilePreviewError: '"{name}" файлды оқығанда қате пайда болды.',
+ msgInvalidFileType: '"{name}" тыйым салынған файл түрі. Тек мынаналарға рұқсат етілген: "{types}"',
+ msgInvalidFileExtension: '"{name}" тыйым салынған файл кеңейтімі. Тек "{extensions}" рұқсат.',
+ msgUploadAborted: 'Файлды жүктеу доғарылды',
+ msgUploadThreshold: 'Өңдеу...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Тексеру қатесі',
+ msgLoading: '{index} файлды {files} &hellip; жүктеу',
+ msgProgress: '{index} файлды {files} - {name} - {percent}% жүктеу аяқталды.',
+ msgSelected: 'Таңдалған файлдар саны: {n}',
+ msgFoldersNotAllowed: 'Тек файлдарды сүйреу рұқсат! {n} папка өткізілген.',
+ msgImageWidthSmall: '{name} суреттің ені {size} px. аз болмау керек',
+ msgImageHeightSmall: '{name} суреттің биіктігі {size} px. аз болмау керек',
+ msgImageWidthLarge: '"{name}" суреттің ені {size} px. аспау керек',
+ msgImageHeightLarge: '"{name}" суреттің биіктігі {size} px. аспау керек',
+ msgImageResizeError: 'Суреттің өлшемін өзгерту үшін, мөлшері алынбады',
+ msgImageResizeException: 'Суреттің мөлшерлерін өзгерткен кезде қателік пайда болды.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Файлдарды осында сүйреу &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: 'Файлды өшіру',
+ uploadTitle: 'Файлды жүктеу',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'мәліметтерді көру',
+ dragTitle: 'Орнын ауыстыру',
+ indicatorNewTitle: 'Жүктелген жоқ',
+ indicatorSuccessTitle: 'Жүктелген',
+ indicatorErrorTitle: 'Жүктелу қатесі ',
+ indicatorLoadingTitle: 'Жүктелу ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Алдыңғы файлды қарау',
+ next: 'Келесі файлды қарау',
+ toggleheader: 'Тақырыпты ауыстыру',
+ fullscreen: 'Толық экран режимін қосу',
+ borderless: 'Жиексіз режиміне ауысу',
+ close: 'Толық көрінісін жабу'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/lt.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/lt.js
new file mode 100644
index 00000000..91f36c94
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/lt.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput <_LANG_> Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author Mindaugas Varkalys <varkalys.mindaugas@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['lt'] = {
+ fileSingle: 'failas',
+ filePlural: 'failai',
+ browseLabel: 'Naršyti &hellip;',
+ removeLabel: 'Šalinti',
+ removeTitle: 'Pašalinti pasirinktus failus',
+ cancelLabel: 'Atšaukti',
+ cancelTitle: 'Atšaukti vykstantį įkėlimą',
+ uploadLabel: 'Įkelti',
+ uploadTitle: 'Įkelti pasirinktus failus',
+ msgNo: 'Ne',
+ msgNoFilesSelected: 'Nepasirinkta jokių failų',
+ msgCancelled: 'Atšaukta',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Detali Peržiūra',
+ msgFileRequired: 'Pasirinkite failą įkėlimui.',
+ msgSizeTooSmall: 'Failas "{name}" (<b>{size} KB</b>) yra per mažas ir turi būti didesnis nei <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Failas "{name}" (<b>{size} KB</b>) viršija maksimalų leidžiamą įkeliamo failo dydį <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Turite pasirinkti bent <b>{n}</b> failus įkėlimui.',
+ msgFilesTooMany: 'Įkėlimui pasirinktų failų skaičius <b>({n})</b> viršija maksimalų leidžiamą limitą <b>{m}</b>.',
+ msgFileNotFound: 'Failas "{name}" nerastas!',
+ msgFileSecured: 'Saugumo apribojimai neleidžia perskaityti failo "{name}".',
+ msgFileNotReadable: 'Failas "{name}" neperskaitomas.',
+ msgFilePreviewAborted: 'Failo peržiūra nutraukta "{name}".',
+ msgFilePreviewError: 'Įvyko klaida skaitant failą "{name}".',
+ msgInvalidFileName: 'Klaidingi arba nepalaikomi simboliai failo pavadinime "{name}".',
+ msgInvalidFileType: 'Klaidingas failo "{name}" tipas. Tik "{types}" tipai yra palaikomi.',
+ msgInvalidFileExtension: 'Klaidingas failo "{name}" plėtinys. Tik "{extensions}" plėtiniai yra palaikomi.',
+ msgFileTypes: {
+ 'image': 'paveikslėlis',
+ 'html': 'HTML',
+ 'text': 'tekstas',
+ 'video': 'vaizdo įrašas',
+ 'audio': 'garso įrašas',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'objektas'
+ },
+ msgUploadAborted: 'Failo įkėlimas buvo nutrauktas',
+ msgUploadThreshold: 'Vykdoma...',
+ msgUploadBegin: 'Inicijuojama...',
+ msgUploadEnd: 'Baigta',
+ msgUploadEmpty: 'Nėra teisingų duomenų įkėlimui.',
+ msgUploadError: 'Klaida',
+ msgValidationError: 'Validacijos Klaida',
+ msgLoading: 'Keliamas failas {index} iš {files} &hellip;',
+ msgProgress: 'Keliamas failas {index} iš {files} - {name} - {percent}% baigta.',
+ msgSelected: 'Pasirinkti {n} {files}',
+ msgFoldersNotAllowed: 'Tempkite tik failus! Praleisti {n} nutempti aplankalas(-i).',
+ msgImageWidthSmall: 'Paveikslėlio "{name}" plotis turi būti bent {size} px.',
+ msgImageHeightSmall: 'Paveikslėlio "{name}" aukštis turi būti bent {size} px.',
+ msgImageWidthLarge: 'Paveikslėlio "{name}" plotis negali viršyti {size} px.',
+ msgImageHeightLarge: 'Paveikslėlio "{name}" aukštis negali viršyti {size} px.',
+ msgImageResizeError: 'Nepavyksta gauti paveikslėlio matmetų, kad pakeisti jo matmemis.',
+ msgImageResizeException: 'Klaida keičiant paveikslėlio matmenis.<pre>{errors}</pre>',
+ msgAjaxError: 'Kažkas nutiko vykdant {operation} operaciją. Prašome pabandyti vėliau!',
+ msgAjaxProgressError: '{operation} operacija nesėkminga',
+ ajaxOperations: {
+ deleteThumb: 'failo trynimo',
+ uploadThumb: 'failo įkėlimo',
+ uploadBatch: 'failų rinkinio įkėlimo',
+ uploadExtra: 'formos duomenų įkėlimo'
+ },
+ dropZoneTitle: 'Tempkite failus čia &hellip;',
+ dropZoneClickTitle: '<br>(arba paspauskite, kad pasirinktumėte failus)',
+ fileActionSettings: {
+ removeTitle: 'Šalinti failą',
+ uploadTitle: 'Įkelti failą',
+ uploadRetryTitle: 'Bandyti įkelti vėl',
+ zoomTitle: 'Peržiūrėti detales',
+ dragTitle: 'Perstumti',
+ indicatorNewTitle: 'Dar neįkelta',
+ indicatorSuccessTitle: 'Įkelta',
+ indicatorErrorTitle: 'Įkėlimo Klaida',
+ indicatorLoadingTitle: 'Įkeliama ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Peržiūrėti ankstesnį failą',
+ next: 'Peržiūrėti kitą failą',
+ toggleheader: 'Perjungti viršutinę juostą',
+ fullscreen: 'Perjungti pilno ekrano rėžimą',
+ borderless: 'Perjungti berėmį režimą',
+ close: 'Uždaryti detalią peržiūrą'
+ }
+ };
+})(window.jQuery); \ No newline at end of file
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/nl.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/nl.js
new file mode 100644
index 00000000..eb83d50e
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/nl.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Dutch Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['nl'] = {
+ fileSingle: 'bestand',
+ filePlural: 'bestanden',
+ browseLabel: 'Zoek &hellip;',
+ removeLabel: 'Verwijder',
+ removeTitle: 'Verwijder geselecteerde bestanden',
+ cancelLabel: 'Annuleren',
+ cancelTitle: 'Annuleer upload',
+ uploadLabel: 'Upload',
+ uploadTitle: 'Upload geselecteerde bestanden',
+ msgNo: 'Nee',
+ msgNoFilesSelected: '',
+ msgCancelled: 'Geannuleerd',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Gedetailleerd voorbeeld',
+ msgFileRequired: 'U moet een bestand kiezen om te uploaden.',
+ msgSizeTooSmall: 'Bestand "{name}" (<b>{size} KB</b>) is te klein en moet groter zijn dan <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Bestand "{name}" (<b>{size} KB</b>) is groter dan de toegestane <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'U moet minstens <b>{n}</b> {files} selecteren om te uploaden.',
+ msgFilesTooMany: 'Aantal geselecteerde bestanden <b>({n})</b> is meer dan de toegestane <b>{m}</b>.',
+ msgFileNotFound: 'Bestand "{name}" niet gevonden!',
+ msgFileSecured: 'Bestand kan niet gelezen worden in verband met beveiligings redenen "{name}".',
+ msgFileNotReadable: 'Bestand "{name}" is niet leesbaar.',
+ msgFilePreviewAborted: 'Bestand weergaven geannuleerd voor "{name}".',
+ msgFilePreviewError: 'Er is een fout opgetreden met het lezen van "{name}".',
+ msgInvalidFileName: 'Ongeldige of niet ondersteunde karakters in bestandsnaam "{name}".',
+ msgInvalidFileType: 'Geen geldig bestand "{name}". Alleen "{types}" zijn toegestaan.',
+ msgInvalidFileExtension: 'Geen geldige extensie "{name}". Alleen "{extensions}" zijn toegestaan.',
+ msgFileTypes: {
+ 'image': 'afbeelding',
+ 'html': 'HTML',
+ 'text': 'tekst',
+ 'video': 'video',
+ 'audio': 'geluid',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Het uploaden van bestanden is afgebroken',
+ msgUploadThreshold: 'Verwerken...',
+ msgUploadBegin: 'Initialiseren...',
+ msgUploadEnd: 'Gedaan',
+ msgUploadEmpty: 'Geen geldige data beschikbaar voor upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Bevestiging fout',
+ msgLoading: 'Bestanden laden {index} van de {files} &hellip;',
+ msgProgress: 'Bestanden laden {index} van de {files} - {name} - {percent}% compleet.',
+ msgSelected: '{n} {files} geselecteerd',
+ msgFoldersNotAllowed: 'Drag & drop alleen bestanden! {n} overgeslagen map(pen).',
+ msgImageWidthSmall: 'Breedte van het foto-bestand "{name}" moet minstens {size} px zijn.',
+ msgImageHeightSmall: 'Hoogte van het foto-bestand "{name}" moet minstens {size} px zijn.',
+ msgImageWidthLarge: 'Breedte van het foto-bestand "{name}" kan niet hoger zijn dan {size} px.',
+ msgImageHeightLarge: 'Hoogte van het foto bestand "{name}" kan niet hoger zijn dan {size} px.',
+ msgImageResizeError: 'Kon de foto afmetingen niet lezen om te verkleinen.',
+ msgImageResizeException: 'Fout bij het verkleinen van de foto.<pre>{errors}</pre>',
+ msgAjaxError: 'Er ging iets mis met de {operation} actie. Gelieve later opnieuw te proberen!',
+ msgAjaxProgressError: '{operation} mislukt',
+ ajaxOperations: {
+ deleteThumb: 'bestand verwijderen',
+ uploadThumb: 'bestand uploaden',
+ uploadBatch: 'alle bestanden uploaden',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Drag & drop bestanden hier &hellip;',
+ dropZoneClickTitle: '<br>(of klik hier om {files} te selecteren)',
+ fileActionSettings: {
+ removeTitle: 'Verwijder bestand',
+ uploadTitle: 'bestand uploaden',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Bekijk details',
+ dragTitle: 'Move / Rearrange',
+ indicatorNewTitle: 'Nog niet geupload',
+ indicatorSuccessTitle: 'geupload',
+ indicatorErrorTitle: 'fout uploaden',
+ indicatorLoadingTitle: 'uploaden ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Toon vorig bestand',
+ next: 'Toon volgend bestand',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/no.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/no.js
new file mode 100644
index 00000000..773bb1bd
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/no.js
@@ -0,0 +1,99 @@
+/*!
+ * FileInput Norwegian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['no'] = {
+ fileSingle: 'fil',
+ filePlural: 'filer',
+ browseLabel: 'Bla gjennom &hellip;',
+ removeLabel: 'Fjern',
+ removeTitle: 'Fjern valgte filer',
+ cancelLabel: 'Avbryt',
+ cancelTitle: 'Stopp pågående opplastninger',
+ uploadLabel: 'Last opp',
+ uploadTitle: 'Last opp valgte filer',
+ msgNo: 'Nei',
+ msgNoFilesSelected: 'Ingen filer er valgt',
+ msgCancelled: 'Avbrutt',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Detaljert visning',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'Filen "{name}" (<b>{size} KB</b>) er for liten og må være større enn <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Filen "{name}" (<b>{size} KB</b>) er for stor, maksimal filstørrelse er <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Du må velge minst <b>{n}</b> {files} for opplastning.',
+ msgFilesTooMany: 'For mange filer til opplastning, <b>({n})</b> overstiger maksantallet som er <b>{m}</b>.',
+ msgFileNotFound: 'Fant ikke filen "{name}"!',
+ msgFileSecured: 'Sikkerhetsrestriksjoner hindrer lesing av filen "{name}".',
+ msgFileNotReadable: 'Filen "{name}" er ikke lesbar.',
+ msgFilePreviewAborted: 'Filvisning avbrutt for "{name}".',
+ msgFilePreviewError: 'En feil oppstod under lesing av filen "{name}".',
+ msgInvalidFileName: 'Ugyldige tegn i filen "{name}".',
+ msgInvalidFileType: 'Ugyldig type for filen "{name}". Kun "{types}" filer er tillatt.',
+ msgInvalidFileExtension: 'Ugyldig endelse for filen "{name}". Kun "{extensions}" filer støttes.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Filopplastningen ble avbrutt',
+ msgUploadThreshold: 'Prosesserer...',
+ msgUploadBegin: 'Initialiserer...',
+ msgUploadEnd: 'Ferdig',
+ msgUploadEmpty: 'Ingen gyldige data tilgjengelig for opplastning.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Valideringsfeil',
+ msgLoading: 'Laster fil {index} av {files} &hellip;',
+ msgProgress: 'Laster fil {index} av {files} - {name} - {percent}% fullført.',
+ msgSelected: '{n} {files} valgt',
+ msgFoldersNotAllowed: 'Kun Dra & slipp filer! Hoppet over {n} mappe(r).',
+ msgImageWidthSmall: 'Bredde på bildefilen "{name}" må være minst {size} px.',
+ msgImageHeightSmall: 'Høyde på bildefilen "{name}" må være minst {size} px.',
+ msgImageWidthLarge: 'Bredde på bildefilen "{name}" kan ikke overstige {size} px.',
+ msgImageHeightLarge: 'Høyde på bildefilen "{name}" kan ikke overstige {size} px.',
+ msgImageResizeError: 'Fant ikke dimensjonene som skulle resizes.',
+ msgImageResizeException: 'En feil oppstod under endring av størrelse .<pre>{errors}</pre>',
+ msgAjaxError: 'Noe gikk galt med {operation} operasjonen. Vennligst prøv igjen senere!',
+ msgAjaxProgressError: '{operation} feilet',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Dra & slipp filer her &hellip;',
+ dropZoneClickTitle: '<br>(eller klikk for å velge {files})',
+ fileActionSettings: {
+ removeTitle: 'Fjern fil',
+ uploadTitle: 'Last opp fil',
+ uploadRetryTitle: 'Retry upload',
+ zoomTitle: 'Vis detaljer',
+ dragTitle: 'Flytt / endre rekkefølge',
+ indicatorNewTitle: 'Opplastning ikke fullført',
+ indicatorSuccessTitle: 'Opplastet',
+ indicatorErrorTitle: 'Opplastningsfeil',
+ indicatorLoadingTitle: 'Laster opp ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Vis forrige fil',
+ next: 'Vis neste fil',
+ toggleheader: 'Vis header',
+ fullscreen: 'Åpne fullskjerm',
+ borderless: 'Åpne uten kanter',
+ close: 'Lukk detaljer'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/pl.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/pl.js
new file mode 100644
index 00000000..e19a0ed2
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/pl.js
@@ -0,0 +1,90 @@
+/*!
+ * FileInput Polish Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['pl'] = {
+ fileSingle: 'plik',
+ filePlural: 'pliki',
+ browseLabel: 'Przeglądaj &hellip;',
+ removeLabel: 'Usuń',
+ removeTitle: 'Usuń zaznaczone pliki',
+ cancelLabel: 'Przerwij',
+ cancelTitle: 'Anuluj wysyłanie',
+ uploadLabel: 'Wgraj',
+ uploadTitle: 'Wgraj zaznaczone pliki',
+ msgNo: 'Nie',
+ msgNoFilesSelected: 'Brak zaznaczonych plików',
+ msgCancelled: 'Odwołany',
+ msgPlaceholder: 'Wybierz {files}...',
+ msgZoomModalHeading: 'Szczegółowy podgląd',
+ msgFileRequired: 'Musisz wybrać plik do wgrania.',
+ msgSizeTooSmall: 'Plik "{name}" (<b>{size} KB</b>) jest zbyt mały i musi być większy niż <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Plik o nazwie "{name}" (<b>{size} KB</b>) przekroczył maksymalną dopuszczalną wielkość pliku wynoszącą <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Minimalna liczba plików do wgrania: <b>{n}</b>.',
+ msgFilesTooMany: 'Liczba plików wybranych do wgrania w liczbie <b>({n})</b>, przekracza maksymalny dozwolony limit wynoszący <b>{m}</b>.',
+ msgFileNotFound: 'Plik "{name}" nie istnieje!',
+ msgFileSecured: 'Ustawienia zabezpieczeń uniemożliwiają odczyt pliku "{name}".',
+ msgFileNotReadable: 'Plik "{name}" nie jest plikiem do odczytu.',
+ msgFilePreviewAborted: 'Podgląd pliku "{name}" został przerwany.',
+ msgFilePreviewError: 'Wystąpił błąd w czasie odczytu pliku "{name}".',
+ msgInvalidFileName: 'Nieprawidłowe lub nieobsługiwane znaki w nazwie pliku "{name}".',
+ msgInvalidFileType: 'Nieznany typ pliku "{name}". Tylko następujące rodzaje plików są dozwolone: "{types}".',
+ msgInvalidFileExtension: 'Złe rozszerzenie dla pliku "{name}". Tylko następujące rozszerzenia plików są dozwolone: "{extensions}".',
+ msgUploadAborted: 'Przesyłanie pliku zostało przerwane',
+ msgUploadThreshold: 'Przetwarzanie...',
+ msgUploadBegin: 'Rozpoczynanie...',
+ msgUploadEnd: 'Gotowe!',
+ msgUploadEmpty: 'Brak poprawnych danych do przesłania.',
+ msgUploadError: 'Błąd',
+ msgValidationError: 'Błąd walidacji',
+ msgLoading: 'Wczytywanie pliku {index} z {files} &hellip;',
+ msgProgress: 'Wczytywanie pliku {index} z {files} - {name} - {percent}% zakończone.',
+ msgSelected: '{n} Plików zaznaczonych',
+ msgFoldersNotAllowed: 'Metodą przeciągnij i upuść, można przenosić tylko pliki. Pominięto {n} katalogów.',
+ msgImageWidthSmall: 'Szerokość pliku obrazu "{name}" musi być co najmniej {size} px.',
+ msgImageHeightSmall: 'Wysokość pliku obrazu "{name}" musi być co najmniej {size} px.',
+ msgImageWidthLarge: 'Szerokość pliku obrazu "{name}" nie może przekraczać {size} px.',
+ msgImageHeightLarge: 'Wysokość pliku obrazu "{name}" nie może przekraczać {size} px.',
+ msgImageResizeError: 'Nie udało się uzyskać wymiaru obrazu, aby zmienić rozmiar.',
+ msgImageResizeException: 'Błąd podczas zmiany rozmiaru obrazu.<pre>{errors}</pre>',
+ msgAjaxError: 'Coś poczło nie tak podczas {operation}. Spróbuj ponownie!',
+ msgAjaxProgressError: '{operation} nie powiodło się',
+ ajaxOperations: {
+ deleteThumb: 'usuwanie pliku',
+ uploadThumb: 'przesyłanie pliku',
+ uploadBatch: 'masowe przesyłanie plików',
+ uploadExtra: 'przesyłanie danych formularza'
+ },
+ dropZoneTitle: 'Przeciągnij i upuść pliki tutaj &hellip;',
+ dropZoneClickTitle: '<br>(lub kliknij tutaj i wybierz {files} z komputera)',
+ fileActionSettings: {
+ removeTitle: 'Usuń plik',
+ uploadTitle: 'Przesyłanie pliku',
+ uploadRetryTitle: 'Ponów',
+ downloadTitle: 'Pobierz plik',
+ zoomTitle: 'Pokaż szczegóły',
+ dragTitle: 'Przenies / Ponownie zaaranżuj',
+ indicatorNewTitle: 'Jeszcze nie przesłany',
+ indicatorSuccessTitle: 'Dodane',
+ indicatorErrorTitle: 'Błąd',
+ indicatorLoadingTitle: 'Przesyłanie ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Pokaż poprzedni plik',
+ next: 'Pokaż następny plik',
+ toggleheader: 'Włącz / wyłącz nagłówek',
+ fullscreen: 'Włącz / wyłącz pełny ekran',
+ borderless: 'Włącz / wyłącz tryb bez ramek',
+ close: 'Zamknij szczegółowy widok'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/pt-BR.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/pt-BR.js
new file mode 100644
index 00000000..157ac84c
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/pt-BR.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Brazillian Portuguese Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['pt-BR'] = {
+ fileSingle: 'arquivo',
+ filePlural: 'arquivos',
+ browseLabel: 'Procurar&hellip;',
+ removeLabel: 'Remover',
+ removeTitle: 'Remover arquivos selecionados',
+ cancelLabel: 'Cancelar',
+ cancelTitle: 'Interromper envio em andamento',
+ uploadLabel: 'Enviar',
+ uploadTitle: 'Enviar arquivos selecionados',
+ msgNo: 'Não',
+ msgNoFilesSelected: 'Nenhum arquivo selecionado',
+ msgCancelled: 'Cancelado',
+ msgPlaceholder: 'Selecionar {files}...',
+ msgZoomModalHeading: 'Pré-visualização detalhada',
+ msgFileRequired: 'Você deve selecionar um arquivo para enviar.',
+ msgSizeTooSmall: 'O arquivo "{name}" (<b>{size} KB</b>) é muito pequeno e deve ser maior que <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'O arquivo "{name}" (<b>{size} KB</b>) excede o tamanho máximo permitido de <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Você deve selecionar pelo menos <b>{n}</b> {files} para enviar.',
+ msgFilesTooMany: 'O número de arquivos selecionados para o envio <b>({n})</b> excede o limite máximo permitido de <b>{m}</b>.',
+ msgFileNotFound: 'O arquivo "{name}" não foi encontrado!',
+ msgFileSecured: 'Restrições de segurança impedem a leitura do arquivo "{name}".',
+ msgFileNotReadable: 'O arquivo "{name}" não pode ser lido.',
+ msgFilePreviewAborted: 'A pré-visualização do arquivo "{name}" foi interrompida.',
+ msgFilePreviewError: 'Ocorreu um erro ao ler o arquivo "{name}".',
+ msgInvalidFileName: 'Caracteres inválidos ou não suportados no arquivo "{name}".',
+ msgInvalidFileType: 'Tipo inválido para o arquivo "{name}". Apenas arquivos "{types}" são permitidos.',
+ msgInvalidFileExtension: 'Extensão inválida para o arquivo "{name}". Apenas arquivos "{extensions}" são permitidos.',
+ msgFileTypes: {
+ 'image': 'imagem',
+ 'html': 'HTML',
+ 'text': 'texto',
+ 'video': 'vídeo',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'objeto'
+ },
+ msgUploadAborted: 'O envio do arquivo foi abortado',
+ msgUploadThreshold: 'Processando...',
+ msgUploadBegin: 'Inicializando...',
+ msgUploadEnd: 'Concluído',
+ msgUploadEmpty: 'Nenhuma informação válida para upload.',
+ msgUploadError: 'Erro de Upload',
+ msgValidationError: 'Erro de validação',
+ msgLoading: 'Enviando arquivo {index} de {files}&hellip;',
+ msgProgress: 'Enviando arquivo {index} de {files} - {name} - {percent}% completo.',
+ msgSelected: '{n} {files} selecionado(s)',
+ msgFoldersNotAllowed: 'Arraste e solte apenas arquivos! {n} pasta(s) ignoradas.',
+ msgImageWidthSmall: 'Largura do arquivo de imagem "{name}" deve ser pelo menos {size} px.',
+ msgImageHeightSmall: 'Altura do arquivo de imagem "{name}" deve ser pelo menos {size} px.',
+ msgImageWidthLarge: 'Largura do arquivo de imagem "{name}" não pode exceder {size} px.',
+ msgImageHeightLarge: 'Altura do arquivo de imagem "{name}" não pode exceder {size} px.',
+ msgImageResizeError: 'Não foi possível obter as dimensões da imagem para redimensionar.',
+ msgImageResizeException: 'Erro ao redimensionar a imagem.<pre>{errors}</pre>',
+ msgAjaxError: 'Algo deu errado com a operação {operation}. Por favor tente novamente mais tarde!',
+ msgAjaxProgressError: '{operation} falhou',
+ ajaxOperations: {
+ deleteThumb: 'Exclusão de arquivo',
+ uploadThumb: 'Upload de arquivos',
+ uploadBatch: 'Carregamento de arquivos em lote',
+ uploadExtra: 'Carregamento de dados do formulário'
+ },
+ dropZoneTitle: 'Arraste e solte os arquivos aqui&hellip;',
+ dropZoneClickTitle: '<br>(ou clique para selecionar o(s) arquivo(s))',
+ fileActionSettings: {
+ removeTitle: 'Remover arquivo',
+ uploadTitle: 'Enviar arquivo',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Ver detalhes',
+ dragTitle: 'Mover / Reordenar',
+ indicatorNewTitle: 'Ainda não enviado',
+ indicatorSuccessTitle: 'Enviado',
+ indicatorErrorTitle: 'Erro',
+ indicatorLoadingTitle: 'Enviando...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Visualizar arquivo anterior',
+ next: 'Visualizar próximo arquivo',
+ toggleheader: 'Mostrar cabeçalho',
+ fullscreen: 'Ativar tela cheia',
+ borderless: 'Ativar modo sem borda',
+ close: 'Fechar pré-visualização detalhada'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/pt.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/pt.js
new file mode 100644
index 00000000..419b1761
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/pt.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Portuguese Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['pt'] = {
+ fileSingle: 'ficheiro',
+ filePlural: 'ficheiros',
+ browseLabel: 'Procurar &hellip;',
+ removeLabel: 'Remover',
+ removeTitle: 'Remover ficheiros seleccionados',
+ cancelLabel: 'Cancelar',
+ cancelTitle: 'Abortar carregamento ',
+ uploadLabel: 'Carregar',
+ uploadTitle: 'Carregar ficheiros seleccionados',
+ msgNo: 'Não',
+ msgNoFilesSelected: '',
+ msgCancelled: 'Cancelado',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Pré-visualização detalhada',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Ficheiro "{name}" (<b>{size} KB</b>) excede o tamanho máximo permido de <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Deve seleccionar pelo menos <b>{n}</b> {files} para fazer upload.',
+ msgFilesTooMany: 'Número máximo de ficheiros seleccionados <b>({n})</b> excede o limite máximo de <b>{m}</b>.',
+ msgFileNotFound: 'Ficheiro "{name}" não encontrado!',
+ msgFileSecured: 'Restrições de segurança preventem a leitura do ficheiro "{name}".',
+ msgFileNotReadable: 'Ficheiro "{name}" não pode ser lido.',
+ msgFilePreviewAborted: 'Pré-visualização abortado para o ficheiro "{name}".',
+ msgFilePreviewError: 'Ocorreu um erro ao ler o ficheiro "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'Tipo inválido para o ficheiro "{name}". Apenas ficheiros "{types}" são suportados.',
+ msgInvalidFileExtension: 'Extensão inválida para o ficheiro "{name}". Apenas ficheiros "{extensions}" são suportados.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'O upload do arquivo foi abortada',
+ msgUploadThreshold: 'Processing...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Erro de validação',
+ msgLoading: 'A carregar ficheiro {index} de {files} &hellip;',
+ msgProgress: 'A carregar ficheiro {index} de {files} - {name} - {percent}% completo.',
+ msgSelected: '{n} {files} seleccionados',
+ msgFoldersNotAllowed: 'Arrastar e largar ficheiros apenas! {n} pasta(s) ignoradas.',
+ msgImageWidthSmall: 'Largura do arquivo de imagem "{name}" deve ser pelo menos {size} px.',
+ msgImageHeightSmall: 'Altura do arquivo de imagem "{name}" deve ser pelo menos {size} px.',
+ msgImageWidthLarge: 'Largura do arquivo de imagem "{name}" não pode exceder {size} px.',
+ msgImageHeightLarge: 'Altura do arquivo de imagem "{name}" não pode exceder {size} px.',
+ msgImageResizeError: 'Could not get the image dimensions to resize.',
+ msgImageResizeException: 'Erro ao redimensionar a imagem.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Arrastar e largar ficheiros aqui &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: 'Remover arquivo',
+ uploadTitle: 'Carregar arquivo',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Ver detalhes',
+ dragTitle: 'Move / Rearrange',
+ indicatorNewTitle: 'Ainda não carregou',
+ indicatorSuccessTitle: 'Carregado',
+ indicatorErrorTitle: 'Carregar Erro',
+ indicatorLoadingTitle: 'A carregar ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'View previous file',
+ next: 'View next file',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery); \ No newline at end of file
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ro.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ro.js
new file mode 100644
index 00000000..d31b85bd
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ro.js
@@ -0,0 +1,101 @@
+/*!
+ * FileInput Romanian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author Ciprian Voicu <pictoru@autoportret.ro>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['ro'] = {
+ fileSingle: 'fișier',
+ filePlural: 'fișiere',
+ browseLabel: 'Răsfoiește &hellip;',
+ removeLabel: 'Șterge',
+ removeTitle: 'Curăță fișierele selectate',
+ cancelLabel: 'Renunță',
+ cancelTitle: 'Anulează încărcarea curentă',
+ uploadLabel: 'Încarcă',
+ uploadTitle: 'Încarcă fișierele selectate',
+ msgNo: 'Nu',
+ msgNoFilesSelected: '',
+ msgCancelled: 'Anulat',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Previzualizare detaliată',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Fișierul "{name}" (<b>{size} KB</b>) depășește limita maximă de încărcare de <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Trebuie să selectezi cel puțin <b>{n}</b> {files} pentru a încărca.',
+ msgFilesTooMany: 'Numărul fișierelor pentru încărcare <b>({n})</b> depășește limita maximă de <b>{m}</b>.',
+ msgFileNotFound: 'Fișierul "{name}" nu a fost găsit!',
+ msgFileSecured: 'Restricții de securitate previn citirea fișierului "{name}".',
+ msgFileNotReadable: 'Fișierul "{name}" nu se poate citi.',
+ msgFilePreviewAborted: 'Fișierului "{name}" nu poate fi previzualizat.',
+ msgFilePreviewError: 'A intervenit o eroare în încercarea de citire a fișierului "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'Tip de fișier incorect pentru "{name}". Sunt suportate doar fișiere de tipurile "{types}".',
+ msgInvalidFileExtension: 'Extensie incorectă pentru "{name}". Sunt suportate doar extensiile "{extensions}".',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Fișierul Încărcarea a fost întrerupt',
+ msgUploadThreshold: 'Processing...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Eroare de validare',
+ msgLoading: 'Se încarcă fișierul {index} din {files} &hellip;',
+ msgProgress: 'Se încarcă fișierul {index} din {files} - {name} - {percent}% încărcat.',
+ msgSelected: '{n} {files} încărcate',
+ msgFoldersNotAllowed: 'Se poate doar trăgând fișierele! Se renunță la {n} dosar(e).',
+ msgImageWidthSmall: 'Lățimea de fișier de imagine "{name}" trebuie să fie de cel puțin {size} px.',
+ msgImageHeightSmall: 'Înălțimea fișier imagine "{name}" trebuie să fie de cel puțin {size} px.',
+ msgImageWidthLarge: 'Lățimea de fișier de imagine "{name}" nu poate depăși {size} px.',
+ msgImageHeightLarge: 'Înălțimea fișier imagine "{name}" nu poate depăși {size} px.',
+ msgImageResizeError: 'Nu a putut obține dimensiunile imaginii pentru a redimensiona.',
+ msgImageResizeException: 'Eroare la redimensionarea imaginii.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Trage fișierele aici &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: 'Scoateți fișier',
+ uploadTitle: 'Incarca fisier',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Vezi detalii',
+ dragTitle: 'Move / Rearrange',
+ indicatorNewTitle: 'Nu a încărcat încă',
+ indicatorSuccessTitle: 'încărcat',
+ indicatorErrorTitle: 'Încărcați eroare',
+ indicatorLoadingTitle: 'Se încarcă ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'View previous file',
+ next: 'View next file',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ru.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ru.js
new file mode 100644
index 00000000..71c5ff7a
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/ru.js
@@ -0,0 +1,101 @@
+/*!
+ * FileInput Russian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author CyanoFresh <cyanofresh@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['ru'] = {
+ fileSingle: 'файл',
+ filePlural: 'файлы',
+ browseLabel: 'Выбрать &hellip;',
+ removeLabel: 'Удалить',
+ removeTitle: 'Очистить выбранные файлы',
+ cancelLabel: 'Отмена',
+ cancelTitle: 'Отменить текущую загрузку',
+ uploadLabel: 'Загрузить',
+ uploadTitle: 'Загрузить выбранные файлы',
+ msgNo: 'нет',
+ msgNoFilesSelected: '',
+ msgCancelled: 'Отменено',
+ msgPlaceholder: 'Выбрать {files}...',
+ msgZoomModalHeading: 'Подробное превью',
+ msgFileRequired: 'Необходимо выбрать файл для загрузки.',
+ msgSizeTooSmall: 'Файл "{name}" (<b>{size} KB</b>) имеет слишком маленький размер и должен быть больше <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Файл "{name}" (<b>{size} KB</b>) превышает максимальный размер <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Вы должны выбрать как минимум <b>{n}</b> {files} для загрузки.',
+ msgFilesTooMany: 'Количество выбранных файлов <b>({n})</b> превышает максимально допустимое количество <b>{m}</b>.',
+ msgFileNotFound: 'Файл "{name}" не найден!',
+ msgFileSecured: 'Ограничения безопасности запрещают читать файл "{name}".',
+ msgFileNotReadable: 'Файл "{name}" невозможно прочитать.',
+ msgFilePreviewAborted: 'Предпросмотр отменен для файла "{name}".',
+ msgFilePreviewError: 'Произошла ошибка при чтении файла "{name}".',
+ msgInvalidFileName: 'Неверные или неподдерживаемые символы в названии файла "{name}".',
+ msgInvalidFileType: 'Запрещенный тип файла для "{name}". Только "{types}" разрешены.',
+ msgInvalidFileExtension: 'Запрещенное расширение для файла "{name}". Только "{extensions}" разрешены.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Выгрузка файла прервана',
+ msgUploadThreshold: 'Обработка...',
+ msgUploadBegin: 'Инициализация...',
+ msgUploadEnd: 'Готово',
+ msgUploadEmpty: 'Недопустимые данные для загрузки',
+ msgUploadError: 'Ошибка загрузки',
+ msgValidationError: 'Ошибка проверки',
+ msgLoading: 'Загрузка файла {index} из {files} &hellip;',
+ msgProgress: 'Загрузка файла {index} из {files} - {name} - {percent}% завершено.',
+ msgSelected: 'Выбрано файлов: {n}',
+ msgFoldersNotAllowed: 'Разрешено перетаскивание только файлов! Пропущено {n} папок.',
+ msgImageWidthSmall: 'Ширина изображения {name} должна быть не меньше {size} px.',
+ msgImageHeightSmall: 'Высота изображения {name} должна быть не меньше {size} px.',
+ msgImageWidthLarge: 'Ширина изображения "{name}" не может превышать {size} px.',
+ msgImageHeightLarge: 'Высота изображения "{name}" не может превышать {size} px.',
+ msgImageResizeError: 'Не удалось получить размеры изображения, чтобы изменить размер.',
+ msgImageResizeException: 'Ошибка при изменении размера изображения.<pre>{errors}</pre>',
+ msgAjaxError: 'Произошла ошибка при выполнении операции {operation}. Повторите попытку позже!',
+ msgAjaxProgressError: 'Не удалось выполнить {operation}',
+ ajaxOperations: {
+ deleteThumb: 'удалить файл',
+ uploadThumb: 'загрузить файл',
+ uploadBatch: 'загрузить пакет файлов',
+ uploadExtra: 'загрузка данных с формы'
+ },
+ dropZoneTitle: 'Перетащите файлы сюда &hellip;',
+ dropZoneClickTitle: '<br>(Или щёлкните, чтобы выбрать {files})',
+ fileActionSettings: {
+ removeTitle: 'Удалить файл',
+ uploadTitle: 'Загрузить файл',
+ uploadRetryTitle: 'Повторить загрузку',
+ downloadTitle: 'Загрузить файл',
+ zoomTitle: 'Посмотреть детали',
+ dragTitle: 'Переместить / Изменить порядок',
+ indicatorNewTitle: 'Еще не загружен',
+ indicatorSuccessTitle: 'Загружен',
+ indicatorErrorTitle: 'Ошибка загрузки',
+ indicatorLoadingTitle: 'Загрузка ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Посмотреть предыдущий файл',
+ next: 'Посмотреть следующий файл',
+ toggleheader: 'Переключить заголовок',
+ fullscreen: 'Переключить полноэкранный режим',
+ borderless: 'Переключить режим без полей',
+ close: 'Закрыть подробный предпросмотр'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/sk.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/sk.js
new file mode 100644
index 00000000..28d67e9a
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/sk.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Slovakian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['sk'] = {
+ fileSingle: 'súbor',
+ filePlural: 'súbory',
+ browseLabel: 'Vybrať &hellip;',
+ removeLabel: 'Odstrániť',
+ removeTitle: 'Vyčistiť vybraté súbory',
+ cancelLabel: 'Storno',
+ cancelTitle: 'Prerušiť nahrávanie',
+ uploadLabel: 'Nahrať',
+ uploadTitle: 'Nahrať vybraté súbory',
+ msgNo: 'Nie',
+ msgNoFilesSelected: '',
+ msgCancelled: 'Zrušené',
+ msgPlaceholder: 'Vybrať {files}...',
+ msgZoomModalHeading: 'Detailný náhľad',
+ msgFileRequired: 'Musíte vybrať súbor, ktorý chcete nahrať.',
+ msgSizeTooSmall: 'Súbor "{name}" (<b>{size} KB</b>) je príliš malý, musí mať veľkosť najmenej <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Súbor "{name}" (<b>{size} KB</b>) je príliš veľký, maximálna povolená veľkosť <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Musíte vybrať najmenej <b>{n}</b> {files} pre nahranie.',
+ msgFilesTooMany: 'Počet vybratých súborov <b>({n})</b> prekročil maximálny povolený limit <b>{m}</b>.',
+ msgFileNotFound: 'Súbor "{name}" nebol nájdený!',
+ msgFileSecured: 'Zabezpečenie súboru znemožnilo čítať súbor "{name}".',
+ msgFileNotReadable: 'Súbor "{name}" nie je čitateľný.',
+ msgFilePreviewAborted: 'Náhľad súboru bol prerušený pre "{name}".',
+ msgFilePreviewError: 'Nastala chyba pri načítaní súboru "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'Neplatný typ súboru "{name}". Iba "{types}" súborov sú podporované.',
+ msgInvalidFileExtension: 'Neplatná extenzia súboru "{name}". Iba "{extensions}" súborov sú podporované.',
+ msgFileTypes: {
+ 'image': 'obrázok',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Nahrávanie súboru bolo prerušené',
+ msgUploadThreshold: 'Spracovávam...',
+ msgUploadBegin: 'Inicializujem...',
+ msgUploadEnd: 'Hotovo',
+ msgUploadEmpty: 'Na nahrávanie nie sú k dispozícii žiadne platné údaje.',
+ msgUploadError: 'Chyba',
+ msgValidationError: 'Chyba overenia',
+ msgLoading: 'Nahrávanie súboru {index} z {files} &hellip;',
+ msgProgress: 'Nahrávanie súboru {index} z {files} - {name} - {percent}% dokončené.',
+ msgSelected: '{n} {files} vybraté',
+ msgFoldersNotAllowed: 'Tiahni a pusť iba súbory! Vynechané {n} pustené prečinok(y).',
+ msgImageWidthSmall: 'Šírka obrázku "{name}", musí byť minimálne {size} px.',
+ msgImageHeightSmall: 'Výška obrázku "{name}", musí byť minimálne {size} px.',
+ msgImageWidthLarge: 'Šírka obrázku "{name}" nemôže presiahnuť {size} px.',
+ msgImageHeightLarge: 'Výška obrázku "{name}" nesmie presiahnuť {size} px.',
+ msgImageResizeError: 'Nepodarilo sa získať veľkosť obrázka pre zmenu veľkosti.',
+ msgImageResizeException: 'Chyba pri zmene veľkosti obrázka.<pre>{errors}</pre>',
+ msgAjaxError: 'Pri operácii {operation} sa vyskytla chyba. Skúste to prosím neskôr!',
+ msgAjaxProgressError: '{operation} - neúspešné',
+ ajaxOperations: {
+ deleteThumb: 'odstrániť súbor',
+ uploadThumb: 'nahrať súbor',
+ uploadBatch: 'nahrať várku súborov',
+ uploadExtra: 'odosielanie údajov z formulára'
+ },
+ dropZoneTitle: 'Tiahni a pusť súbory tu &hellip;',
+ dropZoneClickTitle: '<br>(alebo kliknite sem a vyberte {files})',
+ fileActionSettings: {
+ removeTitle: 'Odstrániť súbor',
+ uploadTitle: 'Nahrať súbor',
+ uploadRetryTitle: 'Znova nahrať',
+ downloadTitle: 'Stiahnuť súbor',
+ zoomTitle: 'Zobraziť podrobnosti',
+ dragTitle: 'Posunúť / Preskládať',
+ indicatorNewTitle: 'Ešte nenahral',
+ indicatorSuccessTitle: 'Nahraný',
+ indicatorErrorTitle: 'Chyba pri nahrávaní',
+ indicatorLoadingTitle: 'Nahrávanie ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Zobraziť predchádzajúci súbor',
+ next: 'Zobraziť následujúci súbor',
+ toggleheader: 'Prepnúť záhlavie',
+ fullscreen: 'Prepnúť zobrazenie na celú obrazovku',
+ borderless: 'Prepnúť na bezrámikové zobrazenie',
+ close: 'Zatvoriť detailný náhľad'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/sl.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/sl.js
new file mode 100644
index 00000000..490d7d61
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/sl.js
@@ -0,0 +1,98 @@
+/*!
+ * FileInput Slovenian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author kv1dr <kv1dr.android@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['sl'] = {
+ fileSingle: 'datoteka',
+ filePlural: 'datotek',
+ browseLabel: 'Prebrskaj &hellip;',
+ removeLabel: 'Odstrani',
+ removeTitle: 'Počisti izbrane datoteke',
+ cancelLabel: 'Prekliči',
+ cancelTitle: 'Prekliči nalaganje',
+ uploadLabel: 'Naloži',
+ uploadTitle: 'Naloži izbrane datoteke',
+ msgNo: 'Ne',
+ msgNoFilesSelected: 'Nobena datoteka ni izbrana',
+ msgCancelled: 'Preklicano',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Podroben predogled',
+ msgSizeTooLarge: 'Datoteka "{name}" (<b>{size} KB</b>) presega največjo dovoljeno velikost za nalaganje <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Za nalaganje morate izbrati vsaj <b>{n}</b> {files}.',
+ msgFilesTooMany: 'Število datotek, izbranih za nalaganje <b>({n})</b> je prekoračilo največjo dovoljeno število <b>{m}</b>.',
+ msgFileNotFound: 'Datoteka "{name}" ni bila najdena!',
+ msgFileSecured: 'Zaradi varnostnih omejitev nisem mogel prebrati datoteko "{name}".',
+ msgFileNotReadable: 'Datoteka "{name}" ni berljiva.',
+ msgFilePreviewAborted: 'Predogled datoteke "{name}" preklican.',
+ msgFilePreviewError: 'Pri branju datoteke "{name}" je prišlo do napake.',
+ msgInvalidFileType: 'Napačen tip datoteke "{name}". Samo "{types}" datoteke so podprte.',
+ msgInvalidFileExtension: 'Napačna končnica datoteke "{name}". Samo "{extensions}" datoteke so podprte.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Nalaganje datoteke je bilo preklicano',
+ msgUploadThreshold: 'Procesiram...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Napaki pri validiranju',
+ msgLoading: 'Nalaganje datoteke {index} od {files} &hellip;',
+ msgProgress: 'Nalaganje datoteke {index} od {files} - {name} - {percent}% dokončano.',
+ msgSelected: '{n} {files} izbrano',
+ msgFoldersNotAllowed: 'Povlecite in spustite samo datoteke! Izpuščenih je bilo {n} map.',
+ msgImageWidthSmall: 'Širina slike "{name}" mora biti vsaj {size} px.',
+ msgImageHeightSmall: 'Višina slike "{name}" mora biti vsaj {size} px.',
+ msgImageWidthLarge: 'Širina slike "{name}" ne sme preseči {size} px.',
+ msgImageHeightLarge: 'Višina slike "{name}" ne sme preseči {size} px.',
+ msgImageResizeError: 'Nisem mogel pridobiti dimenzij slike za spreminjanje velikosti.',
+ msgImageResizeException: 'Napaka pri spreminjanju velikosti slike.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Povlecite in spustite datoteke sem &hellip;',
+ dropZoneClickTitle: '<br>(ali kliknite sem za izbiro {files})',
+ fileActionSettings: {
+ removeTitle: 'Odstrani datoteko',
+ uploadTitle: 'Naloži datoteko',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Poglej podrobnosti',
+ dragTitle: 'Premaki / Razporedi',
+ indicatorNewTitle: 'Še ni naloženo',
+ indicatorSuccessTitle: 'Naloženo',
+ indicatorErrorTitle: 'Napaka pri nalaganju',
+ indicatorLoadingTitle: 'Nalagam ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Poglej prejšno datoteko',
+ next: 'Poglej naslednjo datoteko',
+ toggleheader: 'Preklopi glavo',
+ fullscreen: 'Preklopi celozaslonski način',
+ borderless: 'Preklopi način brez robov',
+ close: 'Zapri predogled podrobnosti'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/sv.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/sv.js
new file mode 100644
index 00000000..038b1d99
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/sv.js
@@ -0,0 +1,99 @@
+/*!
+ * FileInput <_LANG_> Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['sv'] = {
+ fileSingle: 'fil',
+ filePlural: 'filer',
+ browseLabel: 'Bläddra &hellip;',
+ removeLabel: 'Ta bort',
+ removeTitle: 'Rensa valda filer',
+ cancelLabel: 'Avbryt',
+ cancelTitle: 'Avbryt pågående uppladdning',
+ uploadLabel: 'Ladda upp',
+ uploadTitle: 'Ladda upp valda filer',
+ msgNo: 'Nej',
+ msgNoFilesSelected: 'Inga filer valda',
+ msgCancelled: 'Avbruten',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'detaljerad förhandsgranskning',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'Filen "{name}" (<b>{size} KB</b>) är för liten och måste vara större än <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'File "{name}" (<b>{size} KB</b>) överstiger högsta tillåtna uppladdningsstorlek <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Du måste välja minst <b>{n}</b> {files} för att ladda upp.',
+ msgFilesTooMany: 'Antal filer valda för uppladdning <b>({n})</b> överstiger högsta tillåtna gränsen <b>{m}</b>.',
+ msgFileNotFound: 'Filen "{name}" kunde inte hittas!',
+ msgFileSecured: 'Säkerhetsbegränsningar förhindrar att läsa filen "{name}".',
+ msgFileNotReadable: 'Filen "{name}" är inte läsbar.',
+ msgFilePreviewAborted: 'Filförhandsvisning avbröts för "{name}".',
+ msgFilePreviewError: 'Ett fel uppstod vid inläsning av filen "{name}".',
+ msgInvalidFileName: 'Ogiltiga eller tecken som inte stöds i filnamnet "{name}".',
+ msgInvalidFileType: 'Ogiltig typ för filen "{name}". Endast "{types}" filtyper stöds.',
+ msgInvalidFileExtension: 'Ogiltigt filtillägg för filen "{name}". Endast "{extensions}" filer stöds.',
+ msgFileTypes: {
+ 'image': 'bild',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'ljud',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'objekt'
+ },
+ msgUploadAborted: 'Filöverföringen avbröts',
+ msgUploadThreshold: 'Bearbetar...',
+ msgUploadBegin: 'Påbörjar...',
+ msgUploadEnd: 'Färdig',
+ msgUploadEmpty: 'Ingen giltig data tillgänglig för uppladdning.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Valideringsfel',
+ msgLoading: 'Laddar fil {index} av {files} &hellip;',
+ msgProgress: 'Laddar fil {index} av {files} - {name} - {percent}% färdig.',
+ msgSelected: '{n} {files} valda',
+ msgFoldersNotAllowed: 'Endast drag & släppfiler! Skippade {n} släpta mappar.',
+ msgImageWidthSmall: 'Bredd på bildfilen "{name}" måste minst vara {size} pixlar.',
+ msgImageHeightSmall: 'Höjden på bildfilen "{name}" måste minst vara {size} pixlar.',
+ msgImageWidthLarge: 'Bredd på bildfil "{name}" kan inte överstiga {size} pixlar.',
+ msgImageHeightLarge: 'Höjden på bildfilen "{name}" kan inte överstiga {size} pixlar.',
+ msgImageResizeError: 'Det gick inte att hämta bildens dimensioner för att ändra storlek.',
+ msgImageResizeException: 'Fel vid storleksändring av bilden.<pre>{errors}</pre>',
+ msgAjaxError: 'Något gick fel med {operation} operationen. Försök igen senare!',
+ msgAjaxProgressError: '{operation} misslyckades',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Drag & släpp filer här &hellip;',
+ dropZoneClickTitle: '<br>(eller klicka för att markera {files})',
+ fileActionSettings: {
+ removeTitle: 'Ta bort fil',
+ uploadTitle: 'Ladda upp fil',
+ uploadRetryTitle: 'Retry upload',
+ zoomTitle: 'Visa detaljer',
+ dragTitle: 'Flytta / Ändra ordning',
+ indicatorNewTitle: 'Inte uppladdat ännu',
+ indicatorSuccessTitle: 'Uppladdad',
+ indicatorErrorTitle: 'Uppladdningsfel',
+ indicatorLoadingTitle: 'Laddar upp...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Visa föregående fil',
+ next: 'Visa nästa fil',
+ toggleheader: 'Rubrik',
+ fullscreen: 'Fullskärm',
+ borderless: 'Gränslös',
+ close: 'Stäng detaljerad förhandsgranskning'
+ }
+ };
+})(window.jQuery); \ No newline at end of file
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/th.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/th.js
new file mode 100644
index 00000000..7a2d0460
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/th.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Thai Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['th'] = {
+ fileSingle: 'ไฟล์',
+ filePlural: 'ไฟล์',
+ browseLabel: 'เลือกดู &hellip;',
+ removeLabel: 'ลบทิ้ง',
+ removeTitle: 'ลบไฟล์ที่เลือกทิ้ง',
+ cancelLabel: 'ยกเลิก',
+ cancelTitle: 'ยกเลิกการอัพโหลด',
+ uploadLabel: 'อัพโหลด',
+ uploadTitle: 'อัพโหลดไฟล์ที่เลือก',
+ msgNo: 'ไม่',
+ msgNoFilesSelected: '',
+ msgCancelled: 'ยกเลิก',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'ตัวอย่างละเอียด',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'ไฟล์ "{name}" (<b>{size} KB</b>) มีขนาดเกินที่ระบบอนุญาตที่ <b>{maxSize} KB</b>, กรุณาลองใหม่อีกครั้ง!',
+ msgFilesTooLess: 'คุณต้องเลือกไฟล์จำนวนอย่างน้อย <b>{n}</b> {files} เพื่ออัพโหลด, กรุณาลองใหม่อีกครั้ง!',
+ msgFilesTooMany: 'ไฟล์ที่คุณเลือกมีจำนวน <b>({n})</b> ซึ่งเกินกว่าที่ระบบอนุญาตที่ <b>{m}</b>, กรุณาลองใหม่อีกครั้ง!',
+ msgFileNotFound: 'ไม่พบไฟล์ "{name}" !',
+ msgFileSecured: 'ระบบความปลอดภัยไม่อนุญาตให้อ่านไฟล์ "{name}".',
+ msgFileNotReadable: 'ไม่สามารถอ่านไฟล์ "{name}" ได้',
+ msgFilePreviewAborted: 'ไฟล์ "{name}" ไม่อนุญาตให้ดูตัวอย่าง',
+ msgFilePreviewError: 'พบปัญหาในการดูตัวอย่างไฟล์ "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'ไฟล์ "{name}" เป็นประเภทไฟล์ที่ไม่ถูกต้อง, อนุญาตเฉพาะไฟล์ประเภท "{types}"',
+ msgInvalidFileExtension: 'ไฟล์ "{name}" เป็น extension ที่ไมถูกต้อง, อนุญาตเฉพาะไฟล์ extension "{extensions}"',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'อัปโหลดไฟล์ถูกยกเลิก',
+ msgUploadThreshold: 'Processing...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'ข้อผิดพลาดในการตรวจสอบ',
+ msgLoading: 'กำลังโหลดไฟล์ {index} จาก {files} &hellip;',
+ msgProgress: 'กำลังโหลดไฟล์ {index} จาก {files} - {name} - {percent}%',
+ msgSelected: '{n} {files} ถูกเลือก',
+ msgFoldersNotAllowed: 'Drag & drop เฉพาะไฟล์เท่านั้น! ข้าม dropped folder จำนวน {n}',
+ msgImageWidthSmall: 'ความกว้างของภาพไฟล์ "{name}" ต้องมีอย่างน้อย {size} px.',
+ msgImageHeightSmall: 'ความสูงของภาพไฟล์ "{name}" ต้องมีอย่างน้อย {size} px.',
+ msgImageWidthLarge: 'ความกว้างของภาพไฟล์ "{name}" ไม่เกิน {size} พิกเซล.',
+ msgImageHeightLarge: 'ความสูงของไฟล์ภาพ "{name}" ไม่เกิน {size} พิกเซล.',
+ msgImageResizeError: 'ไม่สามารถรับขนาดภาพเพื่อปรับขนาด',
+ msgImageResizeException: 'ข้อผิดพลาดขณะปรับขนาดภาพ<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Drag & drop ไฟล์ตรงนี้ &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: 'ลบไฟล์',
+ uploadTitle: 'อัปโหลดไฟล์',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'ดูรายละเอียด',
+ dragTitle: 'Move / Rearrange',
+ indicatorNewTitle: 'ยังไม่ได้อัปโหลด',
+ indicatorSuccessTitle: 'อัพโหลด',
+ indicatorErrorTitle: 'อัปโหลดข้อผิดพลาด',
+ indicatorLoadingTitle: 'อัพโหลด ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'View previous file',
+ next: 'View next file',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery); \ No newline at end of file
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/tr.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/tr.js
new file mode 100644
index 00000000..ff4d9555
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/tr.js
@@ -0,0 +1,99 @@
+/*!
+ * FileInput Turkish Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['tr'] = {
+ fileSingle: 'dosya',
+ filePlural: 'dosyalar',
+ browseLabel: 'Gözat &hellip;',
+ removeLabel: 'Sil',
+ removeTitle: 'Seçilen dosyaları sil',
+ cancelLabel: 'İptal',
+ cancelTitle: 'Devam eden yüklemeyi iptal et',
+ uploadLabel: 'Yükle',
+ uploadTitle: 'Seçilen dosyaları yükle',
+ msgNo: 'Hayır',
+ msgNoFilesSelected: '',
+ msgCancelled: 'İptal edildi',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Detaylı Önizleme',
+ msgFileRequired: 'Yüklemek için bir dosya seçmelisiniz.',
+ msgSizeTooSmall: '"{name}"(<b>{size} KB</b>) dosyası çok küçük ve <b>{minSize} KB</b> boyutundan büyük olmalıdır.',
+ msgSizeTooLarge: '"{name}" dosyasının boyutu (<b>{size} KB</b>) izin verilen azami dosya boyutu olan <b>{maxSize} KB</b>\'tan büyük.',
+ msgFilesTooLess: 'Yüklemek için en az <b>{n}</b> {files} dosya seçmelisiniz.',
+ msgFilesTooMany: 'Yüklemek için seçtiğiniz dosya sayısı <b>({n})</b> azami limitin <b>({m})</b> altında olmalıdır.',
+ msgFileNotFound: '"{name}" dosyası bulunamadı!',
+ msgFileSecured: 'Güvenlik kısıtlamaları "{name}" dosyasının okunmasını engelliyor.',
+ msgFileNotReadable: '"{name}" dosyası okunabilir değil.',
+ msgFilePreviewAborted: '"{name}" dosyası için önizleme iptal edildi.',
+ msgFilePreviewError: '"{name}" dosyası okunurken bir hata oluştu.',
+ msgInvalidFileName: '"{name}" dosya adında geçersiz veya desteklenmeyen karakterler var.',
+ msgInvalidFileType: '"{name}" dosyasının türü geçerli değil. Yalnızca "{types}" türünde dosyalara izin veriliyor.',
+ msgInvalidFileExtension: '"{name}" dosyasının uzantısı geçersiz. Yalnızca "{extensions}" uzantılı dosyalara izin veriliyor.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Dosya yükleme iptal edildi',
+ msgUploadThreshold: 'İşlem yapılıyor...',
+ msgUploadBegin: 'Başlıyor...',
+ msgUploadEnd: 'Başarılı',
+ msgUploadEmpty: 'Yüklemek için geçerli veri mevcut değil.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Doğrulama Hatası',
+ msgLoading: 'Dosya yükleniyor {index} / {files} &hellip;',
+ msgProgress: 'Dosya yükleniyor {index} / {files} - {name} - %{percent} tamamlandı.',
+ msgSelected: '{n} {files} seçildi',
+ msgFoldersNotAllowed: 'Yalnızca dosyaları sürükleyip bırakabilirsiniz! {n} dizin(ler) göz ardı edildi.',
+ msgImageWidthSmall: '"{name}" adlı görüntü dosyasının genişliği en az {size} piksel olmalıdır.',
+ msgImageHeightSmall: '"{name}" adlı görüntü dosyasının yüksekliği en az {size} piksel olmalıdır.',
+ msgImageWidthLarge: '"{name}" adlı görüntü dosyasının genişliği {size} pikseli geçemez.',
+ msgImageHeightLarge: '"{name}" adlı görüntü dosyasının yüksekliği {size} pikseli geçemez.',
+ msgImageResizeError: 'Görüntü boyutlarını yeniden boyutlandıramadı.',
+ msgImageResizeException: 'Görüntü boyutlandırma sırasında hata.<pre>{errors}</pre>',
+ msgAjaxError: '{operation} işlemi ile ilgili bir şeyler ters gitti. Lütfen daha sonra tekrar deneyiniz!',
+ msgAjaxProgressError: '{operation} işlemi başarısız oldu.',
+ ajaxOperations: {
+ deleteThumb: 'dosya silme',
+ uploadThumb: 'dosya yükleme',
+ uploadBatch: 'toplu dosya yükleme',
+ uploadExtra: 'form verisi yükleme'
+ },
+ dropZoneTitle: 'Dosyaları buraya sürükleyip bırakın',
+ dropZoneClickTitle: '<br>(ya da {files} seçmek için tıklayınız)',
+ fileActionSettings: {
+ removeTitle: 'Dosyayı kaldır',
+ uploadTitle: 'Dosyayı yükle',
+ uploadRetryTitle: 'Retry upload',
+ zoomTitle: 'Ayrıntıları görüntüle',
+ dragTitle: 'Taşı / Yeniden düzenle',
+ indicatorNewTitle: 'Henüz yüklenmedi',
+ indicatorSuccessTitle: 'Yüklendi',
+ indicatorErrorTitle: 'Yükleme Hatası',
+ indicatorLoadingTitle: 'Yükleniyor ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Önceki dosyayı göster',
+ next: 'Sonraki dosyayı göster',
+ toggleheader: 'Üst bilgi geçiş',
+ fullscreen: 'Tam ekran geçiş',
+ borderless: 'Çerçevesiz moda geçiş',
+ close: 'Detaylı önizlemeyi kapat'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/uk.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/uk.js
new file mode 100644
index 00000000..99294f19
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/uk.js
@@ -0,0 +1,101 @@
+/*!
+ * FileInput Ukrainian Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author CyanoFresh <cyanofresh@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['uk'] = {
+ fileSingle: 'файл',
+ filePlural: 'файли',
+ browseLabel: 'Вибрати &hellip;',
+ removeLabel: 'Видалити',
+ removeTitle: 'Видалити вибрані файли',
+ cancelLabel: 'Скасувати',
+ cancelTitle: 'Скасувати поточну загрузку',
+ uploadLabel: 'Загрузити',
+ uploadTitle: 'Загрузити вибрані файли',
+ msgNo: 'Немає',
+ msgNoFilesSelected: '',
+ msgCancelled: 'Cкасовано',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Детальний превью',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Файл "{name}" (<b>{size} KB</b>) перевищує максимальний розмір <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Ви повинні вибрати як мінімум <b>{n}</b> {files} для загрузки.',
+ msgFilesTooMany: 'Кількість вибраних файлів <b>({n})</b> перевищує максимально допустиму кількість <b>{m}</b>.',
+ msgFileNotFound: 'Файл "{name}" не знайдено!',
+ msgFileSecured: 'Обмеження безпеки перешкоджають читанню файла "{name}".',
+ msgFileNotReadable: 'Файл "{name}" неможливо прочитати.',
+ msgFilePreviewAborted: 'Перегляд скасований для файла "{name}".',
+ msgFilePreviewError: 'Сталася помилка під час читання файла "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'Заборонений тип файла для "{name}". Тільки "{types}" дозволені.',
+ msgInvalidFileExtension: 'Заборонене розширення для файла "{name}". Тільки "{extensions}" дозволені.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Вивантаження файлу перервана',
+ msgUploadThreshold: 'Processing...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Помилка перевірки',
+ msgLoading: 'Загрузка файла {index} із {files} &hellip;',
+ msgProgress: 'Загрузка файла {index} із {files} - {name} - {percent}% завершено.',
+ msgSelected: '{n} {files} вибрано',
+ msgFoldersNotAllowed: 'Дозволено перетягувати тільки файли! Пропущено {n} папок.',
+ msgImageWidthSmall: 'Ширина зображення "{name}" повинна бути не менше {size} px.',
+ msgImageHeightSmall: 'Висота зображення "{name}" повинна бути не менше {size} px.',
+ msgImageWidthLarge: 'Ширина зображення "{name}" не може перевищувати {size} px.',
+ msgImageHeightLarge: 'Висота зображення "{name}" не може перевищувати {size} px.',
+ msgImageResizeError: 'Не вдалося розміри зображення, щоб змінити розмір.',
+ msgImageResizeException: 'Помилка при зміні розміру зображення.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Перетягніть файли сюди &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: 'Видалити файл',
+ uploadTitle: 'Загрузити файл',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Подивитися деталі',
+ dragTitle: 'Move / Rearrange',
+ indicatorNewTitle: 'Ще не загружено',
+ indicatorSuccessTitle: 'Загружено',
+ indicatorErrorTitle: 'Помилка при загрузці',
+ indicatorLoadingTitle: 'Загрузка ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'View previous file',
+ next: 'View next file',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/vi.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/vi.js
new file mode 100644
index 00000000..7d140b5e
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/vi.js
@@ -0,0 +1,101 @@
+/*!
+ * FileInput Vietnamese Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['vi'] = {
+ fileSingle: 'tập tin',
+ filePlural: 'các tập tin',
+ browseLabel: 'Duyệt &hellip;',
+ removeLabel: 'Gỡ bỏ',
+ removeTitle: 'Bỏ tập tin đã chọn',
+ cancelLabel: 'Hủy',
+ cancelTitle: 'Hủy upload',
+ uploadLabel: 'Upload',
+ uploadTitle: 'Upload tập tin đã chọn',
+ msgNo: 'Không',
+ msgNoFilesSelected: 'Không tập tin nào được chọn',
+ msgCancelled: 'Đã hủy',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: 'Chi tiết xem trước',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: 'Tập tin "{name}" (<b>{size} KB</b>) vượt quá kích thước giới hạn cho phép <b>{maxSize} KB</b>.',
+ msgFilesTooLess: 'Bạn phải chọn ít nhất <b>{n}</b> {files} để upload.',
+ msgFilesTooMany: 'Số lượng tập tin upload <b>({n})</b> vượt quá giới hạn cho phép là <b>{m}</b>.',
+ msgFileNotFound: 'Không tìm thấy tập tin "{name}"!',
+ msgFileSecured: 'Các hạn chế về bảo mật không cho phép đọc tập tin "{name}".',
+ msgFileNotReadable: 'Không đọc được tập tin "{name}".',
+ msgFilePreviewAborted: 'Đã dừng xem trước tập tin "{name}".',
+ msgFilePreviewError: 'Đã xảy ra lỗi khi đọc tập tin "{name}".',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: 'Tập tin "{name}" không hợp lệ. Chỉ hỗ trợ loại tập tin "{types}".',
+ msgInvalidFileExtension: 'Phần mở rộng của tập tin "{name}" không hợp lệ. Chỉ hỗ trợ phần mở rộng "{extensions}".',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: 'Đã dừng upload',
+ msgUploadThreshold: 'Đang xử lý...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: 'Lỗi xác nhận',
+ msgLoading: 'Đang nạp {index} tập tin trong số {files} &hellip;',
+ msgProgress: 'Đang nạp {index} tập tin trong số {files} - {name} - {percent}% hoàn thành.',
+ msgSelected: '{n} {files} được chọn',
+ msgFoldersNotAllowed: 'Chỉ kéo thả tập tin! Đã bỏ qua {n} thư mục.',
+ msgImageWidthSmall: 'Chiều rộng của hình ảnh "{name}" phải tối thiểu là {size} px.',
+ msgImageHeightSmall: 'Chiều cao của hình ảnh "{name}" phải tối thiểu là {size} px.',
+ msgImageWidthLarge: 'Chiều rộng của hình ảnh "{name}" không được quá {size} px.',
+ msgImageHeightLarge: 'Chiều cao của hình ảnh "{name}" không được quá {size} px.',
+ msgImageResizeError: 'Không lấy được kích thước của hình ảnh để resize.',
+ msgImageResizeException: 'Resize hình ảnh bị lỗi.<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: 'Kéo thả tập tin vào đây &hellip;',
+ dropZoneClickTitle: '<br>(hoặc click để chọn {files})',
+ fileActionSettings: {
+ removeTitle: 'Gỡ bỏ',
+ uploadTitle: 'Upload tập tin',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: 'Phóng lớn',
+ dragTitle: 'Di chuyển / Sắp xếp lại',
+ indicatorNewTitle: 'Chưa được upload',
+ indicatorSuccessTitle: 'Đã upload',
+ indicatorErrorTitle: 'Upload bị lỗi',
+ indicatorLoadingTitle: 'Đang upload ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'Xem tập tin phía trước',
+ next: 'Xem tập tin tiếp theo',
+ toggleheader: 'Ẩn/hiện tiêu đề',
+ fullscreen: 'Bật/tắt toàn màn hình',
+ borderless: 'Bật/tắt chế độ không viền',
+ close: 'Đóng'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/zh-TW.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/zh-TW.js
new file mode 100644
index 00000000..3f7986a9
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/zh-TW.js
@@ -0,0 +1,102 @@
+/*!
+ * FileInput Chinese Traditional Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author kangqf <kangqingfei@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['zh-TW'] = {
+ fileSingle: '單一檔案',
+ filePlural: '複選檔案',
+ browseLabel: '瀏覽 &hellip;',
+ removeLabel: '移除',
+ removeTitle: '清除選取檔案',
+ cancelLabel: '取消',
+ cancelTitle: '取消上傳中檔案',
+ uploadLabel: '上傳',
+ uploadTitle: '上傳選取檔案',
+ msgNo: '沒有',
+ msgNoFilesSelected: '',
+ msgCancelled: '取消',
+ zoomTitle: '詳細資料',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: '內容預覽',
+ msgFileRequired: 'You must select a file to upload.',
+ msgSizeTooSmall: 'File "{name}" (<b>{size} KB</b>) is too small and must be larger than <b>{minSize} KB</b>.',
+ msgSizeTooLarge: '檔案 "{name}" (<b>{size} KB</b>) 大小超過上限 <b>{maxSize} KB</b>.',
+ msgFilesTooLess: '最少必須選擇 <b>{n}</b> {files} 來上傳. ',
+ msgFilesTooMany: '上傳的檔案數量 <b>({n})</b> 超過最大檔案上傳限制 <b>{m}</b>.',
+ msgFileNotFound: '檔案 "{name}" 未發現!',
+ msgFileSecured: '安全限制,禁止讀取檔案 "{name}".',
+ msgFileNotReadable: '文件 "{name}" 不可讀取.',
+ msgFilePreviewAborted: '檔案 "{name}" 預覽中止.',
+ msgFilePreviewError: '讀取 "{name}" 發生錯誤.',
+ msgInvalidFileName: 'Invalid or unsupported characters in file name "{name}".',
+ msgInvalidFileType: '檔案類型錯誤 "{name}". 只能使用 "{types}" 類型的檔案.',
+ msgInvalidFileExtension: '附檔名錯誤 "{name}". 只能使用 "{extensions}" 的檔案.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: '該文件上傳被中止',
+ msgUploadThreshold: 'Processing...',
+ msgUploadBegin: 'Initializing...',
+ msgUploadEnd: 'Done',
+ msgUploadEmpty: 'No valid data available for upload.',
+ msgUploadError: 'Error',
+ msgValidationError: '驗證錯誤',
+ msgLoading: '載入第 {index} 個檔案,共 {files} &hellip;',
+ msgProgress: '載入第 {index} 個檔案,共 {files} - {name} - {percent}% 成功.',
+ msgSelected: '{n} {files} 選取',
+ msgFoldersNotAllowed: '只支援單檔拖曳! 無法使用 {n} 拖拽的資料夹.',
+ msgImageWidthSmall: '圖檔寬度"{name}"必須至少為{size}像素(px).',
+ msgImageHeightSmall: '圖檔高度"{name}"必須至少為{size}像素(px).',
+ msgImageWidthLarge: '圖檔寬度"{name}"不能超過{size}像素(px).',
+ msgImageHeightLarge: '圖檔高度"{name}"不能超過{size}像素(px).',
+ msgImageResizeError: '無法獲取的圖像尺寸調整。',
+ msgImageResizeException: '錯誤而調整圖像大小。<pre>{errors}</pre>',
+ msgAjaxError: 'Something went wrong with the {operation} operation. Please try again later!',
+ msgAjaxProgressError: '{operation} failed',
+ ajaxOperations: {
+ deleteThumb: 'file delete',
+ uploadThumb: 'file upload',
+ uploadBatch: 'batch file upload',
+ uploadExtra: 'form data upload'
+ },
+ dropZoneTitle: '拖曳檔案至此 &hellip;',
+ dropZoneClickTitle: '<br>(or click to select {files})',
+ fileActionSettings: {
+ removeTitle: '刪除檔案',
+ uploadTitle: '上傳檔案',
+ uploadRetryTitle: 'Retry upload',
+ downloadTitle: 'Download file',
+ zoomTitle: '詳細資料',
+ dragTitle: 'Move / Rearrange',
+ indicatorNewTitle: '尚未上傳',
+ indicatorSuccessTitle: '上傳成功',
+ indicatorErrorTitle: '上傳失敗',
+ indicatorLoadingTitle: '上傳中 ...'
+ },
+ previewZoomButtonTitles: {
+ prev: 'View previous file',
+ next: 'View next file',
+ toggleheader: 'Toggle header',
+ fullscreen: 'Toggle full screen',
+ borderless: 'Toggle borderless mode',
+ close: 'Close detailed preview'
+ }
+ };
+})(window.jQuery);
diff --git a/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/zh.js b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/zh.js
new file mode 100644
index 00000000..0e2ed3a5
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/main/resources/js/locales/bootstrap-fileinput/zh.js
@@ -0,0 +1,100 @@
+/*!
+ * FileInput Chinese Translations
+ *
+ * This file must be loaded after 'fileinput.js'. Patterns in braces '{}', or
+ * any HTML markup tags in the messages must not be converted or translated.
+ *
+ * @see http://github.com/kartik-v/bootstrap-fileinput
+ * @author kangqf <kangqingfei@gmail.com>
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+(function ($) {
+ "use strict";
+
+ $.fn.fileinputLocales['zh'] = {
+ fileSingle: '文件',
+ filePlural: '个文件',
+ browseLabel: '选择 &hellip;',
+ removeLabel: '移除',
+ removeTitle: '清除选中文件',
+ cancelLabel: '取消',
+ cancelTitle: '取消进行中的上传',
+ uploadLabel: '上传',
+ uploadTitle: '上传选中文件',
+ msgNo: '没有',
+ msgNoFilesSelected: '',
+ msgCancelled: '取消',
+ msgPlaceholder: 'Select {files}...',
+ msgZoomModalHeading: '详细预览',
+ msgFileRequired: '必须选择一个文件上传.',
+ msgSizeTooSmall: '文件 "{name}" (<b>{size} KB</b>) 必须大于限定大小 <b>{minSize} KB</b>.',
+ msgSizeTooLarge: '文件 "{name}" (<b>{size} KB</b>) 超过了允许大小 <b>{maxSize} KB</b>.',
+ msgFilesTooLess: '你必须选择最少 <b>{n}</b> {files} 来上传. ',
+ msgFilesTooMany: '选择的上传文件个数 <b>({n})</b> 超出最大文件的限制个数 <b>{m}</b>.',
+ msgFileNotFound: '文件 "{name}" 未找到!',
+ msgFileSecured: '安全限制,为了防止读取文件 "{name}".',
+ msgFileNotReadable: '文件 "{name}" 不可读.',
+ msgFilePreviewAborted: '取消 "{name}" 的预览.',
+ msgFilePreviewError: '读取 "{name}" 时出现了一个错误.',
+ msgInvalidFileName: '文件名 "{name}" 包含非法字符.',
+ msgInvalidFileType: '不正确的类型 "{name}". 只支持 "{types}" 类型的文件.',
+ msgInvalidFileExtension: '不正确的文件扩展名 "{name}". 只支持 "{extensions}" 的文件扩展名.',
+ msgFileTypes: {
+ 'image': 'image',
+ 'html': 'HTML',
+ 'text': 'text',
+ 'video': 'video',
+ 'audio': 'audio',
+ 'flash': 'flash',
+ 'pdf': 'PDF',
+ 'object': 'object'
+ },
+ msgUploadAborted: '该文件上传被中止',
+ msgUploadThreshold: '处理中...',
+ msgUploadBegin: '正在初始化...',
+ msgUploadEnd: '完成',
+ msgUploadEmpty: '无效的文件上传.',
+ msgUploadError: 'Error',
+ msgValidationError: '验证错误',
+ msgLoading: '加载第 {index} 文件 共 {files} &hellip;',
+ msgProgress: '加载第 {index} 文件 共 {files} - {name} - {percent}% 完成.',
+ msgSelected: '{n} {files} 选中',
+ msgFoldersNotAllowed: '只支持拖拽文件! 跳过 {n} 拖拽的文件夹.',
+ msgImageWidthSmall: '图像文件的"{name}"的宽度必须是至少{size}像素.',
+ msgImageHeightSmall: '图像文件的"{name}"的高度必须至少为{size}像素.',
+ msgImageWidthLarge: '图像文件"{name}"的宽度不能超过{size}像素.',
+ msgImageHeightLarge: '图像文件"{name}"的高度不能超过{size}像素.',
+ msgImageResizeError: '无法获取的图像尺寸调整。',
+ msgImageResizeException: '调整图像大小时发生错误。<pre>{errors}</pre>',
+ msgAjaxError: '{operation} 发生错误. 请重试!',
+ msgAjaxProgressError: '{operation} 失败',
+ ajaxOperations: {
+ deleteThumb: '删除文件',
+ uploadThumb: '上传文件',
+ uploadBatch: '批量上传',
+ uploadExtra: '表单数据上传'
+ },
+ dropZoneTitle: '拖拽文件到这里 &hellip;<br>支持多文件同时上传',
+ dropZoneClickTitle: '<br>(或点击{files}按钮选择文件)',
+ fileActionSettings: {
+ removeTitle: '删除文件',
+ uploadTitle: '上传文件',
+ uploadRetryTitle: 'Retry upload',
+ zoomTitle: '查看详情',
+ dragTitle: '移动 / 重置',
+ indicatorNewTitle: '没有上传',
+ indicatorSuccessTitle: '上传',
+ indicatorErrorTitle: '上传错误',
+ indicatorLoadingTitle: '上传 ...'
+ },
+ previewZoomButtonTitles: {
+ prev: '预览上一个文件',
+ next: '预览下一个文件',
+ toggleheader: '缩放',
+ fullscreen: '全屏',
+ borderless: '无边界模式',
+ close: '关闭当前预览'
+ }
+ };
+})(window.jQuery);
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
new file mode 100644
index 00000000..1da1fe1a
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/TestUtil.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package test.pl.treksoft.kvision
+
+import org.w3c.dom.Element
+import pl.treksoft.jquery.jQuery
+import pl.treksoft.kvision.core.Widget
+import pl.treksoft.kvision.panel.Root
+import kotlin.browser.document
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+interface TestSpec {
+ fun beforeTest()
+
+ fun afterTest()
+
+ fun run(code: () -> Unit) {
+ beforeTest()
+ code()
+ afterTest()
+ }
+}
+
+interface SimpleSpec : TestSpec {
+
+ override fun beforeTest() {
+ }
+
+ override fun afterTest() {
+ }
+
+}
+
+interface DomSpec : TestSpec {
+
+ override fun beforeTest() {
+ val fixture = "<div style=\"display: none\" id=\"pretest\">" +
+ "<div id=\"test\"></div></div>"
+ document.body?.insertAdjacentHTML("afterbegin", fixture)
+ }
+
+ override fun afterTest() {
+ val div = document.getElementById("pretest")
+ div?.let { jQuery(it).remove() }
+ jQuery(`object` = ".modal-backdrop").remove()
+ }
+
+ fun assertEqualsHtml(expected: String?, actual: String?, message: String?) {
+ if (expected != null && actual != null) {
+ val exp = jQuery(html = expected)
+ val act = jQuery(html = actual)
+ val result = exp[0]?.isEqualNode(act[0])
+ if (result == true) {
+ assertTrue(result == true, message)
+ } else {
+ assertEquals(expected, actual, message)
+ }
+ } else {
+ assertEquals(expected, actual, message)
+ }
+ }
+}
+
+interface WSpec : DomSpec {
+
+ fun runW(code: (widget: Widget, element: Element?) -> Unit) {
+ run {
+ val root = Root("test", true)
+ val widget = Widget()
+ widget.id = "test_id"
+ root.add(widget)
+ val element = document.getElementById("test_id")
+ code(widget, element)
+ }
+ }
+
+}
+
+external fun require(name: String): dynamic
diff --git a/kvision-modules/kvision-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
new file mode 100644
index 00000000..626b70e4
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadInputSpec.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.upload
+
+import pl.treksoft.jquery.jQuery
+import pl.treksoft.kvision.form.upload.UploadInput
+import pl.treksoft.kvision.panel.Root
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+
+class UploadInputSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", true)
+ val upi = UploadInput(multiple = true).apply {
+ id = "idti"
+ }
+ root.add(upi)
+ val content = document.getElementById("test")?.let { jQuery(it).find("input.form-control")[0]?.outerHTML }
+ assertEqualsHtml(
+ "<input class=\"form-control\" id=\"idti\" type=\"file\" multiple=\"true\">",
+ content,
+ "Should render correct file input control for multiple files"
+ )
+ upi.multiple = false
+ val content2 = document.getElementById("test")?.let { jQuery(it).find("input.form-control")[0]?.outerHTML }
+ assertEqualsHtml(
+ "<input class=\"form-control\" id=\"idti\" type=\"file\">",
+ content2,
+ "Should render correct file input control for single file"
+ )
+ }
+ }
+
+}
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
new file mode 100644
index 00000000..bea4ddee
--- /dev/null
+++ b/kvision-modules/kvision-upload/src/test/kotlin/test/pl/treksoft/kvision/form/upload/UploadSpec.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.upload
+
+import pl.treksoft.jquery.jQuery
+import pl.treksoft.kvision.form.upload.Upload
+import pl.treksoft.kvision.panel.Root
+import test.pl.treksoft.kvision.DomSpec
+import kotlin.browser.document
+import kotlin.test.Test
+
+class UploadSpec : DomSpec {
+
+ @Test
+ fun render() {
+ run {
+ val root = Root("test", true)
+ val upi = Upload(multiple = true)
+ val id = upi.input.id
+ root.add(upi)
+ val content = document.getElementById("test")?.let { jQuery(it).find("input.form-control")[0]?.outerHTML }
+ assertEqualsHtml(
+ "<input class=\"form-control\" id=\"$id\" type=\"file\" multiple=\"true\">",
+ content,
+ "Should render correct file input control for multiple files"
+ )
+ upi.multiple = false
+ val content2 = document.getElementById("test")?.let { jQuery(it).find("input.form-control")[0]?.outerHTML }
+ assertEqualsHtml(
+ "<input class=\"form-control\" id=\"$id\" type=\"file\">",
+ content2,
+ "Should render correct file input control for single file"
+ )
+ }
+ }
+
+}
diff --git a/kvision-modules/shared.gradle b/kvision-modules/shared.gradle
new file mode 100644
index 00000000..4ded9c6c
--- /dev/null
+++ b/kvision-modules/shared.gradle
@@ -0,0 +1,65 @@
+apply plugin: 'kotlin-platform-js'
+apply plugin: 'org.jetbrains.kotlin.frontend'
+apply plugin: 'kotlinx-serialization'
+
+kotlinFrontend {
+
+ webpackBundle {
+ bundleName = "main"
+ contentPath = file('src/main/web')
+ mode = production ? "production" : "development"
+ }
+
+ define "PRODUCTION", production
+}
+
+
+dependencies {
+ if (!project.gradle.startParameter.taskNames.contains("generatePomFileForMavenProjectPublication")) {
+ compile project(":kvision-modules:kvision-base")
+ } else {
+ compile rootProject
+ }
+}
+
+task cleanLibs(type: Delete) {
+ delete 'build/js', 'build/libs'
+}
+
+if (project.gradle.startParameter.taskNames.contains("jar")) {
+ compileKotlin2Js.dependsOn 'cleanLibs'
+}
+
+jar {
+ duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+}
+
+compileKotlin2Js {
+ kotlinOptions.metaInfo = true
+ kotlinOptions.outputFile = "$project.buildDir.path/js/${project.name}.js"
+ kotlinOptions.sourceMap = !production
+ kotlinOptions.moduleKind = 'umd'
+}
+
+compileTestKotlin2Js {
+ kotlinOptions.metaInfo = true
+ kotlinOptions.outputFile = "$project.buildDir.path/js-tests/${project.name}-tests.js"
+ kotlinOptions.sourceMap = !production
+ kotlinOptions.moduleKind = 'umd'
+}
+
+task copyResources(type: Copy) {
+ from "src/main/resources"
+ into file(buildDir.path + "/js")
+}
+
+task copyResourcesForTests(type: Copy) {
+ from "src/main/resources"
+ into file(buildDir.path + "/js-tests/")
+}
+
+afterEvaluate {
+ tasks.getByName("webpack-bundle") { dependsOn(copyResources) }
+ tasks.getByName("webpack-run") { dependsOn(copyResources) }
+ tasks.getByName("karma-start") { dependsOn(copyResources, copyResourcesForTests) }
+}