I am trying to create a form that saves some data, but I intend to include the possibility of adding a photo media. I did some research on the subject, and saw that they are implementing this new feature in liveview. I was wondering if there is any other way that I can create this type of event for the socket to understand the file upload?
It isn’t really possible at the moment as far as I am aware.
There are some workarounds discussed here: https://github.com/phoenixframework/phoenix_live_view/issues/104
I have it working quite nicely so if you can’t figure out a way forward based on the information there, let me know.
Thanks for the information .Can you explain to me how you managed to implement this event?
So between my comments (mindok) and those from montebrown in that github issue there should be quite a few pointers to implementing, but here’s the (messy) code…
Image upload controller - handles incoming images
defmodule AppWeb.UploadController do
use AppWeb, :controller
alias AppWeb.Context.Media
def create(conn, %{"file" => %Plug.Upload{}=upload, "id" => raw_thing_id}) do
{thing_id, ""} = Integer.parse(raw_thing_id)
# You need code to store the file from the Plug.Upload struct here
case Media.create_upload_from_plug_upload(upload, thing_id) do
{:ok, upload}->
put_flash(conn, :info, "file uploaded correctly")
conn |> put_status(:created) |> json(%{id: upload.id})
{:error, reason}->
put_flash(conn, :error, "error upload file: #{inspect(reason)}")
render(conn, "new.html")
end
end
end
Router entries
resources "/uploads", UploadController, only: [:create]
leex template with script embedded (haven’t got around to extracting js as yet, plus you will need some css from dropzone.js). And just to make it super nasty I inject an id into the script. In other places where I do file uploads I attach the JS to an element using liveview hooks and look up the id from a data attribute.
You may have to fight with CSRF tokens a bit too - they have changed in recent liveview versions
<div phx-update="ignore" class="dropzone dz-clickable" id="divtarget">
<div class="dz-small dz-message d-flex flex-column">
<span class="icon"><i class="fas fa-cloud-upload-alt fa-3x"></i></span>
Drag & Drop here or click
</div>
</div>
<script src="/js/dropzone.js"></script>
<script src="/js/canvasToBlob.js"></script>
<script src="/js/cropper.js"></script>
<script>
Dropzone.options.divtarget = {
paramName: "file", // The name that will be used to transfer the file
url: '/uploads',
maxFilesize: 10, // MB
headers: {
'x-csrf-token': '<%= @token %>'
},
init: function () {
this.on("sending", function (fil, xhr, formData) {
formData.append("id", '<%= @thing.id %>')
});
this.on("complete", function (file) {
this.removeFile(file);
});
},
acceptedFiles: "image/jpeg, image/jpg, image/png, image/gif",
transformFile: function (file, done) {
var myDropZone = this;
// Create the image editor overlay
var editor = document.createElement('div');
editor.style.position = 'fixed';
editor.style.left = 0;
editor.style.right = 0;
editor.style.top = 0;
editor.style.bottom = 0;
editor.style.zIndex = 9999;
editor.style.backgroundColor = '#000';
// Create the confirm button
var confirm = document.createElement('button');
confirm.style.position = 'absolute';
confirm.style.left = '10px';
confirm.style.top = '10px';
confirm.style.zIndex = 9999;
confirm.textContent = 'Confirm';
confirm.addEventListener('click', function () {
// Get the canvas with image data from Cropper.js
var canvas = cropper.getCroppedCanvas({});
// Turn the canvas into a Blob (file object without a name)
canvas.toBlob(function (blob) {
// Update the image thumbnail with the new image data
myDropZone.createThumbnail(
blob,
myDropZone.options.thumbnailWidth,
myDropZone.options.thumbnailHeight,
myDropZone.options.thumbnailMethod,
false,
function (dataURL) {
// Update the Dropzone file thumbnail
myDropZone.emit('thumbnail', file, dataURL);
// Return modified file to dropzone
done(blob);
}
);
}, 'image/jpeg', 0.9);
// Remove the editor from view
editor.parentNode.removeChild(editor);
});
editor.appendChild(confirm);
// Load the image
var image = new Image();
image.src = URL.createObjectURL(file);
editor.appendChild(image);
// Append the editor to the page
document.body.appendChild(editor);
// Create Cropper.js and pass image
var cropper = new Cropper(image, {});
}
};
</script>
I’ve actually been tinkering with LiveView myself and ran into a similar challenge when I was trying to implement photo uploads. I ended up building something where I could preview the photo before it actually saved, which was a cool feature (kind of like a digital picture frame for the app). For socket communication, I used something like a multipart form with Phoenix LiveView’s phx-change
to manage the file uploads. It was a bit tricky at first, but I think using LiveView for dynamic events is super powerful, especially when you’re combining media like photos.
Liveview has official support for file uploads these days: Uploads — Phoenix LiveView v1.0.4