diff options
Diffstat (limited to 'kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft')
4 files changed, 856 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..f3370743 --- /dev/null +++ b/kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/KVManagerUpload.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 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-fa/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/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") + require("bootstrap-fileinput/themes/explorer-fa/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..71e00ede --- /dev/null +++ b/kvision-modules/kvision-bootstrap-upload/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt @@ -0,0 +1,375 @@ +/* + * 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-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.fileActionSettings = obj { + this.showUpload = showUpload + this.showRemove = showRemove + } + 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() +} |