Event handlers removed when resetting input file - javascript

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));

Related

How to delete specific event listener on an element in JavaScript? [duplicate]

Some of the third party plugin will attach the eventListener into the site. How to I remove the eventListener without knowing the function that attached.
I refer this removeEventListener but I can't get any clue to remove this.
Eg: getEventListeners(window) shows the events attached. But, when I try to remove the event using window.removeEventListener("eventname") is not working without knowing that function.
Please help, Thanks in advance.
getEventListeners(window) will return a map of events and their registered event listeners.
So for DOMContentLoaded event for example you can have many event listeners. If you know the index of the listener you want to remove (or if there exists only one), you can do:
var eventlistener = getEventListeners(window)["DOMContentLoaded"][index];
window.removeEventListener("DOMContentLoaded",
eventlistener.listener,
eventlistener.useCapture);
Unfortunately, you cannot do that. You need to have a reference to the event handler function in order to remove it by removeEventListener.
Your only option if you cannot get that reference would be by entirely replacing that Node.
Update: 2023
EventListeners can be removed without knowing the actual function reference. But it will only work in modern browsers.
Use AbortController to remove the event. With AbortSignal, you can simply get the signal to remove it for you:
Sample Code:
const controller = new AbortController();
const { signal } = controller;
window.addEventListener('resize', () => doSomething(), { signal });
controller.abort(); // It wll remove the "resize" event handler.
You can check and add a polyfill for older browsers

Source js file of event listener

Is there a way I could identify the source file of a specific event?
My events are being removed, because the usage of document.open usage in the code. This is something can't change. I am trying to re-use my events, but because of another limitation I have, I need to know which events are coming from different JS files.
EventTarget.prototype.addEventListenerBase = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type,listener,params)
{
var isFromSourceX = "nameoffile.js"
var worker_events = ['DOMContentLoaded',
'beforeunload', "blue","devicemotion","deviceorientation",
"error","focus","load","message","orientationchange",
"resize","scroll","storage","click"];
var _this=this;
var _isEventExists = window._stackedListeners.filter(function(item){ return item.type==type && item.target==_this; })[0]===undefined?false:true;
var isHtmlElement = this instanceof HTMLElement;
if (worker_events.indexOf(type)>-1 && !_isEventExists && isFromSourceX) {
window._stackedListeners.push({
target: _this,
type: type,
listener: listener,
params: params
});
this.addEventListenerBase(type, listener, params);
}
};
})(self);
Eventually, I'll be pushing all needed events to an array to later attach them on the web page. But the problem as mentioned, is that I need to identify the source (to exclude external events) in the webpage.
p.s: I did not chose to work with document.open :)
Any ideas?
Thanks.
If you tend to use Chrome Dev Tools you will be able to see all the events associated with the specific type if you open the Source tab. On the right pane in Event listener breakpoints you could check the event category that you are interested in. By executing specific ones you will be able to see the source file.
It does not give you all the event listeners out of the box but it could be helpful if you are trying to diagnose your issue. You could also find event listeners on a dom node by inspecting it. On the right pane there should be a list of event listeners for it.
Hope I gave you some glimpse.

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/

waiting till an on change handler is complete before saving model

I have a User model(Backbone.js) and I want to update its settings attribute and then save it to the server. Settings is in JSON format, and the way I have it set up is that settings is the string version and settingsJSON is the object version. I bind functions to the change event of each so that when one changes, it updates the other.
The problem I am having, is that the save method is running before the changed handler is finished running. Is there any way i could ensure that all event handlers for that model are complete or something like that?
how I'm calling it:
currentUser.get('settingsJSON').apps = appsEnabled;
currentUser.save();
My event handlers:
Initialize: function() {
var that = this;
this.on("change:settingsJSON", function(model){
model.set({settings: JSON.stringify(model.get('settingsJSON'))});
});
this.on("change:settings", function(model){
model.set({settingsJSON: JSON.parse(model.get('settings'))});
});
}
#fencliff:
The change event is firing when I run this and works properly, I had it print the new settings string to the console.
Are you sure that they are called synchronously? I added console.log('changed') to the end of the .on(change) and put console.log('saved') directly after currentUser.save() and every time the console read:
saved
changed
For now I have just made it so that I stringily the JSON and save it to settings directly before I save and that works fine.
Backbone events are executed synchronously. That means that unless you (or some library) has overridden some part of the event handling, the change handlers will have processed as soon as you execute the next line of code.
In you code example there is another problem. When you call
user.get('settingsJSON').apps = appsEnabled;
The change event will not fire, because the value of settingsJSON has not been changed, merely the contents of the object were modified. The model.attributes.settingsJSON is still the same object as before.
The events are fired only when you call set on the property, and the new value is a different object. For example:
user.set('settingsJSON', _.extend({}, user.get('settingsJSON'), {apps:appsEnabled});
Another problem, it would seem, is that your event handlers, if triggered, would cause the change event being fired twice for the property which was first set:
this.on("change:settingsJSON", function(model){
//-> changes settings, and set triggers change
model.set({settings: JSON.stringify(model.get('settingsJSON'))});
});
this.on("change:settings", function(model){
//-> changes settingsJSON, and set triggers change
model.set({settingsJSON: JSON.parse(model.get('settings'))});
});
To solve that issue, call set with {silent:true} or modify the model.attributes hash directly.
Edited with corrections by #muistooshort.
Edited again with further corrections

Files input change event fires only once

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){
}
});
});

Categories