Automatically return jQuery object - javascript

In jQuery, when I call:
$("selector").a_function(function(){
this.toggleClass("a-class");
}
I am told that this is of a certain type, and does not have the jQuery function available to it. The problem is that I am not getting jQuery objects returned to me. The way I am getting around this is to do:
jquery_object = jQuery(this);
every time. I thought that $() is equivalent to calling jQuery and that I am supposed to receive a jQuery object.
Is this normal? What am I doing wrong here?

You'll want to use $(this) instead of just this.
In the case you gave this doesn't refer to the DOM element.
By wrapping it in the standard JQuery selector function, you'll actually be calling to the DOM element.
I use this a lot for click handlers.
$('a').click(function(e) {
$(this).toggleClass('a-class');
});
Pretty much any function in JQuery that gets called through a selector and has a callback will use this format.

Related

Is there any reason to have nested calls to $ (jQuery)?

I was poking around the source code of a website when I came across some code like this:
$($('#newForm_step1')).hide("slide", { direction: "left" }, 0);
and this:
$($($('.breadcrumbs')[0]).children().last()).html("...");
I've never seen the $ (jQuery) function used this way, and I was wondering if there'd be any practical reason to do something like this? To the best of my knowledge, wrapping a jQuery object with a call to $ simply returns a jQuery object for the same selector, and methods on jQuery objects return this, so I don't see why one would need nested calls to $.
No, there is no reason to do this.
In the first example, $($(...)) is redundant. There is absolutely no effect in immediately wrapping a jQuery object in another jQuery object.
The line should read
$('#newForm_step1').hide("slide", { direction: "left" }, 0);
In the second example, $(...)[0] returns a raw DOM element, so it's wrapped again before having jQuery's .children().last() invoked on it. The result of that is already a jQuery object, so there is no need to re-wrap it, and the "unwrapping" could have been avoided by calling .first() instead of [0].
The line should read
$('.breadcrumbs').first().children().last().html("...");
There's no necessary to wrap jQuery object with jQuery. That will just result the same but over-coding and redundant.
But when you have DOM Object then you need to wrap with jQuery so that it will be jQuery object.
Taking your example:
$('.breadcrumbs')[0];//[0] converts to JavaScript Object
Now, if you want to work with jQuery again, you may then wrap with jQuery:
$($('.breadcrumbs')[0]);//is now a jQuery Object
But with this example is unnecessary because you can do just like this:
$('.breadcrumbs:eq(0)');//selects first .breadcrumbs element
However, if you have some DOM Object rather than jQuery object then you need jQuery wrapper to work with jQuery methods.
You must remember this:
JavaScript Object needs JavaScript methods to be chained:
javascriptObject.javascriptMethods
Ex-
$('.breadcrumbs')[0].addEventListener();//javascript addEventListener method
jQuery Object needs jQuery methods to be chained:
jQueryObject.jQueryMethods
Ex-
$('.breadcrumbs').on();//jQuery on method

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.

Any difference between a $ wrap function and getElementById?

I mean a wrap function like this:
function $(id) { return document.getElementById(id); }
but in some code like this:
oDiv1 = $("postInfoDiv");
oDiv2 = document.getElementById("postInfoDiv");
alert(oDiv1 == oDiv2); // return false
alert(oDiv1.style); // error
alert(oDiv2.style); // correct
alert(document.getElementById("postInfoDiv").style); // correct
I got strange results as the comments imply.
I thought the first alert should return the true since they are the same dom object.
I thought the second alert should alert something like "object" or "CSS StyleDeclaration" but not "defined".
So what are the problems? Have you ever met this kind of problems?
thanks.
Your $ function is probably being overridden, potentially by a framework.
You should try doing alert( oDiv1.nodeType ) to see if it's a DOM element. alert( oDiv1.length ) to see if it's an empty array because you may be using jQuery on the same page which overrides your $ function.
oDiv1 may be an array-like object containing that item if jQuery is included. oDiv2 is an actual DOM reference. You probably need to compare oDiv1[0] to oDiv1, in which you reference the first element in the array which points to the actual dom element to make a fair comparison.
function $(id){return document.getElementById(id)}
$('content') == document.getElementById('content')
true
The custom $ function will work perfectly but if you're using a framework function it will return that array-like object instead of the DOM element.
You can also rename your function to something like function getID which would be unique and not conflict with framework $s.
My main concern with this is that it will confuse the heck out of someone the first time they read your code, especially if they are used to coding with a JavaScript framework such as jQuery.
For this reason alone I recommend you do not use this particular syntax for your wrap function.
BTW note that even when jQuery is not loaded, Firebug provides its own $ function, which may participate to confusion.

How to set callback on single items using jQuery

I have a js function which has, until now, always been the callback for a click event, and therefore relies heavily on the 'this' pseudo-variable. 'this' is a <li> element.
However I now have a circumstance where it is sometimes triggered using more convoluted route, and in these circumstances 'this' is an entirely different element. However, before calling the function I am able to find the relevant <li>, but is there a way I can feed this in as 'this'? I thought of using .each() on the <li>, but it won't work on a single element.
edit it turns out that each() does work on single elements.. the error turned out to be something else.
Haven't deleted this question though as it could be useful to others
You are looking for the call method:
function onClick() {
console.log(this); // will be the #test element in both cases
return false;
}
$('#test').click(onClick);
$('#test2').click(function() {
onClick.call($('#test')[0]);
return false;
});
Although this is also possible with apply:
onClick.apply($('#test')[0]);
The [0] is necessary to pass the actual DOM element instead of the jQuery wrapped set.
try using jquery.callback plugin. It keeps the context of callback.

Categories

Resources