Uploading files

An example of uploading a single file, or multiple files. Your controller needs to implements UploadableUI.

Back to overview

Upload a single file

Files are always uploaded in small chunks, represented by the DataChunk class. You can define max size validation via @Valid @MaxFileSize("5MB") DataChunk dataChunks. This will count for both a front-end and back-end check. Validation messages appear in elements tagged with the relevant m:validation attribute, similar to regular validation tags. You can also show progress via calling the serverToClient.sendUploadCompletionPercentage("percentage", dataChunk, session);

That means your controller method will be called upon every small chunk. Uploading files in small chunks allows you to stream data to a server's storage without the need to load the entire file into memory. This drastically reduced the resource demands on the server.

JDK22.0.2 w/ Medusa 0.9.6-SNAPSHOT

Client

<section>
    <form>
        <th:block th:if="${percentage == 0}">
            <input type="file" name="filename" accept="image/*" />

            <!-- this is where your validation message would show up if file is too big -->
            <p m:validation="single_file"></p>

            <button id="btn_upload" m:upload="single_file" m:loading-until="upload-done" m:loading-style="top">Upload a file (max 1MB)</button>

        </th:block>

        <!-- show progress -->
        <th:block th:if="${percentage != 0}">
            <p>Percentage uploaded: <span th:text="${percentage}"></span> %</p>
            <button th:if="${percentage == 100}" m:click="reset()">Reset</button>
        </th:block>
    </form>

    <!-- once done, show the image -->
    <th:block th:if="${null != image}">
       <p th:if="${image.completed}">
           <img id="img_upload" th:src="${image.base64ImageString}" th:alt="${image.name}" style="max-width: 75%"/>
           <span style="font-size: 10px;" th:text="${image.name}"></span>
       </p>
    </th:block>
</section>

Server

package sample.getmedusa.showcase.samples.input.special;

import io.getmedusa.medusa.core.annotation.MaxFileSize;
import io.getmedusa.medusa.core.annotation.UIEventPage;
import io.getmedusa.medusa.core.attributes.Attribute;
import io.getmedusa.medusa.core.bidirectional.ServerToClient;
import io.getmedusa.medusa.core.attributes.StandardAttributeKeys;
import io.getmedusa.medusa.core.router.action.DataChunk;
import io.getmedusa.medusa.core.router.action.FileUploadMeta;
import io.getmedusa.medusa.core.router.action.UploadableUI;
import io.getmedusa.medusa.core.session.Session;
import jakarta.validation.Valid;

import java.util.List;

import static io.getmedusa.medusa.core.attributes.Attribute.$$;

@UIEventPage(path = "/detail/uploads", file = "/pages/uploads")
public class UploadsNewController implements UploadableUI {

    final ServerToClient serverToClient;

    public UploadsNewController(ServerToClient serverToClient) {
        this.serverToClient = serverToClient;
    }

    public List<Attribute> setupAttributes() {
        return $$("percentage", 0);
    }

    public List<Attribute> reset() {
        return $$("percentage", 0,
                  "image", null,
                  StandardAttributeKeys.LOADING,  "upload-done");
    }

    @Override
    public void uploadChunk(@Valid @MaxFileSize("1MB") DataChunk dataChunk, Session session) {
        //this is a convenience method to update this session's attribute with the current upload process %
        serverToClient.sendUploadCompletionPercentage("percentage", dataChunk, session);

        if(dataChunk.isCompleted()) {
            serverToClient.sendAttributesToSession($$(StandardAttributeKeys.LOADING,  "upload-done"), session);
        }

        //stream your dataChunk.getChunk() to dataChunk.getFileName() in some storage
        //don't try to store it all in memory, or you lose the benefit of chunking in the first place
        //in this case, we render to the 'image' attribute
    }

    @Override
    public void onCancel(FileUploadMeta uploadMeta, Session session) {
        serverToClient.sendAttributesToSession($$(
            "percentage", 0,
            StandardAttributeKeys.LOADING,  "upload-done"
        ), session);
    }

}