aboutsummaryrefslogtreecommitdiff
path: root/kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl
diff options
context:
space:
mode:
Diffstat (limited to 'kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl')
-rw-r--r--kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/KVManagerUpload.kt76
-rw-r--r--kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/Upload.kt333
-rw-r--r--kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt377
-rw-r--r--kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/utils/Utils.kt73
4 files changed, 859 insertions, 0 deletions
diff --git a/kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/KVManagerUpload.kt b/kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/KVManagerUpload.kt
new file mode 100644
index 00000000..1d8f05b0
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/KVManagerUpload.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision
+
+internal val kVManagerUploadInit = KVManagerUpload.init()
+
+/**
+ * Internal singleton object which initializes and configures KVision upload module.
+ */
+internal object KVManagerUpload {
+
+ init {
+ require("bootstrap-fileinput/css/fileinput.min.css")
+ require("bootstrap-fileinput/themes/explorer-fas/theme.min.css")
+ 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/kr.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")
+ require("bootstrap-fileinput/themes/explorer-fas/theme.min.js")
+ require("bootstrap-fileinput/themes/fas/theme.min.js")
+ }
+
+ internal fun init() {}
+}
diff --git a/kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/Upload.kt b/kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/Upload.kt
new file mode 100644
index 00000000..bd931127
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap-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.InvalidFeedback
+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 invalidFeedback: InvalidFeedback = InvalidFeedback().apply { visible = false }
+
+ init {
+ @Suppress("LeakingThis")
+ input.eventTarget = this
+ this.addInternal(flabel)
+ this.addInternal(input)
+ this.addInternal(invalidFeedback)
+ counter++
+ }
+
+ override fun getSnClass(): List<StringBoolPair> {
+ val cl = super.getSnClass().toMutableList()
+ if (validatorError != null) {
+ cl.add("text-danger" 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
+ */
+ override 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-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt b/kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt
new file mode 100644
index 00000000..faae5274
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt
@@ -0,0 +1,377 @@
+/*
+ * 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.form.ValidationStatus
+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()
+ /**
+ * The validation status of the input.
+ */
+ override var validationStatus: ValidationStatus? 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()
+ validationStatus?.let {
+ cl.add(it.className to true)
+ }
+ 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())
+ if (uploadUrl != null) {
+ 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
+ } ?: listOf()
+ }
+
+ /**
+ * 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.
+ */
+ override fun focus() {
+ getElementJQuery()?.focus()
+ }
+
+ /**
+ * Makes the input element blur.
+ */
+ override 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-fas" else "fas"
+ 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.fileActionSettings = obj {
+ this.showUpload = showUpload
+ this.showRemove = showRemove
+ }
+ this.autoOrientImage = false
+ this.purifyHtml = false
+ this.language = language
+ }
+ }
+
+ companion object {
+
+ /**
+ * 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
+ */
+ 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
+ */
+ 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-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/utils/Utils.kt b/kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/utils/Utils.kt
new file mode 100644
index 00000000..bdae5091
--- /dev/null
+++ b/kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/utils/Utils.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2017-present Robert Jaros
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package pl.treksoft.kvision.utils
+
+import kotlinx.coroutines.suspendCancellableCoroutine
+import org.w3c.files.File
+import org.w3c.files.FileReader
+import pl.treksoft.kvision.form.Form
+import pl.treksoft.kvision.form.FormPanel
+import pl.treksoft.kvision.form.KFilesFormControl
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+
+/**
+ * Suspending extension function to get file content.
+ * @return file content
+ */
+suspend fun File.getContent(): String = suspendCancellableCoroutine { cont ->
+ val reader = FileReader()
+ reader.onload = {
+ @Suppress("UnsafeCastFromDynamic")
+ cont.resume(reader.result)
+ }
+ reader.onerror = { e ->
+ cont.resumeWithException(Exception(e.type))
+ }
+ reader.readAsDataURL(this@getContent)
+}
+
+/**
+ * Returns current data model with file content read for all KFiles controls.
+ * @return data model
+ */
+suspend fun <K : Any> Form<K>.getDataWithFileContent(): K {
+ val map = this.fields.entries.associateBy({ it.key }, {
+ val value = it.value
+ if (value is KFilesFormControl) {
+ value.getValue()?.map {
+ it.copy(content = value.getNativeFile(it)?.getContent())
+ }
+ } else {
+ value.getValue()
+ }
+ })
+ return this.modelFactory(map.withDefault { null })
+}
+
+/**
+ * Returns current data model with file content read for all KFiles controls.
+ * @return data model
+ */
+suspend fun <K : Any> FormPanel<K>.getDataWithFileContent(): K {
+ return this.form.getDataWithFileContent()
+}