Passing 'event' into the function as an argument - javascript

I'm novice with both JS and jQuery, and I'm a little bit confused about what situations would require you to pass event as an argument into the function, and what situations you would not need to.
For example:
$(document).ready(function() {
$('#foo').click(function() {
// Do something
});
});
versus
$(document).ready(function() {
$('#foo').click(function(event) {
// Do something
});
});

The event argument has a few uses. You only need to specify it as an argument to your handler if you're actually going to make use of it -- JavaScript handles variable numbers of arguments without complaint.
The most common use you'll see is to prevent the default behavior of the action that triggered the event. So:
$('a.fake').click(function(e) {
e.preventDefault();
alert("This is a fake link!");
});
...would stop any links with the class fake from actually going to their href when clicked. Likewise, you can cancel form submissions with it, e.g. in validation methods. This is like return false, but rather more reliable.
jQuery's event object is actually a cross-browser version of the standard event argument provided in everything but IE. It's essentially a shortcut, that lets you use only one code path instead of having to check what browser you're using in every event handler.
(If you read non-jQuery code you'll see a lot of the following, which is done to work around IE's deficiency.
function(e) {
e = e || window.event; // For IE
It's a pain, and libraries make it so much easier to deal with.)
There's a full accounting of its properties in the jQuery docs. Essentially, include it if you see anything you need there, and don't worry otherwise. I like to include it always, just so I never have to remember to add it in later if I decide that it's needed after all.

You only need the event if you're going to use it in the body of the handler.

Since you are using jQuery, you only put event as an argument if you need to use the event in the handler, such as if you need the key that was pressed on a keypress event.
In JS, without jQuery or Prototype etc., you need to pass the event as a parameter for standards compliant browsers like Firefox, but the event is not passed as an argument in IE. IE actually maintains a global variable window.event. So in your handler (sans library) you need to check if the argument is undefined; if so, grab the global variable.
function eventHandler(evt) {
var theEvent = evt || window.event;
//use the event
}
But a library like jQuery takes care of that for you.
I honestly don't recommend using a library until you have learned the language. But if this is for a job, the by all means use the library, but learn the details of JS on your own, so you can better appreciate it.

Related

Why does preventDefault not get attched whith the default event argument?

It is said that if we use an event listener as:
document.getElementById("mybtn").addEventListener("click". function(/* event */){...});
then we don't need to mention event parameter inside the function's paranthesis. But if that is true then why don't the following work:
document.getElementById("mybtn").addEventListener("click". function(/* event */){ event.preventDefault()});
This is not true. It can't be implemented like this officially as long as event is not a reserved keyword.
Because people can then do:
document.getElementById("mybtn")
.addEventListener("click",function(troll,event) {
event.preventDefault(); // Boom! error.
});
and complain event is not working. Then it'll become necessary to document how people should not use the event object.
Some browsers might have a global event object. But implementations vary and it's not advisable to rely on such things.
History.
" Some posts say IE allows window.event to accessed without passing event"
When Microsoft corporation reverse engineered JavaScript off Netscape Corporation without consultation, they chose not to implement passing event as a parameter to event handlers.
Instead, IE implemented a window.event property where data for individual events could be accessed. It was during the browser wars when convincing programmers to write code for your browser that failed in the opposition's browser was seen as a good thing. Since JavaScript is single threaded, no re-entrancy problems were created by IE's approach.
You may still see event handler code starting out as
function handler(event)
{ event = event || window.event;
// handler code
}
which is a cross browser way of picking up IE's event object if not supplied to the handler function. At some point IE started passing the event object as a parameter in addition to setting it as a window property to maintain backwards compatibility in their browsers. It certainly works in IE 11 where
<span onclick="alert(arguments[0] === window.event);">click2</span>
alerts "true' when clicked, indicating event is passed as a parameter. In Firefox it is false on the basis window.event doesn't exist.
For current browsers name the first parameter of an event hander as event. If you want to support old versions of IE, use the cross browser code above. Never use 'window.event' by itself.
P.S. Experimenting with document emulation mode in IE11, IE versions <= 8 do not pass event parameter objects to handlers. According to this SO question neither does IE9 in quirks mode. Although IE8 may be the last operable version of IE under Windows XP, the browser is not supported by major commercial web sites in my country (Australia) which forces XP users to upgrade to a different browser. Whatever your case, do not use windows.event by itself.
document.getElementById("mybtn").addEventListener("click", function(event){ event.preventDefault()});
You are passing a callback function to the addEventListener. This function takes a parameter and you're using it.
Maybe changing it to another name and improving the indentation will help you understand this better.
document.getElementById("mybtn")
.addEventListener("click",
function(hello) {
hello.preventDefault()
});
To understand callback functions, try this
If you want use event directly then you have to pass event in that function from where you are calling that function. Otherwise you can create event like this:
document.getElementById("mybtn").addEventListener("click". function(/* event */){
var e=document.getElementById("mybtn");
e.preventDefault()
});

Getting access to $(this) in jQuery's .on() data object?

I am using .on() to add listeners a few items in my DOM - one input range field, and a number of blocks with the class .colorBlock. These event listeners only need to be active intermittently, and I would like to turn them .off() when they are not in use. Doing this means using a named function instead of an anonymous one.
So fair enough, except that I need to pass data into the callback functions. I know I can use the second (third?) argument field to pass in an object to the event, which is readable in the callback, but doing so seems to be scoping this to the event, instead of to the DOM node that .on() was listened on. See below for example:
$('#brushSize').on('touchend', { size: $(this).val() }, utils.setBrushSize);
$('.colorBlock').on('touchstart', { color: $(this).data('color') }, utils.setColor);
In my callback functions, I added an alert for e.data.color and e.data.size, and both call out undefined.
To make matters worse, this is a phone gap app, so I am limited in my options to trace what is getting passed around, so some of what I am assuming could be wrong about what is going on.
Any suggestions?
Thanks.
Let's break down this line:
$('#brushSize').on('touchend', { size: $(this).val() }, utils.setBrushSize);
It's exactly the same (other than the variables) as this:
var sizeValue = $(this).val();
$('#brushSize').on('touchend', { size: sizeValue }, utils.setBrushSize);
E.g., you're calling $(this).val(), and then passing the result of calling it in as part of your data object. So unless this is already what you want to get the value from at that point, it's not going to work.
If you want to get some information from the element when the event happens, just put that code in your event handler. For example, looking at this line:
$('.colorBlock').on('touchstart', { color: $(this).data('color') }, utils.setColor);
It looks to me like you're trying to get the color from the .colorBlock element that was touched. If so:
$('.colorBlock').on('touchstart', function() {
utils.setColor($(this).data('color'));
});
Or if you're going to reuse it:
utils.setColorFromEventElement = function() {
utils.setColor($(this).data('color'));
};
and
$('.colorBlock').on('touchstart', utils.setColorFromEventElement);
Side note:
There's also a possible second problem with that line. You're using utils.setBrushSize as the event handler. Note that within the call to setBrushSize, this will refer to the DOM element on which you hooked the event, not utils. Now, given the name utils, maybe that doesn't matter, but I thought I'd mention it.
More: Mythical methods, You must remember this
The value you're sending in the arguments object is always going to be the number it was when you called the .on() statement. That function's not going to be dynamically re-called every time the event fires.
Personally I think it's really ugly to have the util class go looking for some DOM element and get its value, when as you alluded, what you really want to do is have your util function run in the same scope as the .on() statement.
Your first instinct was probably correct. You don't want an anonymous function, because you want to be able to call off(). Ideally you want a named function that runs in the same scope as the thing that calls the on() statement. So what you want to do is bind the util function to your current scope:
$('#brushSize').on('touchend', utils.setBrushSize.bind(this));
Then in utils.setBrushSize, $(this) is whatever function you called .on() from.
edit Just a warning on this though: when you call off(), you want to call it like this:
$('#brushSize').off('touchend', utils.setBrushSize);
Not on a new scope-bound version of setBrushSize. JQuery should recognize it as equal to the original function you bound and turn it off.
re-edit I'm realizing now that your val() is in $('#brushSize') as that's the "this" you're trying to call... not the function holding the on statement. In that case you can do it this way:
$('#brushSize').on('touchend', utils.setBrushSize.bind($(this)));
So the solution for this particular problem ended up requiring that I strip this bit of code out of Phone Gap and rebuild it in a browser. I was then able to console.log the event that was being sent to the callbacks, and examine them to understand the event object better.
The solutions was to use event.target. This allowed to get the event.target.dataset.color for the .colorBlock listener, and event.target.value from the brushSize range listener.
So for future me, I would be good to have a solid working version of my app in the browser with the phone gap stuff stripped out, to do better testing for problems like this.

Valid candidates for passing into .on when used as delegator

The jQuery documentation states that you need to pass in a selector as a string to the .on() method. For example:
$('#someEl').on('click', '.clickable', function() { /* ... */ });
However, it SEEMS to work when you pass in an individual node as well:
var someNode = getElementById('someNode');
$('#someEl').on('click', someNode, function() { /* ... */ });
When I tried passing in a jQuery object, it sort of failed out as far as I can tell, and treated it as a direct binding instead of a delegated binding:
var $someNode = $('#someNode');
$('#someEl').on('click', $someNode, function() { /* ... */ });
// seemed to act as:
$('#someEl').on('click', function() { /* ... */ });
So I guess the questions are:
Is passing in a DOM node just not a documented part of the API? Or did I miss it in the API docs?
Is there a benefit to caching the node (not the jQuery object-wrapped node), or does jQuery ultimately do the same amount of work? (in other words, I can assume when I pass a selector string that it parses it, finds the valid nodes, and then performs the binding... but if I provide it a nice fresh DOM node will it pass on this stage, or does it still for some reason wrap things up in jQuery before going to work?)
Am I wrong about the jQuery object being an invalid candidate? Did I just miss something in my testing? It seems silly that if I'm already caching jQ objects, that I'd have to supply a selector again (making it do the whole selection process again) rather than being able to use what I've already done...?
Delegation serves two purposes:
Setting a single event handler on a parent element for multiple children that share the same logic when the event is triggered. This is supposed to consume less memory, but should only make a noticeable difference when used to replace a large number of individual event handlers. I suspect this is what you're trying to achieve.
Defining event handler for elements that do not exist in the DOM at the time of the binding.
Now, to answer your questions:
Passing a DOM node is not documented, so it shouldn't be used. Although you said it works, jQuery is fooling you. By looking at the source code, it only seems to work because of event bubbling, but the this value inside the handler will be the container (see demo).
You said:
I can assume when I pass a selector string that it parses it, finds the valid nodes, and then performs the binding
That's a wrong assumption. You're always binding to the element (or elements) you're calling .on at. The selector you pass is only used to check if it matches the event object's currentTarget property, which is provided by the browser.
The same I said in #2 applies here: jQuery won't select anything based on the selector you passed, it will just check the currentTarget against that selector.
To answer your main question, no, selectors are supposed to be a string, or undefined. What you're seeing is a quirk of how jQuery tries to guess which calling convention you are using - more on this in a bit.
There's no way to pass a DOM element instead of a selector, sadly. If delegate is the element that your handler was bound to, and target is the element that fired the event, jQuery will always search delegate's descendants using the selector provided, and check if target is in the matched selection. If jQuery allowed some way to pass DOM nodes instead of a selector, there definitely would be a performance benefit.
Well, in the usage $('#someEl').on('click', '.clickable', handler) you've never selected elements matching .clickable, and neither would jQuery at that stage, so you're not doing the work doubly there.
You can call .on()in multiple ways, especially since there are multiple optional parameters(selector, data).
When you call .on(type, notAString, handler) jQuery assumes you are using the calling convention: .on(type, data, handler) - which it translates to .on(type, undefined, data, handler).
Here is a demonstration of what your suggested calls do:
http://jsfiddle.net/BGSacho/HJLXs/1/
HTML:
<div id="someEl">
<div id="someNode" class="clickable">Click me!</div>
</div>
JavaScript:
$(document).ready(function () {
function display(event) {
console.log("event.currentTarget", event.currentTarget);
console.log("event.delegateTarget:", event.delegateTarget)
console.log("event.data:", event.data);
}
$('#someEl').on('click', ".clickable", function (event) {
console.log("I am .on(type, selector, fn) - my currentTarget should be .clickable and my delegateTarget should be #somEl - this demonstrates that jQuery is using the event delegation mechanism. I have no data bound to my event.");
display(event);
});
$('#someEl').on('click', document.getElementById('someNode'), function (event) {
console.log("I'm .on(type, data, handler) - my currentTarget should be #someEl because I'm not using a delegation mechanism. I also have some data bound.")
display(event);
});
$('#someEl').on('click', $('#someNode'), function (event) {
console.log("I'm still .on(type, data, handler)");
display(event);
});
});
They might all seem to work because you don't use this(aka event.currentTarget) in your handling code. I'm not sure why you are getting different results with a jQuery object and a DOM node.

jQuery Like Inline Syntax Without Using jQuery?

In jQuery you can call a function like this for example:
$("id").someFunction();
Now after looking at the codebase of jQuery it looks like the object being created by using $ has its protoype modified to return the function which was added via .fn, in my application I would like the same syntax only without requiring jQuery.
Another example of this kind of behavior is some of Javascript's in-built methods such as: .replace, .toLowerCase, .split, .toString, etc. I understand some of those listed methods are on the String.prototype object and extending in-built objects is bad practice (so I hear).
How am I able to add a function to the prototype of every "String" or "Object" that gets assigned. The reason I am doing this is I am trying to create a cross-browser implementation of attaching events without having to do if statements all of the time.
So instead of needing to go: if (el.addEventListener) or if (el.attachEvent) I would like to be able to go el.bindEvent which behind the scenes would be a prototype method behind the scenes that would do all of the checking for event binding, etc.
My advanced JS knowledge when it comes to assigning prototype methods and whatnot isn't that great, so your help in understanding and correcting anything I've said is appreciated.
Extending the prototype of DOM elements is an even worse idea than extending built-in objects †. Please have a look at the article "What's wrong with extending the DOM".
What jQuery provides is a simple wrapper around the DOM API, which is ok. The problem with extending the DOM is that
they are host objects, and might behave differently than how objects are defined in the ECMAScript spec and
older IE versions don't even expose the prototype of DOM nodes.
The reason I am doing this is I am trying to create a cross-browser implementation of attaching events without having to do if statements all of the time.
This alone is no reason to extend the DOM. You can define your own function which binds event handlers and you even have to test whether addEventListener or attachEvent exists only once on page load. Such a function could look like this:
var bindEvent = (function() {
if (document.addEventListener) {
return function(element, event, handler) {
element.addEventListener(event, handler, false);
};
}
else {
return function(element, event, handler) {
element.attachEvent("on" + event, function() {
handler.call(element, window.event);
}
};
}
}());
You can put a lot of normalization into such a function. For example, when using attachEvent, this does normally not refer to the element the handler is bound to (it refers to window), unlike with addEventListener. Also, as you might know, the event object is not passed as argument to the handler in IE. Both of these issues have been solved through the line:
handler.call(element, window.event);
(the disadvantage is that you cannot simply remove the handler, since you didn't bind handler directly, but these problems can be solved as well).
You can find more information about these browser differences and more in the excellent articles at quirksmode.org.
†: Since browsers provide Object.defineProperty and hence the possibility to mark properties as non-enumerable, extending built-in objects is not as bad anymore, but if libraries start using this, the chance of name collisions, method overriding and incompatibilities gets higher.
For your own code it should be ok. DOM objects should still be a tabu.
If I understood correctly, you want to know how to add methods to the built in javascript objects, so:
myObject.prototype.myMethod = function() {
// whatever
}
so, by example:
String.prototype.splice = function() {
// splice function for every string
}

I'm overriding the original .focus() method. Is there any way for me to continue to do so, without breaking the original behavior

A few months ago i made a Javascript library for my work, and now it looks like it has a problem with the events handler, the problem is that i have a trigger events function by using the fireEvent method, that works great, and i have something like this:
["focus", "click", "blur", ...].each(function(e){
MyEvents[e] = function(fn){
if(!fn){
trigger(element, e);
} else {
addEvent(element, e, fn);
}
}
});
Of course this is just an idea, the original function is lot bigger... well, as you can notice, i created a custom function for all standards events so i just call it like "element.click(function...); and so...
The problem is that now if i do "input.focus();" it doesnt get focus, but it trigger the event, how can i do so the element get actually in focus ?? maybe removing the focus from the array ?? and if i do so, will i have to remove some other events too like submit, blur, etc??
thank you, actually the library is being tested, so this bugs need to be corrected as soon as possible.
Thank you again.
To get the element in focus - (that is, not triggering the event itself, but focus the element) you use the .focus() method.
You can't do that with the function listed above, because that only assigns events..
You just do something like this:
document.getElementById('#inputbox').focus();
yes, it's as simple as that
Of course, I have no idea how you're referencing the elements in the first place.
after clarifications in the comments
I'm going to restate your question:
"I'm overriding the original .focus() method. Is there any way for me to continue to do so, without breaking the original behavior?"
Yes :)
Here's an example - because I don't know your variables or anything, I'm creating an element on the fly for this example - it's not required:
e = document.createElement('input');
document.body.appendChild(e);
// note: I'm using .focus() just because it was easier for me to debug.. you
// just as well replace it with .blur() instead.
e.focus = function () {
HTMLInputElement.prototype.focus.apply(this, arguments);
}
e.focus();
JS Fiddle link: http://jsfiddle.net/DK8M7/
Ok, I'm not sure how many of those variables you're familiar with. I'm giving an overview:
HTMLInputElement is the name of the original object (think of it as a "class name") for all input elements
.prototype is an object referencing a static object shared across all objects that have or have not been created yet. Kind of like an origin.
.apply() is a method used to call a function from a specific context - that is, you choose it's "this" object, the latter argument is an array of it's parameters
arguments is a special javascript array accessible from all functions which includes an array of all of it's parameters.
More on the apply method:
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/apply
Overriding all input elements' blur() methods
One more thing... If you want all your input elements to have this behavior, the most simple way is to override it's prototype actually.. so since we're on this path, this is how you would do that:
HTMLInputElement.prototype.blurCpy = HTMLInputElement.prototype.blur;
HTMLInputElement.prototype.blur = function () {
HTMLInputElement.prototype.blurCpy.apply(this, arguments);
}
Cheers..

Categories

Resources