<@UFT11FKSP> Hi. I have a situation where I am tr...
# kvision
a
@Robert Jaros Hi. I have a situation where I am trying to upload a large image file and need the result from the MultiPart Form POST. I am using the Ajax version of your upload technique (as far as I could properly grasp from the doc). I am uploading the file to an endpoint on the server (Spring WebFlux), say
/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.
Same code for upload:
Copy code
private 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")
      }
    }
  }
Note: Some of the code above is from KVision samples, some from KVision documentation. The "incorrect" portions are mine 🙂
r
Hi
This is not ajax upload. You are submitting a form here and that is why you are redirected to the
upload
page.
Something like
Upload("/api/v1/image/upload", label = "Upload file (image only)")
remove submit button from the
FormPanel
as well, and enable upload button of the
Upload
component (
showUpload = true
)
If you still have problems I will try to give you a working code tomorrow morning (CET), because I have to go now.
a
Thank you!!!
Robert - for when you have time. I made the code changes you recommended:
Copy code
private 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):
Copy code
@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!
r
This is a working solution, using httpbin.org test endpoint (which sends back JSON from the request as a response, which is why I'm using
stringify
on the response object):
Copy code
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
    )
}
Please note, in your original code you are using both
add
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 😉
a
Robert - Thank you, that fixed it. One follow-up question: In your code example you use
Copy code
GlobalScope.launch {
      dialog.getResult()?.let { uploadImageResult ->...
Which causes IntelliJ to issue a warning demanding that I add this annotation:
Copy code
@OptIn(DelicateCoroutinesApi::class)
And set a compiler switch. I had been using
Copy code
AppScope.launch {....
to avoid this warning/annotation/switch. Why are you suggesting
GlobalScope
here?
r
No, I'm absolutely not suggesting it. Your
AppScope
is probably better.
I've just used GlobalScope for simplicity.
a
Ahh. Understood, thank you again for the help and clarifications