aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/pl/treksoft/kvision/form/upload
diff options
context:
space:
mode:
authorRobert Jaros <rjaros@finn.pl>2018-03-29 01:05:02 +0200
committerRobert Jaros <rjaros@finn.pl>2018-03-29 01:05:02 +0200
commit5ff62fa68f1c6d9693aeb4c27116b77c43e1a309 (patch)
tree98755ab99b1920ebfa22fd0e26229560b5a68f4f /src/main/kotlin/pl/treksoft/kvision/form/upload
parent8a1370e0f661d2c90fd5aed8e868341119a866cd (diff)
downloadkvision-5ff62fa68f1c6d9693aeb4c27116b77c43e1a309.tar.gz
kvision-5ff62fa68f1c6d9693aeb4c27116b77c43e1a309.tar.bz2
kvision-5ff62fa68f1c6d9693aeb4c27116b77c43e1a309.zip
Components for file upload.
Diffstat (limited to 'src/main/kotlin/pl/treksoft/kvision/form/upload')
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/upload/Upload.kt304
-rw-r--r--src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt270
2 files changed, 574 insertions, 0 deletions
diff --git a/src/main/kotlin/pl/treksoft/kvision/form/upload/Upload.kt b/src/main/kotlin/pl/treksoft/kvision/form/upload/Upload.kt
new file mode 100644
index 00000000..e6b397eb
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/form/upload/Upload.kt
@@ -0,0 +1,304 @@
+/*
+ * Copyright (c) 2018. Robert Jaros
+ */
+package pl.treksoft.kvision.form.upload
+
+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.FilesFormControl
+import pl.treksoft.kvision.form.HelpBlock
+import pl.treksoft.kvision.panel.SimplePanel
+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")), FilesFormControl {
+
+ /**
+ * 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()
+ }
+
+ /**
+ * 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/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt b/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt
new file mode 100644
index 00000000..b0c07c3e
--- /dev/null
+++ b/src/main/kotlin/pl/treksoft/kvision/form/upload/UploadInput.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2018. Robert Jaros
+ */
+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.FormInput
+import pl.treksoft.kvision.form.InputSize
+import pl.treksoft.kvision.utils.obj
+
+/**
+ * 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<File>?
+ 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()
+
+ 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<File>? {
+ val v = getFiles()
+ return if (v.isNotEmpty()) v else null
+ }
+
+ override fun afterInsert(node: VNode) {
+ getElementJQueryD()?.fileinput(getSettingsObj())
+ }
+
+ 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")
+ }
+
+ private fun getFiles(): List<File> {
+ return (getElementJQueryD()?.fileinput("getFileStack") as Array<File>).toList()
+ }
+
+ /**
+ * 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 {
+ 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
+ }
+ }
+
+ 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
+ }
+ }
+}