An example of uploading a single file, or multiple files. Your controller needs to implements UploadableUI.
Back to overviewFiles 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.
<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>
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);
}
}