Why does jQuery .after() not chain the new element? - javascript

I am curious why jQuery's .after() does not chain, or "provide you with," the new element that you create with it. It seems to me like it should, but I am no expert and was hoping someone could shed some light on why it's a bad idea. Here's an example to see what I would like from .after():
Here is some markup:
<div id="content1">
<p id="hello1">Hello</p>
</div>
<div id="content2">
<p id="hello2">Hello</p>
</div>
Here's what I want, but this replaces "Hello" with "World!":
$('#hello1').after('<p />').text('World!');
This does the job, but it sure is ugly:
$('#hello2').after('<p />');
var $newParagraph = $('#content2 p').last();
$newParagraph.text('World');
This is nicer, but I'm not entirely sold: (maybe I should be?)
$('#hello1').after( $('<p />').text('World') );
Note this is obviously a simplified example, that is, "<p>World!</p>" is not what I want to add, my real world problem is more complex than that.
Thanks for any thoughts.

Presumably .after() works as it does to allow you to chain more methods that would apply to the originally selected element(s):
$('#hello1').after( $('<p />').text('World') )
.before( $('<p />').text('Before text') )
.remove();
This keeps the behaviour consistent with other methods, so you don't have to try to remember which ones return the original object and which return some other object.
Note that in your case $('#hello1') selects exactly one element since you are selecting by id, but you can apply .after() to a jQuery object containing many elements to add (cloned) content after multiple elements:
$('div.someclass').after( $('<p/>').text('whatever') );
In which case in my opinion it definitely makes sense to keep the chaining context as the original jQuery object.
The .insertAfter() method might be closer to what you want:
$('<p/>').insertAfter('#hello1').text('World');
(It also returns the jQuery object it was applied to, but it does the insert in "reverse".)
You could write your own plugin method to return the element being added, but that would be confusing since it would work opposite to all the standard methods.

That's just how it is for most jQuery methods. It's better for jQuery to be consistent and return the original object.
The fix is this:
$('#hello1').after(
$('<p/>', { text: 'World!' })
);
or:
$('#hello1').after(
$('<p/>').text('World!')
);
or even:
$('<p/>').text('World!').insertAfter('#hello1');
The latter may actually be preferred since it does then continue to chain from the new <p> element.

It doesn't provide you with the created element because you don't always create an element, it would require a significant amount of processing power for JQuery to detect that a element is created and it makes much more sense to always have it return the element witch was the target of the operation.

Related

Find the child of the element represented by "this" that has a certain class

I know how to accomplish this if I can identify this element using a selector:
$("selector.class")
But what if my selector is the keyword this? Obviously $(this".class") isn't going to work, and I don't want to use $(this).children(".class") because then I need to extract the HTML of the element using .html(), and while I know that there will only be one element of this class in the selected element (I'm writing the HTML), JQuery doesn't, and it assumes that children() returns several elements when used with a class (at lease I think that's what it does, because
$(this).children(".class").html()
returns undefined).
Is there an other way I could do this?
Please feel free to ask for clarification, I understand this may not seem clear to some.
EDIT: some of you asked for clarification, so here it is. Let me rephrase the question: normally, when I ask JQuery to get me some elements, and give it a class as a selector it assumes I will get more than one element back and therefore $(".selector").html() doesn't work, because you can't get the HTML of several elements (at least that's my theory). Instead, I want it to recognise that in this case I am only getting 1 element back, and treat is as such. My restriction is that part of my selector is this. I hope that helped!
It isn't entirely clear to me what question you're asking so here are several different options:
To search for any subordinate tags (in the DOM tree with this as its root) that match a desired selector:
$(this).find(".myClass");
Or, if you're just trying to see if the this element has a particular class name, you can use:
if ($(this).hasClass("myClass")) {
// this element has the desired class
}
or, if the selector is more complicated than just a class, you can use .is() like this:
if ($(this).is("selector")) {
// this element matches desired selector
}
Really this isn't a selector, but I think you can do:
$(".class", this)
This is an example of supplying the context argument to the jQuery ($) function
For example (jsfiddle here),
HTML:
<div id="dis">hello
<div class="cls">
hi</div></div>
<div class="cls">
goodbye</div>
jQuery:
$(function () {
$('#dis').click(function () {
alert(
$('.cls', this).html());
});
});
will alert "hi" when the "dis" div is clicked.
Jquery is just a layer on top of JavaScript.
Just use raw javascript to get what you're looking for.
var childOfThis = this.querySelector('.myClass');

Change classname of div

In my page I want to change the class name of the div whose id is answer1 in div id=question. How can I do that? Thanks.
$('question1 answer1').addClassName('new_name');
<div id="question1">
<div id="answer1" class="old_name">
</div>
</div>
<div id="question2">
<div id="answer2" class="old_name">
</div>
</div>
So, as it has been pointed out, any selector method requires an iterator of some sort, so you can not just apply a method to all objects returned. You will notice in the documentation for Element.select that this is supposed to alleviate having to explicitly convert the object to an array, but I have not had luck with this on jsfiddle. However, I did try the following:
$('answer2').addClassName('new_name');
​
And it worked just fine. I don't know if the issue is that you are trying to traverse the DOM in your original element object (by using question1 answer1) and this requires the object/array iterator, or if it's just a hiccup elsewhere in the code. But in your specific example, since you know the id of the actual element you want to change the class of, the above code should work fine without specificing the parent element or using an array index of any kind.
I will admit that prototypejs threw me off because they use the same method names for the Element objects as Enumerable objects, so I saw the first instance of select and thought it looked pretty straight forward. Having now seen that almost every class/method requires you to set up a selector and convert it or manually traverse it, I will definitly say that this would be much easier with jquery and that your initial comment that they are almost the same is specifically not true in this scenario. Perhaps protojs offers some features that jquery does not, perhaps your code is tied to protojs (I worked on a project for a year that had to use YUI, which is a much bigger nightmare, trust me), but jquery is set up to play nice with prototypejs, so in cases like these, I'd consider using both. In jquery, the code would have been:
jQuery.noConflict(); // Avoids $ fight between jquery and prototypejs
jQuery( '#question1 #answer1' ).addClass('new_name');
or, to remove the old one first:
jQuery.noConflict(); // Avoids $ fight between jquery and prototypejs
jQuery( '#question1 #answer1' )removeClass('old_name').addClass('new_name');
Also, prototype has a toggleClass method that is probably also unnecessarilly esoteric, but maybe you should read up on: toggleClassName
And I wouldn't be this pissy about wasting my time on this (because I don't like to consider getting stuck learning a new framework a waste of time) except that their documentation, while attractive, has the worst examples I've ever seen. They all assume you have some fundamental idea without ever a friendly real world example or hyperlink to the difference between an instance and a class, and their examples distinguishing the two are identical. I'm definitely going to take some time out later today to find the real best answer to your question, out of spite and pride, if nothing else, but if it really comes down to having to iterator every time, just use vanilla js and use this framework when it's actually useful.
Simply $('answer1') will get you a reference to the div element that you want. The $ function returns an element with the given id string (or null if none was found). Note that you don't use a selector string when using $ - it only operates on ids.
The $$ function takes in a CSS selector string and returns an array of all matching elements, or an empty array if nothing matched. If you wanted or needed to go that route, you could locate the same div like this:
$$('#question1 .answer1')[0]
Once you have your element reference using either of the above methods, you can use addClassName or removeClassName or any other element methods available.

jQuery - is there better syntax than "parent().parent().parent()"

I'm doing some quite rudimentary jQuery stuff, getting started really, and I'm frequently navigating up the dom by doing things like
$(this).parent().parent().addClass('hello');
I was just wondering if there's a nicer way to do this?
You can use parents, which returns all ancestor elements in turn. If you want to stop traversing at a particular level, use eq to filter the resulting collection. For example, to get the grandparent:
// 0 = parent, 1 = parent of parent, etc.
$(this).parents().eq(1).addClass('hello');
If you want to go upwards through the tree and stop not at a particular level, but at a particular selector match, use closest instead, e.g.:
$(this).closest("table").addClass('hello');
Say you have the following HTML:
<div>
<span>
<strong>Hi there</strong>
</span>
</div>
You could just use .parents() to get the div element:
$("strong").parents("div").addClass("hello");
Simply replace "strong" with this and you can use a selector to find a specific parent element.
You can use the closest() method, which returns the first element that matches the given selector in the ancestor chain.
$(this).closest(SELECTOR_OF_THE_PARENT).addClass('hello')
Sounds like your after .parents() or .closest() -- the latter being the most efficient, as it stops progressing up the DOM once it matches the closest selector.
"I was just wondering if there's a nicer way to do this?"
It's definitely a great idea to be intimately familiar with jQuery's selection and traversal methods.
However, if in the future you find yourself overwhelmed by traversal and discover:
That your "data model" is actually contained in your DOM
Maintaining and changing applications of this nature is very painful
...it might be time to consider a formal model-view approach.

How to optimize $.find().first()?

I need to retrieve the first element.
I do that with this code...
$(element).find('.x').first();
As much as I understand, that code...
Retrieves all elements from element that matched .x,
Removes unneeded elements;
Is there any better way to do it? Like $.findOne() or something?
As per jQuery docs:
Because :first is a jQuery extension and not part of the CSS
specification, queries using :first cannot take advantage of the
performance boost provided by the native DOM querySelectorAll()
method. To achieve the best performance when using :first to select
elements, first select the elements using a pure CSS selector, then
use .filter(":first").
So rewriting your selector to:
$(element).find('.x').filter(":first")
or (this one will give you direct descendants only and will be faster than .find, unless you're looking for nested elements too)
$(element).children('.x').filter(":first")
should give you better results.
Update After valuable inputs from kingjiv and patrick dw (see comments),
it does seem that these two are faster than .filter(':first') contrary to what the doc claims.
$(element).find('.x').first(); // faster
$($(element).find('.x')[0]); // fastest
If you want to have it real fast, you should use native browsers methods. Modern browsers support querySelector [docs]:
var $result;
if(element.querySelector) {
$result = $(element.querySelector('.x'));
}
else {
$result = $(element).find('.x').first();
}
The usage is a bit limited, as it would only work if element is a single element and if the selector is a valid CSS selector. You could make a plugin out of it. But then, if you consider all cases, like multiple elements etc., there is probably no advantage anymore.
So again, if you have a very specific use case, this might be useful, if not, stick with jQuery.
Update: Turns out, making a plugin is still faster: jsPerf benchmark
(function($) {
$.fn.findOne = function(selector) {
try {
var element, i = 0, l = this.length;
while(i < l && (element = this[i].querySelector(selector)) === null) {
i++;
}
return $(element);
}
catch(e) {
return this.find(selector).first();
}
};
}(jQuery));
How this works:
The plugin iterates over the selected DOM elements and calls querySelector on each of them. Once an element is found, the loop will terminate and return the found element. There are two reasons an exception could occur:
The browsers does not support querySelector
The selector is not a pure CSS selector
In both cases the plugin will fall back to use the normal jQuery method.
As crazy as it seems, in every performance test I've seen, .first() has better performance than :first.
As most people are suggesting, it seems as though using $(element).find(".x:first") should have better performance. However, in reality .first is faster. I have not looked into the internals of jquery to figure out why.
http://jsperf.com/jquery-select-first
And apparently using [0] and then rewrapping in a jquery object is the fastest:
$($(element).find(".x")[0])
EDIT: See mrchief's answer for an explanation of why. Apparently they have now added it to the documentation.
This should be better
$(element).find('.x:first');
Use :first selector:
$(element).find('.x:first')
It's better to write:
$('a:first');
What you're writing is "in 'element', find '.x' and return the first one". And that can be expressed like this
$('.x:first', element);
how about using first-child pseudo class ? like
$(element).find('.x:first-child')
However it might generate issues if your structure is like
<div>
<p></p>
</div>
<div>
<p></p>
</div>
so actually it is not what you are looking for (if you mean general solution). Others mnetions :first and this seems to be the correct approach
Your bottleneck is really the .find(), which searches all the descendants instead of just the immediate children.
On top of that, you're searching for a class .x (which uses a jQuery custom search) instead of an ID or a tagname (which use native DOM methods).
I would use Mrchief's answer and then, if possible, fix those two bottlenecks to speed up your selector.
You could combine the $(element) and .find() calls using a descendant selector; I'm unsure of the performance comparison:
$("#element .x").first().hide();
That way is fine according to the jQuery documentation, or at least better than using :first selector.
You can try as alternatives .filter(":first") or get the first element using array accessor against the .find() result [0].
Also, instead of .find() you can change it to:
$('.x', element)
To narrow the search to .x elements inside element, intead of searching the whole document.

How can I check if an element is within another one in jQuery?

Is there any direct way in JavaScript or jQuery to check if an element is within another one.
I'm not referring to the $(this).parent as the element I wish to find can be a random number steps lower in the tree of elements.
As an example, I would like to check if < div id="THIS DIV"> would be within < div id="THIS PARENT">:
<div id="THIS_PARENT">
<div id="random">
<div id="random">
<div id="random">
<div id="THIS_DIV">
(... close all divs ...)
So in pseudo code:
if($("div#THIS_DIV").isWithin("div#THIS_PARENT")) ...
If there isn't any direct way I'll probably do a function for this but still it's worth asking.
You could do this:
if($('#THIS_DIV','#THIS_PARENT').length == 1) {
}
By specifying a context for the search (the second argument) we are basically saying "look for an element with an ID of #THIS_DIV within an element with ID of #THIS_PARENT". This is the most succint way of doing it using jQuery.
We could also write it like this, using find, if it makes more sense to you:
if($('#THIS_PARENT').find('#THIS_DIV').length == 1) {
}
Or like this, using parents, if you want to search from the child upwards:
if($('#THIS_DIV').parents('#THIS_PARENT').length == 1) {
}
Any of these should work fine. The length bit is necessary to make sure the length of the "search" is > 0. I would of course personally recommend you go with the first one as it's the simplest.
Also, if you are referring to an element by ID it's not necessary (although of course perfectly okay) to preface the selector with the tag name. As far as speed, though, it doesn't really help as jQuery is going to use the native getElementById() internally. Using the tag name is only important when selecting by class, as div.myclass is much, much faster than .myclass if only <div> elements are going to have the particular class.
With jQuery >=1.4 (2010) you can use the very fast function jQuery.contains()
This static method works with DOM elements, not with jQuery elements and returns true or false.
jQuery.contains( container, descendant )
Example: To check if a element is in the document you could do this:
jQuery.contains( document.body, myElement )

Categories

Resources