Confine all jQuery selectors to children of a specific element - javascript

I certainly know how to select confine a jQuery selector to children of a particular element each time I make the selection (i.e. $('#left ul') selects all ul that are children of #left). While that's simple enough, if I'm going to perform a bunch of operations to various children of #left, it's a bit cumbersome to have to keep writing $('#left ul'), $('#left p'), $('#left ...'). Instead, I'd prefer to just be able to temporarily confine the potential selectors to only children of #left. For example:
...some code that established #left as the "scope" of any future selectors;
$('ul').css(...); //actually selects $('#left ul')
$('p').css(...); //actually selects $('#left p')
...some code that re-established the full DOM as the "scope";
$('ul').css(...); //selects all ul, not just children of #left
$('p').css(...); //selects all p, not just children of #left
Thanks!

Just rely on context and find.
var $l = $('#left')
, ul = $l.find('ul')
, p = $l.find('p');

The "$" method can receive 2 arguments: the selector and the context. So, you can use the following syntax:
$('.children_selector',$('.the_parent_selector'))
But, to avoid the $(selector,context) usage. You can write a function, "overriding" the $ function, and write your code inside. Take a look:
(function($)
{
//here the $ selects the elements inside the first element with the class xpto
})(function(a){return $(a,$('.xpto')[0]);});
Anyway, I would dislike to find this kind of construction. For maintenance purposes is better to get more line of codes than fewer lines of obscure code.

Related

Cheerio: find tag with multiple specific criteria easily and elegantly?

I'm trying to web scrape https://liquipedia.net/dota2/Admiral this page for all the <li> tags that are inside an <ul> tag that again is within a div with class mw-parser-output that has the title property. (I think that is what they're called in the HTML world? Like <tag property="...">).
What would be the most elegant, simple way to do this with Cheerio? I know I could do this with some for loops and stuff, but if there was a simple way to do this, my code would be a lot cleaner.
I'm sure glad Cheerio is like jQuery. A simple selector like this should do:
const li = $('div.mw-parser-output > ul > li[title]').toArray(); // Optionaly turn selected items into an array
Explanation of the CSS selector:
div.mw-parser-output div makes sure the element is that. The dot signifies that the selector is a class.
> Points to the immediate child
ul Simple ul tag
li[title] Any li tag, but it needs to have the title attribute.
Then we turn the result into an array so it become usable.
It's a simple as that.
You could also get an array of the text of each li element with the following:
const arrayOfLiTexts = li.map($el => $el.text());
https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors
const elements = $('div[title].mw-parser-output ul li').toArray();

How can I change an attribute value for all list items?

I have a simple structure like:
HTML
<ul id="costsDropdown">
<li data-position="bla bla"></li>
</ul>
and I want to change each "data-position" attribute of my list Elements.
My first Jquery Shot was this here:
$("#costsDropdown ul").each(function() {
$("li").attr("data-position", "TEST-VALUE123");
});
but it doesnt work, I think my selector are wrong...
could anyone give me a hint please?
Thanks for any help!
Greetz
Your selectors are a bit off
$("#costsDropdown ul").each
That is trying to select the child ul of the container #costsDropdown (which is the ID of the ul) - what you want is:
$("#costsDropdown li").each(function() {
$(this).attr("data-position", "TEST-VALUE123");
});
ID's are unique - no need to double up the selector with an ID and the type of element it is.
Note that I used $(this), not $("li"), inside the each callback. $("li") selects all li elements, anywhere on the page; we just want a jQuery wrapper for the one specific one we're handling inside the each.
In fact, the each is completely unnecessary because of the set-based nature of jQuery; if you use the .attr setter, it sets the attribute on all elements in the set:
$("#costsDropdown li").attr("data-position", "TEST-VALUE123");
That will set the value on all of the li elements inside #costsDropdown.
If you need to set separate individual values on the individual li elements, you still don't need each (though it's fine if you want to use it); you can use the version of attr that accepts a callback that it uses to find out what value to set:
$("#costsDropdown li").attr("data-position", function(index) {
return "Test value " + index;
});
That will set "Test value 0" on the first li, "Test value 1" on the second, etc. And like the each example above, if you need to, you can use this within the callback to refer to the li for that call (possibly using $(this) to wrap it if you need a jQuery wrapper).
$("#costsDropdown ul") matches no elements, it has to be $("#costsDropdown") (#costsDropdown is the ul).
And even that is unnecessary. Go
$("li[data-position]").attr("data-position", "TEST-VALUE123");
instead.

Cannot select all elements except one

I am trying to select all the elements of a page except one, inside a function:
$('#sidebutton').click(function () {
if (!$('.sidemenu').hasClass("current")) {
prevScrolPos = $(window).scrollTop();
scrollTo = 0;
} else {
scrollTo = prevScrolPos;
}
$('.hidelem').toggleClass("hidden");
$('.sidemenu').toggleClass("current");
$('html,body').scrollTop(scrollTo);
});
It works when I use a simple class selector (.hidelem), but doesn't when I use something a bit more complicated (for example, $("*:not(.sidemenu)").toggleClass("hidden"); or $("*").not(".sidemenu").toggleClass("hidden");); these just lead to a blank window.
Could you tell me what I'm missing here?
JSFiddle: https://jsfiddle.net/et978wjw/5/ (full functionality is missing but I hope you get the idea)
The problem is that you may be skipping .sideMenu with $("body *:not(.sidemenu)"), but you are not skipping its parent DIV. If you hide an ancestor, you hide all its descendants too. You also do not skip any descendants, so the children of .sidemenu are also hidden
So you need to exclude anything that is an ancestor of .sidemenu with :not:(has()), then exclude the sidemenu itself, then exclude any children of sidemenu:
$("#container :not(:has(.sidemenu)):not(.sidemenu):not('.sidemenu *')").toggleClass("hidden");
JSFiddle: https://jsfiddle.net/TrueBlueAussie/et978wjw/8/
You really should direct the hide/show at something more specific though. Perhaps a wrapper div around everything you want hidden? I added one for the demo.
Now having said all that, your selection process is quite complicated. You would be better off simply adding a class to all the things you want to toggle instead and just toggle those (you already have nodisplay on the divs, so I used that for now).
e.g. just this:
$(".nodisplay").toggleClass("hidden");
JSFiddle: https://jsfiddle.net/TrueBlueAussie/et978wjw/9/

Each vs smart selector

What is better to use, the each function it self or using some smart selectors to do the same thing.
Example:
EACH FUNCTION
$('nav#mainNav > ul > li > ul').each(function(){
$(this).closest('li').addClass('hasSub');
});
SELECTOR
var addClass = $('nav#mainNav > ul > li > ul').closest('li').addClass('hasSub');
Both of these do the same thing, however is there any point to doing one over the other?
Explicitly iteration (.each) should be used when applying logic that varies between elements. Otherwise, implicit iteration will do just fine.
Your code sample is almost exactly the same as the .each API page:
Note: most jQuery methods that return a jQuery object also loop
through the set of elements in the jQuery collection — a process known
as implicit iteration. When this occurs, it is often unnecessary to
explicitly iterate with the .each() method.
For example, instead of using a selector to select uls nested inside lis and traverse a level up, you can also use .each applying a logic test for lis that contain uls:
$('#mainNav > ul > li').each(function() {
if ($(this).children('ul').length) {
$(this).addClass('hasSub');
}
});
Though this is more verbose, it does essentially the same thing.
For code-golf purposes, as mentioned in the comments and Bergi's answer, li:has(ul) would be the shortest way of achieving this without requiring a .closest() call. Though :has is not essentially the same as the children selector - :has looks for descendants (various levels deep) while > looks for direct children (1 level deep) - I believe it should work as well for this specific use case.
You could use the jQuery .has() method instead which reads a lot better:
var addClass = $('nav#mainNav > ul > li').has('ul').addClass('hasSub');
Both of these do the same thing, however is there any point to doing one over the other?
Yes. The second one is shorter and more performant (since .each is called anyway internally from those methods). There is absolutely no reason to incorporate an each here - it's just as pointless as using .each(function(){ $(this).addClass(…); }).
Btw, maybe you're looking for the :has selector, which would shorten your expression even more:
$('#mainNav > ul > li:has(ul)').addClass('hasSub');
If you want to be sure that the ul is a direct child of the list item, you still may want to use the suggestion from the comments (the closest(li) is always the direct parent of the ul):
$('nav#mainNav > ul > li > ul').parent().addClass('hasSub');
Most jQuery methods will operate on a collection of elements, and automatically iterate as if you'd used .each(). So one does not normally use .each() in simple cases like that.
However, if you need to perform multiple operations based on each element, it may be necessary to use .each(). E.g.
$('nav#mainNav > ul > li > ul').each(function () {
$(this).closest('li').addClass('hasSub');
$(this).closest('.otherClass').removeClass('.otherClass');
});
You could write this without .each() by using .end():
$('nav#mainNav > ul > li > ul')
.closest('li').addClass('hasSub').end()
.closest('.otherClass').removeClass('.otherClass');
but I believe .each() makes the intent clearer (although indentation, like this, does a pretty good job as well).

A quick way of hiding divs?

I have the following line of code
document.getElementById("divName").style.display = "none";
How do I hide a bunch of layers at once with totally different names without writing the line of code that often?
Thanks
Felix's thoughts are good. There's a third way: Since they all share a common ancestor (body), you can hide them by adding a class to body and having rules in the CSS that match the actual elements in question, like so:
body.foo table {
display: none;
}
That would hide every table on a page if you added the class "foo" to body, like this:
document.body.className += " foo";
...and then show the tables again if you removed it:
document.body.className =
document.body.className.replace(/\bfoo\b/, '');
Live example
Naturally, that selector can be a lot more discerning:
body.foo div.magic > table {
display: none;
}
That would only hide table elements that were immediate children of a div with the class "magic" (and only when body had the class "foo").
Off-topic: If the approach above doesn't suit (and it doesn't suit a lot of situations), JavaScript libraries like jQuery, Prototype, YUI, Closure, or any of several others can make manipulating sets of elements (in the ways that Felix mentioned) dramatically easier than going it alone.
Option 1 -- Create a function
function hideDiv(divname) {
document.getElementById(divname).style.display = "none";
}
Option 2 -- Hide a parent element
If all of the elements can be put inside of a parent element or already are, you can simply hide that parent element.
Option 3 -- Use a framework
A javascript framework like jQuery or MooTools will have a convenient coding convention such as .hide()
jQuery: -- see http://api.jquery.com/hide/
mooTools -- see http://mootools.net/docs/more/Element/Element.Shortcuts
Also, frameworks have tools for more complex situations and will allow you to select children of elements or a particular class and iterate through them. They can come in very handy when working with a page that has dynamically created content.
`
// jQuery Example 1: class-hiding
$(".elementsToHide").hide()
// jQuery Example 2: hiding divs within element "#whatever"
$("div", "#whatever").each(function() {
$(this).hide();
});
If they all have the same parent/ancestor, hide the parent (if possible).
Get the references to that elements, put them into an array and loop over them.
var divsToHide = ["thisDiv", "thatDiv", "divName"];
for (var i=0; i<divsToHide.length; ++i)
{
var div = document.getElementById(divsToHide[i]);
if (div) div.style.display = "none";
}
Or, you could use a framework like jQuery, and give the hidden divs some attribute in common, like a class of "hidden". Then,
$(".hidden").hide();
Course, in that case, you could also just set display: none on the class via CSS.
If they are from the same class you could select all elements of that class and loop through them. If they are all of the same parent you can select all the children, loop through them, filter if necessary and hide them this way.
var names = ['divName1', 'divName2','divName3'];
for ( i in names ) document.getElementById(names[i]).style.display = "none";

Categories

Resources