JavaScript "normalize event object" - javascript

This program taken from a book Pro JavaScript Techniques is used to create hover-like functionality for an Element.
I don`t understand what the author means when he says, in the comments, Normalize the Event object.
Can you tell me
a) why is this necessary, explaining what would happen if it wasn`t normalized
b) how does the code provide achieve the effect
Thank you.
var div = document.getElementsByTagName('div')[0];
div.onmouseover = div.onmouseout = function(e) {
//Normalize the Event object
e = e || window.event;
//Toggle the background colover of the <div>
this.style.background = (e.type == 'mouseover') ? '#EEE' : '#FFF';
};

It's referring to window.event, IE's non-standard version of the event object. If it weren't normalized, it would break in at least one browser.
What the code does is set e to itself (essentially a no-op), if the parameter is truthy (the event parameter is properly set). If not (in IE), it sets it to window.event.

Related

Do Javascript engines do this kind of optimisation?

NB I've done a bit of reading about JS engine optimisation, but most of it is too technical for me to understand enough to apply to this question. I'm also aware that not all engines are the same. I'd be interested in particular in handling by V8 and Rhino, I suppose.
If I create a table, and then rows, and then cells... And then I want to put identical key event listeners on all those cells.
Not only does the creation of these listeners for each cell take a certain amount of time, which could be significant with a biggish table, but in addition I'm supposing that each listener function is stored on its own, even though every listener function is actually identical.
The other key event listener approach which I can use is to put a key event listener on the TABLE, and to work out during the run, on each keydown event, which cell fired this event. I can do this by going
let elementOfInterest = document.activeElement;
"Get the currently focused element in the document" from here.
From my experiments, if you type inside a table cell, this TD does indeed have the focus and is indeed returned by the above call.
This way, I only have to create one listener, which will I assume be quicker and take less memory. The only (very) slight downside is that time then has to be spent getting this "active element" by means of the above call. And, just possibly, the risk that something will grab focus in an unexpected way - obviously if you want to listen to changes of text in a cell, the least error-prone technique must be to use a listener attached to that cell.
But I'm just wondering: maybe Javascript is cleverer than this: maybe if you create 100 separate cell listeners something somewhere identifies them as "all the same" and just makes one function in memory. This is the kind of optimisation you might typically expect from a Java compiler, for example.
Does any such optimisation ever occur? How clever is Javascript with a case like this? Or is it just "script and that's it": what you see is what you get?
The semantics of the language itself don't allow for two function expressions to be "merged" into one even if they were functionally equivalent:
> a = function(){return 'foo'};
ƒ (){return 'foo'}
> b = function(){return 'foo'};
ƒ (){return 'foo'}
> a === b
false
In addition, things get extra hairy when you start considering the closure of the function (e.g. the outer names it uses).
So no, that doesn't happen out of the box.
However, for your use case, there are two optimizations:
As you've found out, you can employ event bubbling and add the event listener on an ancestor element and use event.target (preferably instead of document.activeElement) to figure out where it was originally targeted (and event.currentTarget would be the node the handler is on)
If you can't use a common ancestor (tip: you almost always can; document is a valid target), you could define the function once (assuming it doesn't need to close over any dynamically changing variables) and again use event.target, e.g. event.target.dataset to figure out the data you're handling.
Below, a snippet demonstrating the two.
function createButton(parent, datum) {
const btn = document.createElement("button");
btn.dataset.datum = datum;
btn.innerHTML = datum;
parent.appendChild(btn);
return btn;
}
function eventHandler(event) {
if(event.target.tagName !== "BUTTON") return;
const msg = `real target: ${event.target} (datum="${event.target.dataset.datum}")\ncurrent target: ${event.currentTarget}`;
alert(msg);
}
const p2 = document.getElementById("parent2");
// bubbling listener
const p1 = document.getElementById("parent1");
p1.addEventListener("click", eventHandler, false);
for(var i = 0; i < 10; i++) {
createButton(p1, "p1-" + i);
}
// same function on multiple elements
for(var i = 0; i < 10; i++) {
createButton(p2, "p2-" + i).addEventListener("click", eventHandler, false);
}
<div id="parent1"></div>
<div id="parent2"></div>

I don't really understand the DOM standard Event object

right now I'm trying to learn JavaScript with the book "Beginning JavaScript 5th Edition" and because english is not my native language it's sometimes hard to understand.
Right now I'm a bit confused with the so called DOM standard Event object.
function handleEvent(e) {
var target = e.target;
var type = e.type;
if (target.tagName == "P") {
if (type == "mouseover") {
target.className = "underline"; } else if (type == "mouseout") {
target.className = ""; }
}
};
Is e just a convention for a parameter that I could give any name I want ? For example y ?
Can I think of .target as the same as .this ?
.target references to my element on which an event occurs, right ?
e is just a convention for the parameter, and so is event. Because e has all the event propertys. Try outputting e in your console, and you'll see all the values it has. (Btw, e is just an object as you'll see in your console.)
In your handleEvent function, just add console.log(e) to see everything it has.
e.target is the element which the event is called on. And e.type is the event type. If you switch e to event things might become clearer to you.
e is indeed just a convention, so you could give it any name you want. .target is de element that fires the event, so its the element where you attached the event to.
Yes. e is the standard variable name given to the event object. That variable name could be anything.

Detect shiftKey for mouse event on google maps v3 polygon

Is there a way to detect a shift-mouseclick on a polygon when using the google maps V3(.16) javascript API?
https://developers.google.com/maps/documentation/javascript/reference#MouseEvent
I do receive the mouse event, and I can see that the original event is wrapped in a google event class, which is just what's needed (Access the event.Ua.shiftKey property).
I found however that last month, google may have updated their api and now the property is renamed to event.Pa.shiftKey.
For reference, see http://i.stack.imgur.com/80npD.png for a screenshot of the event structure in the webinspector.
Is there any way to detect whether the shift key is pressed during a click on a google maps polygon using the event, and without having to rely on google-not-updating-their-api? What am I missing?
Thx!
Using undocumented properties is almost always a bad idea. If you can possibly avoid it, avoid it. In this case, the API is probably being compressed with the Closure Compiler, and so the next time they update, it may not be Pa anymore either.
Is there any way to detect whether the shift key is pressed during a click on a google maps polygon using the event, and without having to rely on google-not-updating-their-api?
If just Ua vs. Pa:
You could feature detect:
var shiftKey = (event.Ua || event.Pa).shiftKey;
That uses JavaScript's curiously powerful || operator to reference the Ua property's value, if there is one and it's truthy, or the Pa property's value otherwise.
This assumes that when they change it from Ua to Pa, they don't use Ua for something else. If there's a chance of that, this more thorough version would do it:
var shiftKey = ((event.Ua && 'shiftKey' in event.Ua) ? event.Ua : event.Pa).shiftKey;
That specifically checks for a Ua object with a shiftKey property, falling back to Pa if not found.
If it could be anything
...you can search for it:
var shiftKey;
if (Object.keys(event).some(function(key) {
if (event[key] && 'shiftKey' in event[key]) {
shiftKey = event[key].shiftKey;
return true;
}
return false;
})) {
// We found it, `shiftKey` has the value
} else {
// We didn't find it
}
Side note: Object.keys is an ES5 feature present on all modern browsers. If you need to support older browsers like IE8, it can be polyfilled.
I've just come across this problem (after noticing I couldn't just use window.event because Firefox doesn't support it)
google.maps.mouseEvent has, as T.J. Crowder said, a property which contains keys such as shiftKey, altKey, etc. In the current release of google maps api, said property is va. So of course we can't use it because its name will change in the next release of so.
So my solution has been to iterate over the google.maps.mouseEvent parameter, checking its values so see which one of them is a valid window.MouseEvent.
var newcircle= new google.maps.Circle({map:map, position:map.getCenter(), radius:1000});
google.maps.event.addListener(newcircle,'click',function(mouseEvent) {
var event = Object.values(mouseEvent)
.filter(function (property) {
return property instanceof window.MouseEvent;
});
if (event.length) {
event = event[0];
} else {
event = {};
}
var shiftKey = event.shiftKey || false;
});
Funny enough, when I tried to use a marker for this example, there wasn't an instance of window.MouseEvent in the google.maps.mouseEvent.
I ended up with a solution close to amenadiel's answer. I thought it would be better not to relay on all the possible uglified variable names, but to look for the one being a MouseEvent and use that one to check for the shiftKey property, like this:
function polygonListener(polygonEvent) {
var shiftKey = false;
for(var i in polygonEvent){
if(polygonEvent[i] instanceof window.MouseEvent){
shiftKey = polygonEvent[i].shiftKey;
break;
}
}
// Rest of the code
}
In case anyone is still looking how to get the MouseEvent from a google maps event, this is the method I've been using.
const mouseEvent = Object.values(event).find(p => p instanceof window.MouseEvent);
// then do whatever with the MouseEvent
const shiftKey = mouseEvent.shiftKey;

srcElement.readOnly and target.readOnly problems in Internet Explorer

I'm not sure I understand the behavior of IE in this script I'm working. This is part of the script I'm using that seems to work fine in Chrome
$(document).keydown(function (event) {
var keyvalue = event.which || event.keyCode;
var eventtarget = event.srcElement.nodeName || event.target.nodeName;
var readonlycheck = event.srcElement.readOnly || event.target.readOnly;
});
The problem comes in on the readonlycheck variable. In IE I get the error of
"Unable to get property 'readOnly' of undefined or null reference."
In Chrome, readOnly returns 'true' if it's defined and 'false' if it's not. IE gives me an error, even though it still works with the nodeName.
Where I get really confused is that I can make it work by changing the last line to eliminate the target.readOnly. So this seems to work in both browsers...
var readonlycheck = event.srcElement.readOnly;
Can anyone explain why this behaves differently for readOnly? Also, I thought srcElement was IE only, so why is Chrome still working without the target.readOnly?
Any help would be appreciated. I'm still very new to javascript and jquery so I'm sure I'm missing something.
var readonlycheck = event.srcElement.readOnly || event.target.readOnly;
should change it to
var readonlycheck = (event.srcElement !== undefined) ? event.srcElement.readOnly : event.target.readOnly;
how you wrote your code, even though srcElement.readOnly does exist, when it evaluates to false it attempts to read event.target, which breaks IE.
In your code you specify function(e) though in your function you use event instead of e.
Additionally as event.target and event.srcElement are the same thing it would make more sense to initiate the target once at the start.
var target = event.target || event.srcElement;
Updating your code to something similar to the below should work:
$(document).keydown(function (event) {
var target = event.target || event.srcElement;
var keyvalue = event.which || event.keyCode;
var eventtarget = target.nodeName;
var readonlycheck = target.readOnly;
});
The problem is the short-circuit || operator. If event.srcElement.readOnly evaluates to false, the right operand, (which should be) e.target.readOnly will be evaluated. It's the same as writing, for example:
var readonlycheck = false || e.target.readOnly;
You can use braces instead to work around this issue:
var readonlycheck = (e.target || event.srcElement).readOnly
Note that I moved the standards compliant to the left hand side so that it's evaluated first. It doesn't make much of a difference here, but on more time-consuming operations it could, so I just find that it's a good practice to get into.
I start most of my event handlers with the following code when I need the event object and the event target:
function evtHandler(evt) {
evt = evt || window.event;
var target = evt.target || evt.srcElement;
}

Create KeyEvent in Javascript

I have the following problem - I'm catching a key event an I need to create a new altered key event(since it seems the keyCode property is read-only) and afterwards handle the newly created KeyEvent. I came across several old posts in StackOverflow where similar situations are handled, but:
I need this to be working under Webkit /there's a solution here in StackOverfow but it is working only in Gecko/
I need to create another KeyEvent, but not TextInputEvent, since the TextInputEvent will only let my specify a string to be inserted, whilst I cannot do that as I use a third party tool that needs to handle this event and I need a keycode.
I tried jQuery#trigger() but it won't work for me. My code is as follows
var event = jQuery.event('keydown');
event.which = 13; //I'm trying to simulate an enter
$('iframe').contents().find('document').find('body').trigger(event); //my content is inside an iframe
(function($){
$(window).load(function(){
var e = $.Event("keydown");
e.which = 13;
e.keyCode = 13;
$('iframe').contents().find('html, body').trigger(e);
});
})(jQuery);

Categories

Resources