Cannot read drag and dropped file with FileReader - javascript

I am attempting to allow for an image to be uploaded to my site via a drag and drop to my site by dragging and dropping an image onto a specific div on the page:
<div class="container" (drop)="onDrop($event)" (dragover)="onDragOver($event)" (dragleave)="onDragLeave($event)">
<img id="blah" [src]="url || 'http://placehold.it/180'" alt="your image" />
</div>
I have all of the drop related events fiting, I however been unable to actually get the image from FileReader:
What am I doing wrong? my drop related functions from the controller are below for reference.
onDrop(event) {
event.preventDefault();
event.stopPropagation();
this.file = event.dataTransfer.files[0];
const reader = new FileReader();
reader.onload = e => {
this.url = reader.result;
console.log(this.url);
};
reader.readAsDataURL(this.file);
this.fileDraggedOverDiv = false;
}
onDragOver(event) {
event.stopPropagation();
event.preventDefault();
this.fileDraggedOverDiv = true;
return false;
}
onDragLeave(event) {
event.stopPropagation();
event.preventDefault();
this.fileDraggedOverDiv = false;
return false;
}

You are actually successfully passing the file to the onDrop event. Its just that you can't see it when console.log(JSON.stringify(event)); executes, because inherited properties, and non-enumerable properties, are left out as pointed out in this discussion.
To get the image from the event you need to do as follows.
onDrop(event) {
event.preventDefault();
const file = event.dataTransfer.files[0];
}
See this stackblitz for a full working example.

Related

How to get a blob from Javascript with Blazor

I'm using SixLabors.ImageSharp to crop images with Javascript JQuery and all is working rigth but when i need to get the image cropped i don't know how can i get the image without refresh the page and without do a POST.
I'm using Tutexchange tutorial and they obtain the image by a POST method (and i dont wanna do it like that) i think about run a method and with Blazor get the file encoded base64 but when i do it i cant get the reader.result because is inside the onloadend event.
How can i get a image blob without a POST method and without save the image in a folder to read with Blazor?
I tried passing the onloadend reader.result with a method and await with a bucle while to return it when is done but value never is different than null and i tested if the cropper work with a console.log() and all is right with it:
function InitializeCroppie(div_width, div_height) {
basic = $('#main-cropper').croppie
({
enableExif: true,
url: '/images/ChooseImage.png',
viewport: { width: div_width, height: div_height },
boundary: { width: div_width, height: div_height },
showZoomer: false,
format: 'png' //'jpeg'|'png'|'webp'
});
//Reading the contents of the specified Blob or File
function readFile(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#main-cropper').croppie('bind', {
url: e.target.result
});
}
reader.readAsDataURL(input.files[0]);
}
}
// Change Event to Read file content from File input
$('#select').on('change', function () { readFile(this); });
}
var returnThisValue;
async function GetImageCroped() {
returnThisValue = null;
var blob = await basic.croppie('result', 'blob');
var reader = new FileReader();
if (blob != null) {
reader.readAsDataURL(blob);
reader.onloadend = function () {
SetValue(reader.result);
console.log(reader.result);
}
}
while (returnThisValue == null) {
//Waiting...
}
return returnThisValue;
}
function SetValue(value) {
returnThisValue = value;
}
function GetImageValue() {
return returnThisValue;
}
If is impossible without a POST method how can i receive it without reload the page.
EDIT:
I'm doing some test to know if is possible that js let blazor know when put the image in the localStorage to get it synchronic way with Blazor.
AFTER TEST: I tried to get the image with Blazored.LocalStorage and i cannot Chrome has a problem with "big data".
You can add a JavaScript listener event. It will trigger another event when updating the localstorage. It should be loaded when the page is initialized.
var orignalSetItem = localStorage.setItem;
localStorage.setItem = function (key, newValue) {
var setItemEvent = new Event("setItemEvent");
setItemEvent.key = key;
setItemEvent.newValue = newValue;
window.dispatchEvent(setItemEvent);
orignalSetItem.apply(this, arguments);
};
window.addEventListener("setItemEvent", function (e) {
if (e.key == 'image') {
var _this = localStorage.getItem("image")
if (_this != e.newValue) {
console.log(e.newValue)
//call method which sends this blob
} else {
console.log('key->');
console.log(e.newValue)
}
}
});
Trigger method.
$('#btnupload').on('click', function ()
{
basic.croppie('result', 'blob').then(function (blob)
{
localStorage.setItem('image', blob)
//...
});
});

Can't add properly index for each appended divs on input change

You can see my demo here.
I have simple html:
<input type="file" id="files" multiple>
<div class="preview"></div>
So when user choose image he can preview it, and when he click on upload button and add another images he also will see in preview that this images was added.
My problem that I want to add for each appended div a data attribute with index value of it.
My js:
$("#files").on("change", previewFiles);
function previewFiles() {
var preview = $('.preview');
var files = $(this)[0].files;
function readAndPreview(file) {
if ( /\.(jpe?g|png|gif)$/i.test(file.name) ) {
var reader = new FileReader();
reader.onload = function(event) {
var conta = $('<div></div>').addClass("preview__image");
var img = $('<img>');
img.attr('src', event.target.result);
img.attr('title', file.name);
img.appendTo(conta);
conta.appendTo( preview );
}
reader.readAsDataURL(file);
}
}
if (files) {
[].forEach.call(files, readAndPreview);
}
var child = preview.find(".preview__image");
child.each(function(index) {
$(this).attr("data-index", index);
});
}
In my code there is a problem, when user choose image for the first time, data attribute is not created, if he upload image for the second time, appended before div or divs which was first will get data attribute, but new appended div or divs not.
What am I doing wrong?
P.S. I know that my input is clearing its value before uploading another images. All I want is related to preview block. Thanks in advance.
This is happening because you're trying to talk to an element that is not yet in the DOM - because onload is an asynchronous event.
In other words, the element with .preview__image does not get added until AFTER you're running preview.find('.preview__image').
There's various ways round this. One would be to use Promise.all() and convert your reader function to return a promise that gets resolved only once the onload callback has completed.
We could attach a then() to Promise.all, but more elegant would be to use await, which means we'll need to make your outer function asynchronous, by prefixing async to it.
All in all (I've commented the key changes):
$("#files").on("change", previewFiles);
async function previewFiles() { //<-- prefix with async
var preview = $('.preview');
var files = $(this)[0].files;
function readAndPreview(file) {
return new Promise(res => { //<-- reader func now returns promise...
if ( /\.(jpe?g|png|gif)$/i.test(file.name) ) {
var reader = new FileReader();
reader.onload = function(event) {
var conta = $('<div></div>').addClass("preview__image");
var img = $('<img>');
img.attr('src', event.target.result);
img.attr('title', file.name);
img.appendTo(conta);
conta.appendTo( preview );
res(); //...which is resolved once onload is complete
}
reader.readAsDataURL(file);
}
});
}
//now let's have a master promise that waits for the sub-promises to resolve
await Promise.all([...files].map(file => readAndPreview(file)));
//now we can talk to the updated DOM
var child = preview.find(".preview__image");
child.each(function(index) {
$(this).attr("data-index", index);
});
}

Unable to preview image after converting attachment to Base64 using Trix

I'm using Trix, and for uploading attachments our backend developer tells me that I should convert the attachment to base64 and save the base64 data to the database instead of uploading the binary file.
I wrote this code for implementing it, and the output of the input field(HTML) is working as expected, but the image preview doesn't show in the editor.
$(function() {
$(document).on('trix-attachment-add', function(event) {
var attachment = event.originalEvent.attachment;
// Convert data URLs to File objects.
if(attachment.file) {
return uploadAttachment(attachment);
} else {
console.error('Could not upload attachment.');
console.error(attachment);
attachment.remove();
}
});
});
function uploadAttachment(attachment) {
var reader = new FileReader();
console.log(attachment)
// Set the reader to insert images when they are loaded.
reader.onload = function (e) {
var result = e.target.result;
var attrs = {
src : result,
url: result,
href : ''
};
attachment.setAttributes(attrs)
attachment.setUploadProgress(100);
}
// Read image as base64.
reader.readAsDataURL(attachment.file);
}
I don't know what causes this problem.
Try replacing
$(document).on('trix-attachment-add', function(event) {
with
document.addEventListener("trix-attachment-add", function(event) {
This could be event listeners being cached thus firing multiple times. The first load of image works, it could be the next loads that make this look busted.
Could also be Turbolinks issue so wrap your code with this instead:
$(document).on('turbolinks:load', function() {
I've managed to solve the issue by setting the fileObjectURL property as shown below
attachment.attachment.fileObjectURL = result;
Complete code for latest version (works with Symfony 6 easy admin bundle):
(function() {
function asBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
document.addEventListener("trix-file-accept", function(event) {
event.preventDefault();
if (event.file) {
asBase64(event.file).then(function(data) {
let image = document.createElement('img');
image.src = data;
let tmp = document.createElement('div');
tmp.appendChild(image);
let editor = document.querySelector('trix-editor');
editor.editor.insertHTML(tmp.innerHTML);
}).catch(e => console.log(e));
}
}); })();

JavaScript Module pattern and Drag and Drop API

I have a sample module in js that is suppose to manage Drag and Drop file upload.
The code seems to work for 'dragenter' event function but when I drop the file, and 'drop' event should call the dropped function, the code always forwards to the file path.
Here is the code sample
var testModule = (function testBuilder(){
function call(evt) {
evt.preventDefault();
console.log('works');
}
function dropped(evt) {
evt.preventDefault();
console.log('file dropped');
}
var element = document.getElementById('testBlock');
function init() {
element.addEventListener('dragenter', call, false);
element.addEventListener('drop', dropped, false);
}
publicAPI = {
init: init
};
return publicAPI;
})();
window.onload = function() {
testModule.init();
};
and a jsbin here https://jsbin.com/redixucate/edit?js,console,output
If anyone can figure out why it keeps redirecting the file path, I would rly appreciate it.
Add 'dragover' event with preventDefault and it should work.
Inside your init():
element.addEventListener('dragover', over, false);
and over function:
function over(e) {
e = e || window.event;
if(e.preventDefault) {
e.preventDefault();
}
}
Also add the same prevention to your other two functions ..
See https://jsbin.com/xemovariwu/1/edit?js,console,output
Also see this question/answer.

HTML5/Canvas onDrop event isn't firing?

I'm playing with file upload, drag and drop, and canvas, but for some reason the ondrop function never seems to run, here's the fiddle I'm working in: http://jsfiddle.net/JKirchartz/E4yRv/
the relevant code is :
canvas.ondrop = function(e) {
e.preventDefault();
var file = e.dataTransfer.files[0],
reader = new FileReader();
reader.onload = function(event) {
var img = new Image(),
imgStr = event.target.result;
state.innerHTML += ' Image Uploaded: <a href="' +
imgStr + '" target="_blank">view image</a><br />';
img.src = event.target.result;
img.onload = function(event) {
context.height = canvas.height = this.height;
context.width = canvas.width = this.width;
context.drawImage(this, 0, 0);
state.innerHTML += ' Canvas Loaded: view canvas<br />';
};
};
reader.readAsDataURL(file);
return false;
};
why doesn't this event fire? I've tried it in firefox and chrome.
In order to get the drop event to fire at all you need to have an ondragover function:
canvas.ondragover = function(e) {
e.preventDefault();
return false;
};
If you try to drag your cat picture into the canvas it'll still not work, this error is reported in the Firefox console:
[04:16:42.298] uncaught exception: [Exception... "Component returned failure code: 0x80004003 (NS_ERROR_INVALID_POINTER) [nsIDOMFileReader.readAsDataURL]" nsresult: "0x80004003 (NS_ERROR_INVALID_POINTER)" location: "JS frame :: http://fiddle.jshell.net/_display/ :: <TOP_LEVEL> :: line 57" data: no]
However it will work if you drag an image from your desktop. I think for images in the page you should use regular DOM access methods, the File API is only needed for external files dragged into the browser.
As far as I can tell, robertc's answer is how browsers continue to behave, you have to have an ondragover function set.
But to expand on it slightly, the function must return false and not true or undefined-- no-op functions will return undefined. It doesn't seem to matter whether you prevent default, the ondrop event handler will trigger. You will want a preventDefault in the ondrop function, otherwise the file will be immediately downloaded to your browser's default download folder:
document.getElementById('drop-zone').ondragover = function(e) {
return false;
}
document.getElementById('drop-zone').ondrop = function(e) {
e.preventDefault();
console.log('ondrop', e);
}
#drop-zone {
border: solid;
height: 3em;
width: 10em;
}
<div id="drop-zone">Drop zone</div>

Categories

Resources