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/
Related
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.
I'm currently attempting to test some code that uses drag-and-drop. I found some other questions that were kinda related to this, but they were way too specific to help me, or not related enough.
This being a test, I'm struggling on trying to automatically execute code inside a .on('drop',function(e){....} event. The main issue is not that I can't run the code inside, but it's that I can't transfer the dataTransfer property, and I can't seem to fake it because it's read-only. Is there anyway to fake the dataTransfer property or otherwise get around it?
I came up with this JSFiddle that serves as a template of what I'm trying to do: https://jsfiddle.net/gnq50hsp/53/
Essentially if you are able to explain to me (if this is at all possible) how I can possibly fake the dataTransfer property, I should be all set.
Side notes:
I'm totally open to other ways of somehow getting inside that code, like for example, maybe its possible to trigger the event and pass in a fake event object with a fake dataTransfer object.
To see the drag-drop behavior, change the JavaScript load type from no-wrap head to on-Load, then you should see what I'm trying to simulate.
Important to note that I cannot modify any of the code inside the event handlers, only inside the outside function
Using Karma/Jasmine so use of those tools are also possible like spies
Also, I'm using Chrome.
Thanks in advance, and let me know for any questions/clarifications!
You should be able to override pretty much everything you want using Object.defineProperty. Depending on what you want to test it can be very simple or very complex. Faking the dataTransfer can be a bit tricky, since there's a lot of restrictions and behaviors linked to it, but if you simply want to test the drop function, it's fairly easy.
Here's a way, this should give you some ideas as to how to fake some events and data:
//Event stuff
var target = $('#target');
var test = $('#test');
test.on('dragstart', function(e) {
e.originalEvent.dataTransfer.setData("text/plain", "test");
});
target.on('dragover', function(e) {
//e.dataTransfer.setData('test');
e.preventDefault();
e.stopPropagation();
});
target.on('dragenter', function(e) {
e.preventDefault();
e.stopPropagation();
});
//What I want to simulate:
target.on('drop', function(e) {
console.log(e)
//Issue is that I can't properly override the dataTransfer property, since its read-only
document.getElementById('dataTransferDisplay').innerHTML = e.originalEvent.dataTransfer.getData("text");
});
function simulateDrop() {
// You'll need the original event
var fakeOriginalEvent = new DragEvent('drop');
// Using defineProperty you can override dataTransfer property.
// The original property works with a getter and a setter,
// so assigning it won't work. You need Object.defineProperty.
Object.defineProperty(fakeOriginalEvent.constructor.prototype, 'dataTransfer', {
value: {}
});
// Once dataTransfer is overridden, you can define getData.
fakeOriginalEvent.dataTransfer.getData = function() {
return 'test'
};
// TO have the same behavior, you need a jquery Event with an original event
var fakeJqueryEvent = $.Event('drop', {
originalEvent: fakeOriginalEvent
});
target.trigger(fakeJqueryEvent)
}
https://jsfiddle.net/0tbp4wmk/1/
As per jsfiddel link you want to achieve drag and drop feature. jQuery Draggable UI already provides this feature why you can not use that?
For create custom event on your way you have to follow two alternative ways
$('your selector').on( "myCustomEvent", {
foo: "bar"
}, function( event, arg1, arg2 ) {
console.log( event.data.foo ); // "bar"
console.log( arg1 ); // "bim"
console.log( arg2 ); // "baz"
});
$( document ).trigger( "myCustomEvent", [ "bim", "baz" ] );
On above example
In the world of custom events, there are two important jQuery methods: .on() and .trigger(). In the Events chapter, we saw how to use these methods for working with user events; for this chapter, it's important to remember two things:
.on() method takes an event type and an event handling function as arguments. Optionally, it can also receive event-related data as its second argument, pushing the event handling function to the third argument. Any data that is passed will be available to the event handling function in the data property of the event object. The event handling function always receives the event object as its first argument.
.trigger() method takes an event type as its argument. Optionally, it can also take an array of values. These values will be passed to the event handling function as arguments after the event object.
Here is an example of the usage of .on() and .trigger() that uses custom data in both cases:
OR
jQuery.event.special.multiclick = {
delegateType: "click",
bindType: "click",
handle: function( event ) {
var handleObj = event.handleObj;
var targetData = jQuery.data( event.target );
var ret = null;
// If a multiple of the click count, run the handler
targetData.clicks = ( targetData.clicks || 0 ) + 1;
if ( targetData.clicks % event.data.clicks === 0 ) {
event.type = handleObj.origType;
ret = handleObj.handler.apply( this, arguments );
event.type = handleObj.type;
return ret;
}
}
};
// Sample usage
$( "p" ).on( "multiclick", {
clicks: 3
}, function( event ) {
alert( "clicked 3 times" );
});
On above example
This multiclick special event maps itself into a standard click event, but uses a handle hook so that it can monitor the event and only deliver it when the user clicks on the element a multiple of the number of times specified during event binding.
The hook stores the current click count in the data object, so multiclick handlers on different elements don't interfere with each other. It changes the event type to the original multiclick type before calling the handler and restores it to the mapped "click" type before returning:
This works:
var picdrag = document.getElementById('picdrag');
picdrag.addEventListener('drop', picSelect, false);
function picSelect(e) {
var pics = e.dataTransfer.files;
}
This doesn't:
$('#picdrag').on('drop', function(e) {picSelect(e);});
function picSelect(e) {
var pics = e.dataTransfer.files;
}
because it reports an error 'e.dataTransfer is undefined'. I hate it when I don't know why something works or doesn't work. This is specific to the drop event, like jQuery handles it differently.
You can try:
var pics = e.originalEvent.dataTransfer.files;
From the docs:
Certain events may have properties specific to them. Those can be
accessed as properties of the event.originalEvent object. To make
special properties available in all event objects, they can be added
to the jQuery.event.props array. This is not recommended, since it
adds overhead to every event delivered by jQuery.
There is also an example that should be of interest:
// add the dataTransfer property for use with the native `drop` event
// to capture information about files dropped into the browser window
jQuery.event.props.push("dataTransfer");
$('#picdrag').on('drop', picSelect);
var picSelect = function(e) {
var pics = e.dataTransfer.files;
};
I'm taking a Javascript class and was wondering if there was a way to tell which button was selected when a function is called. I basically have a method like this:
function sendRequest()
{
var url = "http://classwebsite/bookmarks.php";
url += "?userid=crystal";
var transmission = document.getElementById("transmission").value;
url += "&response=" + transmission;
var callback = {success:handleResponse,
failure:handleFailure,
timeout:5000
};
var transaction = YAHOO.util.Connect.asyncRequest("GET", url, callback, null);
}
This method gets called when a button is pressed. It basically gets back the prof's response in the appropriate JSON, XML, etc format and displays it. I want to add an "add" feature to add new rows to the table. That's done by calling the same URL in the above method and just manually putting this in the address bar:
http://classwebsite/bookmarks.php?userid=crystal&action=add&name=yahoo&url=yahoo.com&desc=Yahoo+website
In this scenario, if I had another button called "Add" to add in fields from a form, would I call the same sendRequest() method, and modify the url accordingly? If so, how do I know which button was pressed if both the "List" button and "Add" button would be tied to the same event handler.
Or is it a better design to have another method, that handles addRequest() and just adds fields from the form? Thanks.
If you did use the Yahoo utils like they are supposed to be used (i.e. via YAHOO.util.Event.addListener()), then your button is referenced by this.
See Automatic Scope Correction in the YUI docs.
In addition, please encode URL parameters correctly before you use them.
var transmission = document.getElementById("transmission").value,
url = "http://classwebsite/bookmarks.php"
+ "?userid=crystal"
+ "&response=" + encodeURIComponent(transmission); // <- !!
You should be able to modify the parameters of your javascript function to see the sender.
There is also a hidden array called arguments, that will let you look at what parameters are available to a function, in case you are ever curious.
function sendRequest(sender, args) {
//sender is your clicked button
var url = "http://classwebsite/bookmarks.php";
url += "?userid=crystal";
var transmission = document.getElementById("transmission").value;
url += "&response=" + transmission;
var callback = {success:handleResponse,
failure:handleFailure,
timeout:5000
};
var transaction = YAHOO.util.Connect.asyncRequest("GET", url, callback, null);
}
If a button is pressed then an event should be passed to the function. The event object contains the target of the click among other things.
function sendRequest(e) {
var target;
if(!e) {
e = window.event;
}
// the button clicked can now be accessed as
// we use the ternary because IE uses a different property
target = e.target ? e.target : e.srcElement;
}
Youll note a couple if statements in there... this is because IE differs a bit form the standard. However, i see youre using a Yahoo lib for some of your js. I imagine if you are to use the facilities of this library to bind your events it would also normalize the event object passed in to your callbacks so you wouldnt have to manually create xbrowser accommodations.
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){
}
});
});