How to trigger keydown on an element in jasmine unit test - javascript

I am writing a jasmine unit test for a function that receives 'keydown' event. It's just a plain javascript(not using any framework)
var event = document.createEvent('Event');
event.keyCode = 84;
event.initEvent('keydown');
event.target = $('#selector');
myFunction(event);
When I console log the event in myFunction
This is what I get:
{"returnValue":true,"timeStamp":1607595872162,"eventPhase":0,"target":null,"defaultPrevented":false,"srcElement":null,"type":"keydown","cancelable":false,"currentTarget":null,"bubbles":false,"cancelBubble":false,"keyCode":84}
The 'target' property is null
How do I set the target value?
Please help

It looks like you're looking to dispatch the event you created. The MDN article I linked indicates the target parameter is used to initialize target in the event. In your case, I would do something like:
var event = document.createEvent('Event');
event.keyCode = 84;
event.initEvent('keydown');
$('#selector')[0].dispatchEvent(event);
This makes the assumption that you've bound myFunction to #selector's keydown event via:
document.getElementById("selector").addEventListener('keydown', myFunction);
or
$('#selector').on('keydown', myFunction);
If you're not binding the function with an event listener, you could leverage duck typing by building a fake event object and passing that fake event to myFunction:
myFunction({
type: 'keydown',
keyCode: 84,
target: $('#selector')[0]
});

Related

Call change event of an input programatically [duplicate]

How do I programmatically force an onchange event on an input?
I've tried something like this:
var code = ele.getAttribute('onchange');
eval(code);
But my end goal is to fire any listener functions, and that doesn't seem to work. Neither does just updating the 'value' attribute.
Create an Event object and pass it to the dispatchEvent method of the element:
var element = document.getElementById('just_an_example');
var event = new Event('change');
element.dispatchEvent(event);
This will trigger event listeners regardless of whether they were registered by calling the addEventListener method or by setting the onchange property of the element.
By default, events created and dispatched like this don't propagate (bubble) up the DOM tree like events normally do.
If you want the event to bubble, you need to pass a second argument to the Event constructor:
var event = new Event('change', { bubbles: true });
Information about browser compability:
dispatchEvent()
Event()
In jQuery I mostly use:
$("#element").trigger("change");
ugh don't use eval for anything. Well, there are certain things, but they're extremely rare.
Rather, you would do this:
document.getElementById("test").onchange()
Look here for more options:
http://jehiah.cz/archive/firing-javascript-events-properly
For some reason ele.onchange() is throwing a "method not found" expception for me in IE on my page, so I ended up using this function from the link Kolten provided and calling fireEvent(ele, 'change'), which worked:
function fireEvent(element,event){
if (document.createEventObject){
// dispatch for IE
var evt = document.createEventObject();
return element.fireEvent('on'+event,evt)
}
else{
// dispatch for firefox + others
var evt = document.createEvent("HTMLEvents");
evt.initEvent(event, true, true ); // event type,bubbling,cancelable
return !element.dispatchEvent(evt);
}
}
I did however, create a test page that confirmed calling should onchange() work:
<input id="test1" name="test1" value="Hello" onchange="alert(this.value);"/>
<input type="button" onclick="document.getElementById('test1').onchange();" value="Say Hello"/>
Edit: The reason ele.onchange() didn't work was because I hadn't actually declared anything for the onchange event. But the fireEvent still works.
Taken from the bottom of QUnit
function triggerEvent( elem, type, event ) {
if ( $.browser.mozilla || $.browser.opera ) {
event = document.createEvent("MouseEvents");
event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
elem.dispatchEvent( event );
} else if ( $.browser.msie ) {
elem.fireEvent("on"+type);
}
}
You can, of course, replace the $.browser stuff to your own browser detection methods to make it jQuery independent.
To use this function:
var event;
triggerEvent(ele, "change", event);
This will basically fire the real DOM event as if something had actually changed.
This is the most correct answer for IE and Chrome::
var element = document.getElementById('xxxx');
var evt = document.createEvent('HTMLEvents');
evt.initEvent('change', false, true);
element.dispatchEvent(evt);
If you add all your events with this snippet of code:
//put this somewhere in your JavaScript:
HTMLElement.prototype.addEvent = function(event, callback){
if(!this.events)this.events = {};
if(!this.events[event]){
this.events[event] = [];
var element = this;
this['on'+event] = function(e){
var events = element.events[event];
for(var i=0;i<events.length;i++){
events[i](e||event);
}
}
}
this.events[event].push(callback);
}
//use like this:
element.addEvent('change', function(e){...});
then you can just use element.on<EVENTNAME>() where <EVENTNAME> is the name of your event, and that will call all events with <EVENTNAME>
The change event in an input element is triggered directly only by the user. To trigger the change event programmatically we need to dispatch the change event.
The question is Where and How?
"Where" we want the change event to be triggered exactly at the moment after a bunch of codes is executed, and "How" is in the form of the following syntax:
const myInput = document.getElementById("myInputId");
function myFunc() {
//some codes
myInput.dispatchEvent(new Event("change"));
}
In this way, we created the change event programmatically by using the Event constructor and dispatched it by the dispatchEvent() method. So whenever myFunc() method is invoked, after the //some codes are executed, our synthetic change event is immediately triggered on the desired input element.‍
Important result: Here, the change event is triggered by executing the //some codes in myFunc() instead of changing the input value by the user (default mode).
if you're using jQuery you would have:
$('#elementId').change(function() { alert('Do Stuff'); });
or MS AJAX:
$addHandler($get('elementId'), 'change', function(){ alert('Do Stuff'); });
Or in the raw HTML of the element:
<input type="text" onchange="alert('Do Stuff');" id="myElement" />
After re-reading the question I think I miss-read what was to be done. I've never found a way to update a DOM element in a manner which will force a change event, what you're best doing is having a separate event handler method, like this:
$addHandler($get('elementId'), 'change', elementChanged);
function elementChanged(){
alert('Do Stuff!');
}
function editElement(){
var el = $get('elementId');
el.value = 'something new';
elementChanged();
}
Since you're already writing a JavaScript method which will do the changing it's only 1 additional line to call.
Or, if you are using the Microsoft AJAX framework you can access all the event handlers via:
$get('elementId')._events
It'd allow you to do some reflection-style workings to find the right event handler(s) to fire.
Using JQuery you can do the following:
// for the element which uses ID
$("#id").trigger("change");
// for the element which uses class name
$(".class_name").trigger("change");
For triggering any event in Javascript.
document.getElementById("yourid").addEventListener("change", function({
//your code here
})

What is initEvents() and should I use it in 2018?

I'm trying to understand the code of another programmer and there is a initEvents () method. Some articles tells that this is an outdated method, but I'm not sure.
Could you help me? What it is? And is it really outdated?
This is indeed a deprecated method which will init the event before dispatching, you should instead use the Event constructor which have a second argument for event initialisation and then dispatch the event as you like.
Something like this:
// create a look event that bubbles up and cannot be canceled
var evt = new Event("look", {"bubbles":true, "cancelable":false});
document.dispatchEvent(evt);
// event can be dispatched from any element, not only the document
myDiv.dispatchEvent(evt);
initEvents has been deprecated.
Instead use this to Create Events :
const customEvent = new Event("CustomEventEmmiter", {"bubbles":true, "cancelable":false});
document.dispatchEvent(customEvent);
And listen to it like this :
window.addEventListener("CustomEventEmmiter", function(){
alert('CustomEventEmmiter TRIGGERED');
});
Check out more about Events here

Handle React event delegation with stopPropagation()

I have a project in React which should be able to be placed on any website. The idea is that I host a javascript file, people place a div with a specific ID, and React renders in that div.
So far this works, except click-events. These evens are handled at the top level. This is all good, but one of the sites where the app should be placed, has stopPropagation() implemented for a off-canvas menu. Because of this the events aren't working properly.
I tried catching all events at the root-element, and dispatching them manually:
this.refs.wrapper.addEventListener('click', (event) => {
console.log(event);
console.log(event.type);
const evt = event || window.event;
evt.preventDefault();
evt.stopImmediatePropagation();
if (evt.stopPropagation) evt.stopPropagation();
if (evt.cancelBubble !== null) evt.cancelBubble = true;
document.dispatchEvent(event);
});
This doesn't work, because the event is already being dispatched:
Uncaught InvalidStateError: Failed to execute 'dispatchEvent' on 'EventTarget': The event is already being dispatched.
What would be the right way to fix this problem? Not using the synthetic events from React doesn't seem the right way to go for me..
Argument 'event h'as already been dispatched.
You should clone a new eventobject with old event.
var newevent = new event.constructor(event.type, event)
Ther is no solution yet. React, as you say, listen events on the root of DOM, and filter events if their event.target not inside react's mounted node.
You can try:
1. Redispatch new event in the Reract component, but it will be stopped at outside handler too.
2. Dispatch new event outside Reract component, higher (closest to BODY) then node with stopPropagation callback. But event.target will point to node, which not inside React's component and you can not change it, beacause it is readonly.
Maybe in next versions they will fix it.
But you can listen for events in the document, no?
Let say your root component for the whole app is named app. Then, inside it's componentDidMount you can have:
// when the main App component mounts - we'll add the event handlers ..
componentDidMount() {
var appComponent = this;
document.addEventListener('click', function(e) {
var clickedElement = e.target;
// Do something with the clickedElement - by ID or class ..
// You'll have reference to the top level component in `appComponent` ..
});
};
As you said - React handles all events at the top-level node (document), and to determine which react component relates to some event react uses event.target property. So to make everything work you should manually dispatch stopped event on document node and set proper "target" property to this event.
There is 2 problems to solve:
You can't trigger event that is already dispatched. To solve this you have to create a fresh copy of this event.
After you do dispatchEvent() on some node browser automatically set "target" property of this event to be the node on which event is fired. To solve this you should set proper target before dispatchEvent(), and make this property read-only using property descriptors.
General solution:
Solution tested in all modern browsers and IE9+
Here is a source code of solution:
https://jsbin.com/mezosac/1/edit?html,css,js,output . (Sometimes it hangs, so if you don't see UI elements in preview area - click on "run win js" button on top right corner)
It is well commented, so I will not describe here all of that stuff, but I will quickly explain main points:
Event should be redispatched immediately after it was stopped, to achieve this I extended native stopPropagation and stopImmediatePropagation methods of event to call my redispatchEventForReact function before stopping propagation:
if (event.stopPropagation) {
const nativeStopPropagation = event.stopPropagation;
event.stopPropagation = function fakeStopPropagation() {
redispatchEventForReact();
nativeStopPropagation.call(this);
};
}
if (event.stopImmediatePropagation) {
const nativeStopImmediatePropagation = event.stopImmediatePropagation;
event.stopImmediatePropagation = function fakeStopImmediatePropagation() {
redispatchEventForReact();
nativeStopImmediatePropagation.call(this);
};
}
And there is another one possibility to stop event - setting "cancelBubble" property to "true". If you take a look at cancalBubble property descriptor - you will see that this property indeed is a pair of getters/setters, so it's easy to inject "redispatchEventForReact" call inside setter using Object.defineProperty:
if ('cancelBubble' in event) {
const initialCancelBubbleDescriptor = getPropertyDescriptor(event, 'cancelBubble');
Object.defineProperty(event, 'cancelBubble', {
...initialCancelBubbleDescriptor,
set(value) {
redispatchEventForReact();
initialCancelBubbleDescriptor.set.call(this, value);
}
});
}
redispatchEventForReact function:
2.1 Before we dispatch event for react we should remove our customized stopPropagation and stopImmediatePropagation methods (because in react code some component in theory can invoke e.stopPropagation, which will trigger redispatchEventForReact again, and this will lead to infinite loop):
delete event.stopPropagation;
delete event.stopImmediatePropagation;
delete event.cancelBubble;
2.2 Then we should make a copy of this event. It's easy to do in modern browsers, but take a looot of code for IE11-, so I moved this logic in separate function (see attached source code on jsbin for details):
const newEvent = cloneDOMEvent(event);
2.3 Because browser set "target" property of event automatically when event is dispatched we should make it read-only. Important bit here - setting value and writeable=false will not work in IE11-, so we have to use getter and empty setter:
Object.defineProperty(newEvent, 'target', {
enumerable: true,
configurable: false,
get() { return event.target; },
set(val) {}
});
2.4 And finally we can dispatch event for react:
document.dispatchEvent(newEvent);
To guarantee that hacks for react will be injected in event before something stopped this event we should listen to this event on root node in capturing phase and do injections:
const EVENTS_TO_REDISPATCH = ['click'];
EVENTS_TO_REDISPATCH.forEach(eventToRedispatch => {
document.addEventListener(eventToRedispatch, prepareEventToBeRedispatched, true);
});

Get onChange event type

I have registered an onChange event to a comboBox in my form. I am trying to get the event type for example(item selected or key pressed) based on the event type other actions will be done. But I haven't managed to get this properties. Is it even possible? I am using Dojo 1.6!
searchEvent = dojo.connect(combo, 'onChange', function(event) {
// console debug(event.type);
});
To listen for key-events, you can use dojo/keys.
Have a look :
http://dojotoolkit.org/reference-guide/1.6/dojo/keys.html (the old one) and here the new AMD :
http://dojotoolkit.org/reference-guide/1.9/dojo/keys.html
Please note that the old dojo.connect is deprecated an will sooner or later not work anymore. So if it is not a big problem for you, I would use the new AMD.
You can listen for the onKeypress like this(using dojo 1.8):
require(["dojo/keys","dojo/dom","dojo/on"], function(keys, dom, on){
on(dom.byId("exampleCombo"), "keypress", function(evt){
alert(evt.charCode); //alerts the charCode of a letter or Number pressed
alert(evt.keyCode); // alerts the keyCode of a key like ENTER
});
});
In this example the CharCode of the pressed key will be alerted.
To listen for the onChange of the Combobox you can do it like this
require(["dojo/dom", "dojo/on"], function (dom, on) {
on(dom.byId("exampleCombo"), "change", function (evt) {
alert('evt : '+JSON.stringify(evt.type));
});
});
The alert shows : evt : 'change'.
Here's the fiddle for the Example above: http://jsfiddle.net/WPWv3/
For more information about events and their handling look here:
http://dojotoolkit.org/reference-guide/1.9/quickstart/events.html
Hope i could help you.
Regards, Miriam

How do I programmatically force an onchange event on an input?

How do I programmatically force an onchange event on an input?
I've tried something like this:
var code = ele.getAttribute('onchange');
eval(code);
But my end goal is to fire any listener functions, and that doesn't seem to work. Neither does just updating the 'value' attribute.
Create an Event object and pass it to the dispatchEvent method of the element:
var element = document.getElementById('just_an_example');
var event = new Event('change');
element.dispatchEvent(event);
This will trigger event listeners regardless of whether they were registered by calling the addEventListener method or by setting the onchange property of the element.
By default, events created and dispatched like this don't propagate (bubble) up the DOM tree like events normally do.
If you want the event to bubble, you need to pass a second argument to the Event constructor:
var event = new Event('change', { bubbles: true });
Information about browser compability:
dispatchEvent()
Event()
In jQuery I mostly use:
$("#element").trigger("change");
ugh don't use eval for anything. Well, there are certain things, but they're extremely rare.
Rather, you would do this:
document.getElementById("test").onchange()
Look here for more options:
http://jehiah.cz/archive/firing-javascript-events-properly
For some reason ele.onchange() is throwing a "method not found" expception for me in IE on my page, so I ended up using this function from the link Kolten provided and calling fireEvent(ele, 'change'), which worked:
function fireEvent(element,event){
if (document.createEventObject){
// dispatch for IE
var evt = document.createEventObject();
return element.fireEvent('on'+event,evt)
}
else{
// dispatch for firefox + others
var evt = document.createEvent("HTMLEvents");
evt.initEvent(event, true, true ); // event type,bubbling,cancelable
return !element.dispatchEvent(evt);
}
}
I did however, create a test page that confirmed calling should onchange() work:
<input id="test1" name="test1" value="Hello" onchange="alert(this.value);"/>
<input type="button" onclick="document.getElementById('test1').onchange();" value="Say Hello"/>
Edit: The reason ele.onchange() didn't work was because I hadn't actually declared anything for the onchange event. But the fireEvent still works.
Taken from the bottom of QUnit
function triggerEvent( elem, type, event ) {
if ( $.browser.mozilla || $.browser.opera ) {
event = document.createEvent("MouseEvents");
event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
elem.dispatchEvent( event );
} else if ( $.browser.msie ) {
elem.fireEvent("on"+type);
}
}
You can, of course, replace the $.browser stuff to your own browser detection methods to make it jQuery independent.
To use this function:
var event;
triggerEvent(ele, "change", event);
This will basically fire the real DOM event as if something had actually changed.
This is the most correct answer for IE and Chrome::
var element = document.getElementById('xxxx');
var evt = document.createEvent('HTMLEvents');
evt.initEvent('change', false, true);
element.dispatchEvent(evt);
If you add all your events with this snippet of code:
//put this somewhere in your JavaScript:
HTMLElement.prototype.addEvent = function(event, callback){
if(!this.events)this.events = {};
if(!this.events[event]){
this.events[event] = [];
var element = this;
this['on'+event] = function(e){
var events = element.events[event];
for(var i=0;i<events.length;i++){
events[i](e||event);
}
}
}
this.events[event].push(callback);
}
//use like this:
element.addEvent('change', function(e){...});
then you can just use element.on<EVENTNAME>() where <EVENTNAME> is the name of your event, and that will call all events with <EVENTNAME>
The change event in an input element is triggered directly only by the user. To trigger the change event programmatically we need to dispatch the change event.
The question is Where and How?
"Where" we want the change event to be triggered exactly at the moment after a bunch of codes is executed, and "How" is in the form of the following syntax:
const myInput = document.getElementById("myInputId");
function myFunc() {
//some codes
myInput.dispatchEvent(new Event("change"));
}
In this way, we created the change event programmatically by using the Event constructor and dispatched it by the dispatchEvent() method. So whenever myFunc() method is invoked, after the //some codes are executed, our synthetic change event is immediately triggered on the desired input element.‍
Important result: Here, the change event is triggered by executing the //some codes in myFunc() instead of changing the input value by the user (default mode).
if you're using jQuery you would have:
$('#elementId').change(function() { alert('Do Stuff'); });
or MS AJAX:
$addHandler($get('elementId'), 'change', function(){ alert('Do Stuff'); });
Or in the raw HTML of the element:
<input type="text" onchange="alert('Do Stuff');" id="myElement" />
After re-reading the question I think I miss-read what was to be done. I've never found a way to update a DOM element in a manner which will force a change event, what you're best doing is having a separate event handler method, like this:
$addHandler($get('elementId'), 'change', elementChanged);
function elementChanged(){
alert('Do Stuff!');
}
function editElement(){
var el = $get('elementId');
el.value = 'something new';
elementChanged();
}
Since you're already writing a JavaScript method which will do the changing it's only 1 additional line to call.
Or, if you are using the Microsoft AJAX framework you can access all the event handlers via:
$get('elementId')._events
It'd allow you to do some reflection-style workings to find the right event handler(s) to fire.
Using JQuery you can do the following:
// for the element which uses ID
$("#id").trigger("change");
// for the element which uses class name
$(".class_name").trigger("change");
For triggering any event in Javascript.
document.getElementById("yourid").addEventListener("change", function({
//your code here
})

Categories