ashmelev
09/16/2021, 9:07 PM/api/v1/image/upload
. The server-side is reacting correctly and the file is uploading properly and the server returns a Flux<String> with success/fail messages. The problem is that I cannot sort out how to capture that response stream and navigate the UI to a page where I can display results. Rather, the browser navigates to /api/v1/image/upload
and displays the results as text there. If I then hit the "Back button" in the browser I am back to normal Frontend code. In the JQuery docs there should be a success
handler that would catch the servers response data, but I have been unable to sort out where to hook into that in KVision. I'm putting sa sample of my "upload" Frontend code into the thread.ashmelev
09/16/2021, 9:07 PMprivate fun uploadImage() {
val formPanel = formPanel<UploadImage>(<http://FormMethod.POST|FormMethod.POST>, "/api/v1/image/upload", FormEnctype.MULTIPART) {
add(UploadImage::uploadName, Text(label = "Text", name = "uploadName"))
add(UploadImage::file, Upload(label = "Upload file (image only)").apply {
this.upload()
showUpload = false
showCancel = false
explorerTheme = true
dropZoneEnabled = false
allowedFileTypes = setOf("image")
id = "file"
name = "file"
})
hPanel {
button("Submit", type = ButtonType.SUBMIT)
}
}
AppScope.launch {
val dialog = Dialog<String>("Upload Image") {
add(formPanel)
}
dialog.getResult()?.let { uploadImageResult ->
console.log("uploadImageResult: $uploadImageResult")
}
}
}
ashmelev
09/16/2021, 9:07 PMRobert Jaros
09/16/2021, 9:10 PMRobert Jaros
09/16/2021, 9:11 PMupload
page.Robert Jaros
09/16/2021, 9:12 PMRobert Jaros
09/16/2021, 9:12 PMUpload("/api/v1/image/upload", label = "Upload file (image only)")
Robert Jaros
09/16/2021, 9:14 PMFormPanel
as well, and enable upload button of the Upload
component (showUpload = true
)Robert Jaros
09/16/2021, 9:16 PMashmelev
09/16/2021, 9:25 PMashmelev
09/16/2021, 10:50 PMprivate fun uploadImage() {
val formPanel = formPanel<UploadImage> {
add(UploadImage::text, Text(label = "Text"))
add(UploadImage::file, Upload("/api/v1/image/upload", label = "Upload file (image only)").apply {
showUpload = true
showCancel = false
explorerTheme = true
dropZoneEnabled = false
allowedFileTypes = setOf("image")
uploadExtraData = { _, _ -> this@formPanel.getDataJson() } // Needed to pick up the `text` portion of the form
})
}
AppScope.launch {
val dialog = Dialog<String>("Upload Image") {
add(formPanel)
}
dialog.getResult()?.let { uploadImageResult ->
console.log("uploadImageResult: $uploadImageResult")
}
}
}
I had to make some small changes to my Server code (primarily changing Flux<FilePart>
to Flux<Part>
in this line):
@RequestPart("file_data", required = true) fileParts: Flux<Part>
Now, the upload is working, as is the text
field of the Form - no navigation occurs. The remaining issue is that the Dialog does not close and it contains an error message:
*test.png:* SyntaxError: Unexpected token e in JSON at position 1
test.png uploaded
Where test.png
obviously, is my uploaded image file. There are no errors/messages in the browser console and none in the server logs.
I tried dumping the contents of this@formPanel.getDataJson()
to the console, but does not seem to contain anything odd, especially no ` `Unexpected token e`` . Thank again for the help!Robert Jaros
09/17/2021, 5:16 AMstringify
on the response object):
package com.example
import io.kvision.Application
import io.kvision.BootstrapCssModule
import io.kvision.BootstrapModule
import io.kvision.BootstrapUploadModule
import io.kvision.CoreModule
import io.kvision.FontAwesomeModule
import io.kvision.core.jqueryEvent
import io.kvision.core.onEvent
import io.kvision.form.formPanel
import io.kvision.form.text.Text
import io.kvision.form.upload.Upload
import io.kvision.html.button
import io.kvision.i18n.DefaultI18nManager
import io.kvision.i18n.I18n
import io.kvision.modal.Dialog
import io.kvision.module
import io.kvision.panel.root
import io.kvision.require
import io.kvision.startApplication
import io.kvision.types.KFile
import io.kvision.utils.obj
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
@Serializable
data class UploadImage(val text: String? = null, val file: List<KFile>? = null)
class App : Application() {
init {
require("css/kvapp.css")
}
override fun start() {
I18n.manager =
DefaultI18nManager(
mapOf(
"pl" to require("i18n/messages-pl.json"),
"en" to require("i18n/messages-en.json")
)
)
root("kvapp") {
button("Open upload dialog").onClick {
uploadImage()
}
}
}
private fun uploadImage() {
val dialog = Dialog<String>("Upload Image") {
formPanel<UploadImage> {
add(UploadImage::text, Text(label = "Text"))
add(UploadImage::file, Upload("<http://httpbin.org/post>", label = "Upload file (image only)").apply {
showUpload = true
showCancel = false
explorerTheme = true
dropZoneEnabled = false
allowedFileTypes = setOf("image")
uploadExtraData =
{ _, _ ->
obj {
text = this@formPanel.getData().text
}
} // Needed to pick up the `text` portion of the form
onEvent {
jqueryEvent("fileuploaded") { _, data ->
this@Dialog.setResult(JSON.stringify(data.response))
}
}
})
}
}
GlobalScope.launch {
dialog.getResult()?.let { uploadImageResult ->
console.log("uploadImageResult: $uploadImageResult")
}
}
}
}
fun main() {
startApplication(
::App,
module.hot,
BootstrapModule,
BootstrapCssModule,
FontAwesomeModule,
BootstrapUploadModule,
CoreModule
)
}
Robert Jaros
09/17/2021, 5:20 AMadd
method of the Dialog
class and the DSL builder function formPanel
, which adds the created component to some other container. So you are adding your FormPanel
twice to different containers and it's never a good idea 😉ashmelev
09/17/2021, 8:40 PMGlobalScope.launch {
dialog.getResult()?.let { uploadImageResult ->...
Which causes IntelliJ to issue a warning demanding that I add this annotation:
@OptIn(DelicateCoroutinesApi::class)
And set a compiler switch.
I had been using
AppScope.launch {....
to avoid this warning/annotation/switch.
Why are you suggesting GlobalScope
here?Robert Jaros
09/17/2021, 8:44 PMAppScope
is probably better.Robert Jaros
09/17/2021, 8:45 PMashmelev
09/18/2021, 12:42 PM