I am writing tests for an AngularJS directive which fires events of a <textarea> when certain keys are pressed. It all works fine per my manual testing. I want to be good and have a full unit-test suite too, but I have run into a problem I can't solve on my own:
I want to send a specific keyCode in my triggerHandler() call in my test, but I can't find a way to specify the key that actually works. I am aware of a lot of questions and answers on the topic of building and sending events with specific data, but none of them work on my setup:
My setup
Karma test runner
PhantomJS browser running the tests (but also tried Firefox and Chrome without success)
I'm not using jQuery and I'm hoping there is a regular JS solution. There must be!
Test code
var event = document.createEvent("Events");
event.initEvent("keydown", true, true);
event.keyCode = 40; // in debugging the test in Firefox, the event object can be seen to have no "keyCode" property even after this step
textarea.triggerHandler(event); // my keydown handler does not fire
The strange thing is, I can type the first 3 lines into the console in Chrome and see that the event is being created with the keyCode property set to 40.
So it seems like it should work.
Also, when I call the last line like this textarea.triggerHandler("keydown"); it works and the event handler is triggered. However, there is no keyCode to work with, so it is pointless.
I suspect it may be something to do with the nature of the test running against a DOM that is different to a regular page running in the browser. But I can't figure it out!
I've used the following solution to test it and having it working in Chrome, FF, PhantomJS and IE9+ based on this SO answer.
It doesn't work in Safari - tried millions of other solution without any success...
function jsKeydown(code){
var oEvent = document.createEvent('KeyboardEvent');
// Chromium Hack: filter this otherwise Safari will complain
if( navigator.userAgent.toLowerCase().indexOf('chrome') > -1 ){
Object.defineProperty(oEvent, 'keyCode', {
get : function() {
return this.keyCodeVal;
}
});
Object.defineProperty(oEvent, 'which', {
get : function() {
return this.keyCodeVal;
}
});
}
if (oEvent.initKeyboardEvent) {
oEvent.initKeyboardEvent("keydown", true, true, document.defaultView, false, false, false, false, code, code);
} else {
oEvent.initKeyEvent("keydown", true, true, document.defaultView, false, false, false, false, code, 0);
}
oEvent.keyCodeVal = code;
if (oEvent.keyCode !== code) {
console.log("keyCode mismatch " + oEvent.keyCode + "(" + oEvent.which + ") -> "+ code);
}
document.getElementById("idToUseHere").dispatchEvent(oEvent);
}
// press DEL key
jsKeydown(46);
Hope it helps
Update
Today I've found and tested this solution which is offers a much wider coverage of browsers (enabling the legacy support):
https://gist.github.com/termi/4654819
All the credit goes to the author of this GIST.
The code does support Safari, PhantomJS and IE9 - tested for the first 2.
Adding to #MarcoL answer, I'd like to point out for future readers who might stumble on this question, that the methods initKeyboardEvent and initKeyEvent are deprecated methods, and should no longer be used. See here and here.
Instead as the MDN docs suggested, events should be created via their respective constructor.
Related
I am using a plugin that allows me to display an ad in video-js.
https://github.com/dirkjanm/videojs-preroll/blob/master/lib/videojs.ads.js
This worked perfect until version 5 but now that I have wanted to migrate to version 6, this plugin no longer works, the log throws me the following error:
TypeError: videojs.getComponent(...) is undefined videojs.ads.js:386
It seems that everything lies in this section of the plugin (line 386):
(function() {
var
videoEvents = videojs.getComponent('Html5').Events,
i,
returnTrue = function() { return true; },
triggerEvent = function(type, event) {
// pretend we called stopImmediatePropagation because we want the native
// element events to continue propagating
event.isImmediatePropagationStopped = returnTrue;
event.cancelBubble = true;
event.isPropagationStopped = returnTrue;
player.trigger({
type: type + event.type,
state: player.ads.state,
originalEvent: event
});
},
The plugin has not been updated since a while ago so I also gave some alarms on how to register the plugin but that yes I could solve it, I'm not very understanding of javascript so I do not know how I could solve that.
Ok comrades, I have been reading the manual of videojs 6 and apparently to solve that problem only had to change a line:
videojs.getComponent -to- videojs.getTech
I leave it here in case someone has the same problem.
Of course, I am not a Javascript specialist so I honestly would not know how to give an explanation about this.
I have a form. It contains some hyperlinks. I can click on them and get another page in browsers like Google Chrome and Mozila Firefox but instead, I am unable to open them in IE9, IE10,..
What might be the problem? and whats the solution? help me.
In dev-tool(console), I am getting
un-terminated string constant error
.
Apart from any coding this could be the reason:
A previously installed browser or add-in might be interfering with ie browser on your computer.
or it might be a result of bad DCOM.
If bad DCOM is the problem you can get solution here:- http://www.techsupportall.com/links-are-not-working/
Jay,
This might happen due to incorrect concatenations or omission of the semicolon(;) at the end of statement.
Or may be any server variable that is not being populated. Like:
var var1 ='<% = someServerVariable %>'
someCode
It is possible that some someServerVariable is not being populated and the browser compiler would read the code as
var1 = someCode
Please look into your code and find out if there is any similar issue with your code. Or the best is to share your code snippet to point out the exact issue.
I ran into this same exact problem. For me, whenever I was embedding links in an ul / li list format, it just wouldn't let me click the first link (happens a lot navigation menus).It would only let me click the embedded ones. My work around for this was using java-script to create a force the click and pass.
Put this script in your head
<script>
function fakeClick(event, anchorObj) {
if (anchorObj.click) {
anchorObj.click()
} else if(document.createEvent) {
if(event.target !== anchorObj) {
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
var allowDefault = anchorObj.dispatchEvent(evt);
// you can check allowDefault for false to see if
// any handler called evt.preventDefault().
// Firefox will *not* redirect to anchorObj.href
// for you. However every other browser will.
}
}
}
</script>
And then in the body you can use this convention for any link you need to be forced clicked.
<a id="link" href="#YourDestinationLinkHere" onclick="fakeClick((event.target || event.srcElement).innerHTML)">Destination</a>
I would like to write some tests for some input filtering code in a text box. For most tests, I can just call setValue and trigger the change event, which is easy to do. However, in this case, because I want to test that the input gets filtered out (or not), I can't just setValue() directly.
I tried dispatching keydown, keyup, keypress, textinput events. I can see that the handlers for them are being called, but the text doesn't actually show in the text box Note that this only "works" in Firefox, I understand the code would look different for other browsers.
function dispatch(target, eventType, charCode) {
var evt = document.createEvent("KeyboardEvent");
evt.initKeyEvent(
eventType,
true,
true,
window,
false,
false,
false,
false,
charCode,
0
);
target.dispatchEvent(evt);
}
var id = document.getElementById('id');
id.onkeydown = id.onkeyup = id.onkeypress = function() {console.log(arguments)}
dispatch(id, 'keydown', 65);
dispatch(id, 'keyup', 65);
dispatch(id, 'keypress', 65);
dispatch(id, 'textinput', 65);
// I can see the handlers were called but it doesn't display in the text box
I understand this has restrictions because we don't want web apps to just pretend like they are acting for the user. However, this is for testing my own application and I could launch Firefox with a specific profile and install plugins, or even write my own if I know it will help.
What I am after is to avoid using Selenium, I want to keep Java out of my JS tests because not only is it slow, but I have to re-implement a lot of the DOM querying in Java.
After all this, the question is, does anybody know how to get that code to actually modify the input? Tweaking settings, installing plugins?
List of questions that don't answer my question
Simulating user input for TDD JavaScript
Definitive way to trigger keypress events with jQuery
How to send a key to an input text field using Javascript?
Is it possible to simulate key press events programmatically?
I just found out that the following code does work in Chrome at least. No go in firefox or IE http://jsfiddle.net/D2s5T/14/
function dispatch(target, eventType, char) {
var evt = document.createEvent("TextEvent");
evt.initTextEvent (eventType, true, true, window, char, 0, "en-US");
target.focus();
target.dispatchEvent(evt);
}
dispatch(el, "textInput", "a");
I'll start with the question. When a specific browser has a buggy implementation of a feature and your javascript needs to know whether the current browser has that buggy implementation or not so it can use an alternate strategy, how do you figure out if the implementation is buggy without doing browser type sniffing (which is generally considered bad)?
Here's the whole situation.
I was working on some code that wants to use the "input" event for getting notifications of user changes to an <input type="text"> field (works much more live than the "change" event), but when that event isn't supported, it uses a much more complicated scheme involving a bunch of other events.
Since the "input" event is only supported in some browsers, I went in search of a way to do feature detection for the event (rather than browser user agent sniffing) since feature detection is generally a more robust way of doing things. As such, I came across this great article for doing exactly that and this code seems to work:
var isEventSupported = (function(){
var TAGNAMES = {
'select':'input','change':'input',
'submit':'form','reset':'form',
'error':'img','load':'img','abort':'img'
}
function isEventSupported(eventName) {
var el = document.createElement(TAGNAMES[eventName] || 'div');
eventName = 'on' + eventName;
var isSupported = (eventName in el);
if (!isSupported) {
el.setAttribute(eventName, 'return;');
isSupported = typeof el[eventName] == 'function';
}
el = null;
return isSupported;
}
return isEventSupported;
})();
Then, I ran into problems with IE (surprise, surprise). While IE purports to support the "input" event and it passes the feature test above and it works most of the time, IE's support is buggy as hell. It doesn't even trigger the event when the user hits the backspace key (among other missing behaviors). As such, I can't rely on it in IE. So, I had built this nice clean code that did a feature test for the "input" event and uses it's very clean implementation when present and when not present used this much uglier work-around involving monitoring eight other events. Now, it's busted in IE because the feature test for the "input" event passes so the code attempts to use it, but it's buggy as hell so it doesn't work.
Since these IE bugs show up on user actions, I can't think of any way to devise a javascript feature test to identify the buggy behavior. As such, my only current path is to resort to browser sniffing and refuse to rely on the "input" tag if the browser is IE.
Are there any options here for identifying the buggy behavior in the "input" event besides browser sniffing? If one had to do browser sniffing, is there a way to identify IE by behavior rather than a user agent string that can be freely spoofed and isn't guaranteed to be accurate?
jamie-pate suggest something like this:
var broken = false,
ta = angular.element('<textarea>').on('input', function(evt) {
broken = true;
});
ta.attr('placeholder', 'IESUCKS');
So you can check for "supports input event and is not 'broken'" in your code.
See https://github.com/angular/angular.js/issues/2614?source=c
If you are interested in a cross-browser "input change" event, here is my implementation:
function onInputChange(domInput, callback) {
if (domInput.addEventListener) {
domInput.addEventListener('input', callback, false); // Firefox, etc.
} else if (domInput.attachEvent) {
domInput.attachEvent('onpropertychange', callback); // IE
}
}
Usage example:
var leInput = document.getElementById('myInput');
var leCallback = function () {
// awesome stuff here
};
onInputChange(leInput, leCallback);
Works in all browsers, supports keyboard input and copy/paste.
However there is the exception of that cursed IE9, which didn't exist at the time I wrote the above code. Would Microsoft ever consider fixing their bug? :\
Consider this JSFiddle. It works fine in Firefox (14.0.1), but fails in Chrome (21.0.1180.75), Safari (?) and Opera(12.01?) on both Windows (7) and OS X (10.8). As far as I can tell the issue is with either the setData() or getData() methods on the dataTransfer object. Here's the relevant code from the JSFiddle.
var dragStartHandler = function (e) {
e.originalEvent.dataTransfer.effectAllowed = "move";
e.originalEvent.dataTransfer.setData("text/plain", this.id);
};
var dragEnterHandler = function (e) {
// dataTransferValue is a global variable declared higher up.
// No, I don't want to hear about why global variables are evil,
// that's not my issue.
dataTransferValue = e.originalEvent.dataTransfer.getData("text/plain");
console.log(dataTransferValue);
};
As far as I can tell this should work perfectly fine and if you look at the console while dragging an item you will see the id written out, which means that it's finding the element just fine and grabbing it's id attribute. The question is, is it just not setting the data or not getting the data?
I'd appreciate suggestions because after a week of working on this with three attempts and some 200+ versions, I'm starting to loose my mind. All I know is it used to work back in version 60 or so and that specific code hasn't changed at all...
Actually, one of the major differences between 6X and 124 is that I changed the event binding from live() to on(). I don't think that's the issue, but I've come to see a couple failures from Chrome when it comes to DnD while working on this. This has been debunked. The event binding method has no effect on the issue.
UPDATE
I've created a new JSFiddle that strips out absolutely everything and just leaves the event binding and handlers. I tested it with jQuery 1.7.2 and 1.8 with both on() and live(). The issue persisted so I dropped down a level and removed all frameworks and used pure JavaScript. The issue still persisted, so based on my testing it's not my code that's failing. Instead it appears that Chrome, Safari and Opera are all implementing either setData() or getData() off spec or just failing for some reason or another. Please correct me if I'm wrong.
Anyway, if you take a look at the new JSFiddle you should be able to replicate the issue, just look at the console when you're dragging over an element designated to accept a drop. I've gone ahead and opened a ticket with Chromium. In the end I may still be doing something wrong, but I simply don't know how else to do DnD at this point. The new JSFiddle is as stripped down as it can get...
Ok, so after a bit more digging around, I found that the problem actually isn't with Chrome, Safari, and Opera. What gave it away was that Firefox was supporting it and I just couldn't say the other browsers are failing, since that's something I'd normally accept for IE.
The real cause of the issue is the DnD specification itself. According to the spec for the drag, dragenter, dragleave, dragover and dragend events the drag data store mode is protected mode. What is protected mode you ask? It is:
For all other events. The formats and kinds in the drag data store
list of items representing dragged data can be enumerated, but the
data itself is unavailable and no new data can be added.
That translates to, "you have no access to the data that you set, not even in read only mode! Go f#&# yourself.". Really? Who'se the genius that came up with this?
Now, to get around that limitation you have few choices, and I'm only going to outline two that I've come up with. Your first one is to use an evil global variable and pollute the global namespace. Your second choice is to use the HTML5 localStorage API to perform the EXACT same functionality that the DnD API should have provided to begin with!
If you go down this route, which I have, you're now implementing two HTML5 APIs not because you want to, but because you have to. Now I'm starting to appreciate PPK's rant about the disaster that the HTML5 DnD API is.
The bottom line is this, the spec needs to be changed to allow for access to the stored data even if it's only in read only mode. In my case, with this JSFiddle, I'm actually using the dragenter as a way to look ahead at the drop zone and verify that I should allow a drop to occur or not.
In this case Mozilla apparently opted out of full compliance with the spec which is why my JSFiddle was working just fine in it. It just so happens that this is the one time I fully support not supporting the full specification.
There is a reason for the "protected" bit....drag/drop can span completely different windows, and they didn't want somebody to be able to implement a "listener" DIV that would eavesdrop on the content of everything that was dragged over it (and maybe send those contents by AJAX to some spy server in Elbonia). Only the DROP area (which is more clearly under the user's control) gets the full scoop.
Annoying, but I can see why it might be considered necessary.
var dragStartHandler = function (e) {
e.originalEvent.dataTransfer.effectAllowed = "move";
e.originalEvent.dataTransfer.setData("text/plain", this.id);
};
The problem is with the "text/plain". The standard specification in MSDN documentation for setData is just "text" (without the /plain). Chrome accepts the /plain, but IE does not, in any version I tried.
I struggled with the same problem for several weeks, trying to figure out why my "drop" events weren't firing properly in IE while they did in CHrome. It was because the dataTransfer data hadn't been properly loaded.
I know you already answered this, but this is a useful thread -- I just wanted to add an addendum here -- if you're setting the data yourself, you can always add the data into the field itself (ugly I know), but it prevents having to re-create functionality:
For instance, if setting your own custom data:
dataTransfer.setData('mycustom/whatever', 'data');
append the data as a new data entry, and iterate:
dataTransfer.setData('mycustom/whatever/data/{a json encoded string even}');
querying:
// naive webkit only look at the datatransfer.types
if (dataTransfer.types.indexOf('mycustom/whatever') >= 0) {
var dataTest = 'mycustom/whatever/data/';
// loop through types and create a map
for (var i in types) {
if (types[i].substr(0, dataTest.length) == dataTest) {
// shows:
// {a json encoded string even}
console.log('data:', types[i].substr(dataTest.length));
return; // your custom handler
}
}
}
tested in chrome only
Something also worth noting is that if you leave the execution chain using a timeout, the dataTransfer object won't have your data anymore.
e.g.
function dropEventHandler(event){
var dt = event.dataTransfer.getData("text/plain"); // works
var myEvent = event;
setTimeout(function(){
var dt = myEvent.dataTranfer.getData("text/plain"); // null
}, 1);
}
I was getting same error for below code:
event.originalEvent.dataTransfer.setData("text/plain",
event.target.getAttribute('id'));
I Changed code to:
event.originalEvent.dataTransfer.effectAllowed = "move";
event.originalEvent.dataTransfer.setData("text", event.target.getAttribute('id'));
And it worked for me.
I came across this post because I was having a similar experience with Chrome's dataTransfer.setData() and dataTransfer.getData() functions.
I had code that looked something like this:
HTML:
<div ondragstart="drag(event)" ondrop="newDrop(event)"></div>
JAVASCRIPT:
function drag(ev) {
ev.dataTransfer.setData("text", ev.target.id);
}
function newDrop(ev){
var itemDragged = ev.dataTransfer.getData("text");
var toDropTo = ev.target.id;
}
The code worked perfectly fine in Internet Explorer but when it was tested in Chrome, I was unable to get values set in my dataTransfer object (set in drag function) using the dataTransfer.getData() function in the newDrop function. I was also unable to get the id value from the statement ev.target.id.
After some digging around on the web, I discovered that I was suppose to use the event parameters currentTarget property rather than the events target property. Updated code looked something like this:
JAVASCRIPT:
function drag(ev) {
ev.dataTransfer.setData("text", ev.currentTarget.id);
}
function newDrop(ev){
var itemDragged = ev.dataTransfer.getData("text");
var toDropTo = ev.currentTarget.id;
}
With this change I was able to use the dataTransfer.setData() and dataTransfer.getData() functions in chrome as well as internet explorer. I have not tested anywhere else and I am not sure why this worked. Hope this helps and hopefully someone can give an explanation.
I was working on a website testing with Firefox.
In WAMP on my laptop, code like the OP's worked. However, when I moved the website to HOSTMONSTER, it didn't work there.
I followed Joshua Espana's answer, and it resolved my problem.
failed:
ev.dataTransfer.setData("text/plain", ev.target.id);
worked:
ev.dataTransfer.setData("text", ev.currentTarget.id);
Thank, Joshua!
Anything pased to the dataTransfer only becomes available on ondrop events but ONLY on ondrop events (I believe this is a security consideration to prevent data being exposed to nefarious elements during a drag).
If you try adding an ondrop handler you should see the data exposed. Well at least you would if there weren't for one final trick...
To get the drop event to fire you need call .preventDefault on the dragover event or it prevents the drop event from firing
HTML (Angular)
<div (dragstart)="handleDragStart($event)"
(dragover)="handleDragover($event)"
(dragend)="handleDragEnd($event)"
(drop)="handleDrop($event)">
<div class="sortItem">item 1</div>
<div class="sortItem">item 2</div>
<div class="sortItem" draggable="true">Draggable</div>
<div class="sortItem">item 4</div>
</div>
Handlers (Typescript)
handleDragStart(event: DragEvent){
event.dataTransfer?.setData("text", '{"some": "data"}')
console.log('dragstart data:', event.dataTransfer?.getData("text"))
}
handleDragover(event: DragEvent) {
console.log('dragover data:', event.dataTransfer?.getData("text") || 'none')
event.preventDefault()
}
handleDragEnd(event: DragEvent) {
console.log('drag end data:', event.dataTransfer?.getData("text") || 'none')
}
handleDrop(event: DragEvent) {
console.log('drag drop data:', event.dataTransfer?.getData("text") || 'none')
}
Output from the above
If you drop the item INSIDE the container with the ondrop handler
If you don't cancel the dragover event...
If you drop the item OUTSIDE container with the ondrop handler
If you don't cancel the dragover event...
Other relevant SO questions
SO: Data only available on drop
SO: Drop event not firing
I also faced with this problem but no way worked for me when I set the data in ondrag function and get it back it gives the data but when try in other events like dragover and drop it dose not work.
I went this way maybe its not a proper answer but may help some one
let selectedId = 0;
function onDrag(params) {
// event.dataTransfer.setData("text",params);
selectedId = params
}
function onDragOver(params) {
// event.preventDefault()
// const data = event.dataTransfer.getData("text");
let id = selectedId
}