Files input change event fires only once - javascript

I've made a small script using some of the HTML5 files features, which allows you to select one or more files, and each time it will write the name of the file(s). Everything works as it should, only the event to detect the value change of the files input fire only once, so how can I make it fire every change and not only on the first change?
By the way, here is what I made:
http://tamir.netspot.co.il/html5/files/

If you want to upload twice, clear file input value
$('input[type="file"]').val(null);
jsfiddle test

It appears that the change event listener is being removed because you're using innerHTML to update the same element (wrapper) that the input itself is inside. So the contents of the wrapper element – including the file input – is being re-rendered, and along the way, the event listener is removed (or, rather, it's connected to an element that's no longer there).
Here's a simple jsfiddle that does exactly the same as your code, except that it prints the selected file names in a different element than the element the input is in. And it works (in WebKit, anyway)
Here's further proof (I basically copied your code, and only added a line to re-register the event listener after the modification of wrapper.innerHTML)
So, the change event does fire for each change, but the input that's being observed is removed by the use of innerHTML on the input's parent element.
I honestly don't know whether this is a legitimate browser bug or not. It makes sense for innerHTML to "overwrite" the existing input element, yet the browser is smart enough to not not reset the input's value, so you'd think listeners would stick around too… so… well, it's confusing

I'm not sure why but none of the answers to this old question are all that simple. Here's the way to do this easily today...
with jquery...
$('#myfileinputfieldid')[0].onchange = function(e) {
//do something with e. Like write an image to a canvas or make a yummy cup of coffee
e.target.value = '';
};
that's it after you have changed the value to something other than the file that was selected the next time the file input is clicked the onchange
event will fire.

Basically, if you still have a value for your input, no extra event would be fired. I'm working with react and i Had to clear the value of the input for the next event to be triggered.
Using a ref, you can do something like this.
buttonRef.current.value = null;

Instead of using onchange use oninput event
$scope.ShowIcon = function (input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
reader.onload = function (e) {
$('#iAIcon')
.attr('src', e.target.result)
};
reader.readAsDataURL(input.files[0]);
}
}

None of the above worked for me, I actually had to create a new "dummy" file input field each time it was changed - allowing me to capture the change event again.
The process for me was:
OnChange
- move file input to another element
- create a new file input to capture the change event again

addEventListener wont work for IE8(Not sure about IE9 onwards). We need to use attachEvent listiner. If you need cross browser support then use this
if (!inputfile.addEventListener) {
inputfile.attachEvent("onclick", setCheckedValues); //IE8
}else {
inputfile.addEventListener("click", setCheckedValues, false); //Other browser
}

ok well according to #Flambino the input is being re-rendered. For whatever reason this may be, for me its irrelevant.
The $.on('change', callback) functionality is lost.
Try using .delegate function which I absolutely love!
http://api.jquery.com/delegate/
Ok so delegate is exactly the same, it just tells jquery if there is an element rendered on screen with a particular handle, attach a functionality to it.
So even if the element is re-rendered, it will still keep to function.
$(document).delegate('.file_upload_btn', 'change', function(){});
You may think this is a throw away function & say whats the difference but this has saved me a lot of time on projects.

I got the .change callback to fire on every new file by reassigning the .change function at the end of its own callback:
$('#myfileinputfieldid').change(function (event) {
scope.processFile(event.target.files[0]);
});
scope.processFile = function(fileStruct) {
doStuff;
// Reassign the onchange callback.
$('#myfileinputfieldid').change(function (event) {
scope.processFile(event.target.files[0]);
});
};

In my case i use ajax to upload file.
I just clear the value of input with onclick event handler.
$('#myFile').click(function(e) {e.target.value = '';});
$('#myFile').change(function(e) {
var file = e.target.value;
var formdata = new FormData();
formdata.append('file', file, 'somefile');
$.ajax({
type: 'POST',
url: './uploadFile',
data: formdata,
processData: false,
contentType: false,
success: function(data){
}
});
});

Related

Making one handler debounced for all input elements in react

I have a parent component with a state holding the data and a handler to update it upon any input element change.
Now I want to improve performance as this handler is doing quite a lot of validations, so I would like to wait until the last keystroke before starting the validations but I still want the input to react to users typing.
The issue is when I debounce my handler, it works but in the time of the debounce delay if I switch to a different input element fast and start typing it will delete the old event so I'm stuck with the result inside the element but with validation only on the second element.
I tried to create a new function that will call that handler to make each a diffrerent instance so they wont block each other but it didnt work.
Tried to copy the function and then run it, no luck.
I also tried setting timeout by myself, which did work but then I didnt manage to find a way to stop it on new event :(
Im stuck with debouncing the handler on the constructor level:
this.onAdvancedChange = debounce(this.onAdvancedChange, 500);
which is exactly what I described before.
There is two handlers one which is not debounced to react to input typing:
onSimpleChange({ target }) {
const newData = ...; // Im copying target data to a new object here to pass instead.
setState(..., () => this.onAdvancedChange({ target: newData }));
}
onAdvancedChange({ target }) {
validate(target);
if needed -> setState(...);
}
It does work, but again when I switch fast to a different input and start typing while the prev debounce didnt hit yet, it wont hit at all and only the second will validate.
How Do I make my advanced handler private for each input call, or maybe the other option I tried to use timeout but then how to stop it on every new event for each input individualy?
Thank you a LOT in advance.

Trigger 'drop' event while providing the file data

Question
How could I, while providing the file, trigger a drop event of a field, on which I do not have access at loading.
Details
There is a page with a field on which is attached a drop listener that process an image when dropped. I would like to be able to use this process by pasting an image. I know how to get the file from a paste, but I do not know how to dispatch a drop event that would contains this very file.
The obstacles are:
The code is obfuscated, I cannot access the function linked with the listener by name.
There is no way to get the drop listener after it being attached to an element. It seems there is some way to do it in the console, but not from a script.
I do not control the page rendering; i.e. I cannot intercept the event listener addition.
Vanilla Javascript & could only work in Chrome (extension).
This page is built in vanilla; i.e. no jQuery or anything.
Does anyone have an idea on how to tackle this task?
I am looking into DragEvent but "although this interface has a constructor, it is not possible to create a useful DataTransfer object from script, since DataTransfer objects have a processing and security model that is coordinated by the browser during drag-and-drops."
I saw a possible approach https://stackoverflow.com/a/39066443/1004274 but I want to mimic a real drop event with its data, i.e. pass a file I got via clipboardData.items[0].getAsFile(); instead of just text.
You can fake the drop event, and fake pretty much everything that's in there. What you'll have problem doing is triggering a default event, such as opening a file in a tab by dropping it. The reason isn't so much because of the dataTransfer object being protected, but the event not being trusted. By having trusted event and protected dataTransfer, you can be sure you won't pass data to a trusted event, and that you won't trigger default event with unwanted data.
But depending on how the drop function is accessing the file that is dropped, you might be able to trick it with a fake drop event and a fake dataTransfer object. See this fiddle for a general idea of how it may work:
var a = document.getElementById('link');
var dropZone1 = document.getElementById('dropZone1');
var dropZone2 = document.getElementById('dropZone2');
var fakeDropBtn = document.getElementById('fakeDropBtn');
dropZone1.addEventListener('dragover', function(e) {
e.preventDefault();
});
dropZone2.addEventListener('dragover', function(e) {
e.preventDefault();
});
dropZone1.addEventListener('drop', function(e) {
// This first drop zone is simply to get access to a file.
// In your case the file would come from the clipboard
// but you need to work with an extension to have access
// to paste data, so here I use a drop event
e.preventDefault();
fakeDropBtn.classList.remove('disabled');
dropZone2.classList.remove('disabled');
var fileToDrop = e.dataTransfer.files[0];
// You create a drop event
var fakeDropEvent = new DragEvent('drop');
// You override dataTransfer with whichever property
// and method the drop function needs
Object.defineProperty(fakeDropEvent, 'dataTransfer', {
value: new FakeDataTransfer(fileToDrop)
});
fakeDropBtn.addEventListener('click', function(e) {
e.preventDefault();
// the fake event will be called on the button click
dropZone2.dispatchEvent(fakeDropEvent);
});
});
dropZone2.addEventListener('drop', function(e) {
e.preventDefault();
// this is the fake event being called. In this case for
// example, the function gets access to dataTransfer files.
// You'll see the result will be the same with a real
// drop event or with a fake drop event. The only thing
// that matters is to override the specific property this function
// is using.
var url = window.URL.createObjectURL(e.dataTransfer.files[0]);
a.href = url;
a.click();
window.URL.revokeObjectURL(url);
});
function FakeDataTransfer(file) {
this.dropEffect = 'all';
this.effectAllowed = 'all';
this.items = [];
this.types = ['Files'];
this.getData = function() {
return file;
};
this.files = [file];
};
https://jsfiddle.net/5m2u0tux/6/

Possible to set event.dataTransfer asynchronously after drag start?

Currently I am working on a scenario that I need to change the data attached to a dragging element after the dragging starts. Basically the drop zones are input fields or textareas so i would like to use the native event.dataTransfer.setData since the native drag-drop can make the caret move with mouse.
Everything works perfectly at the beginning if i just call the setData() synchronously in the listener of dragstart event.
dragItem.addEventListener("dragstart",function(event){
event.dataTransfer.setData("text/plain","data set in dragstart");
})
However, my scenario could be that the data is from an asynchronouly callback function like an AJAX request. Then I attempted to call setData() in this callback function but nothing seems to be successfully set.
dragItem.addEventListener("dragstart",function(event){
event.dataTransfer.setData("text/plain","data set in dragstart");
//like a callback in Ajax or resolve function of a promise.
setTimeout(function(){
console.log("attempt to set data asynchonrously after drag start");
event.dataTransfer.setData("text/plain","asynchonrously set data");
//looks like failed, the console output is empty, even not the original set one
console.log(event.dataTransfer.getData("text/plain"));
},200)
})
I also tried to change the data in dragenter and even drop event listeners of the drop zones. But there was still no luck.
This plunker shows what i have tried.
Then i referred to the MDN API document to find offical api description of the dataTransfer object. But there is nothing about problems like asynchronously using setData after drag start. One really weird thing is that if i try to compare the two dataTransfer references in dragstart and drop event, the are NOT the same object. Now I have no clue what is actually happening.
So my questions are
Is it possible to set the data in dataTransfer after the dragging is started with the native APIs (without using event.preventDefault) ?
If the first question's answer is NO, what kind of workaround could I try? I could think of something about how to save and get the data to transfer. My main concern is that if event.preventDefault() is used on drop, it is not easy to get the caret move with mouse like the native dropping does.
Here is your answer.
Firstly,You can only set data in your dragstart event. So, every time any dragstart event starts it sets value and what ever you set asynchronouly will not get reflected no matter what.
So, one thing that you can do is have a global object and set that on drag start event like this:
var someObj = {
asd : 'something'
}
and set in you dragstart callback, like this:
dragItem.addEventListener("dragstart",function(event){
event.dataTransfer.setData("text/plain", someObj.asd);
dataTransferObject = event.dataTransfer;
setTimeout(function(){
console.log("attempt to set data asynchonrously after drag start");
//event.dataTransfer.setData("text/plain","asynchonrously set data");
someObj.asd = 'asynchonrously';
//looks like failed, the console output is empty
console.log(event.dataTransfer.getData("text/plain"));
}, 100)
})
Object is by default reference type.
Now, You can set someObj.asd to any value you want and you will see new value reflected. But there is a problem with this approach, value will be reflected after drop happens means after event has been ended.
So, to solve your problem what you can do is don't set any value on dragstart just set some value to someObj.asd on drag start and use someObj.asd on drop.
Here is a link of what i'm trying to explain:
https://plnkr.co/edit/SZhI9lGRI37eEd1nWfhn?p=preview
see console on drop event you will see reflected value there.
DON'T SEE UI JUST GO FOR CONSOLE

Event handlers removed when resetting input file

I have the following code that resets the input file:
$(this).replaceWith($(this).clone());
However, I noticed that if I used the particular input file, its event firing was not handled. So this the code for handling the event when a file input is changed:
$('#frontfile').change(function(){
reader2 = Main.Mod.image_change(this);
reader2.onload = rearImageIsLoaded;
});
What seems to be the problem?
Your responses will be greatly appreciated...
You can pass true to the clone method to clone the handlers and data associated with it
$(this).replaceWith($(this).clone(true, true));

jQuery addClass when value of hidden input changes dyamically

I have a contact form that sends a value to a hidden input on successful completion of the sendmail function. I want to detect this value change and then use it to apply a class to a div/paragraph.
I asked a similar question recently and I'm aware that this requires the script to continually check the doc after DOM is loaded but even after adding .change() it just doesn't seem to want to add the class.
Here's the jQuery:
$(document).ready(function() {
$("#acf_success_sent").change(function(){
if ($("#acf_success_sent").val() == "1"){
$("#acf_verified").addClass('gone');
}
});
});
any help would be great. here's a link to a test version of form in case you're interested, everything works except the verified symbol doesn't disappear after a successful send http://seeshell.me/forms/contact.php
There'll be no "change" event fired when code updates the value of your <input> element, so the handler you've registered won't run. What you could do however is fire "change" from a watchdog:
var watchdog = setInterval(function() {
if ($('#acf_success_sent').val() !== originalValue)
$('#acf_success_sent').trigger('change');
}, 100);
How you set up "originalValue" depends on your application. You could, for example, keep a separate ".data()" value, and watch for whenever your saved value differs from the current "value" attribute. Or you could keep the value in a closure variable:
var watchdog = (function() {
var $acfSuccessSent = $('#acf_success_sent'), cachedValue = $acfSuccessSent.val();
return function() {
if (cachedValue !== $acfSuccessSent.val())
$acfSuccessSent.trigger('change');
};
})();

Categories