How to find "next" element in rendered layout using jQuery? - javascript

jQuery's next() returns the next immediate sibling of an element.
However, how does one find the next rendered element, even when that element has different lineage?
For example, consider this (simplistic) HTML:
<div id='a'>A
<div id='b'>B
<div id='c'>C</div>
</div>
<div>D</div>
</div>
which renders to something like:
A
B
C
D
Your eye naturally sees D following C, though in the DOM they're uncle/nephew.
So, how would one find "D" given #c?
var c = $('#c'); // Pretty straightforward
var x = c.someJqFunction(with some params);
Note: Pretend you don't know anything about D, other than it exists. It could be the true next sibling of #c, or it could be in a complex traverse from #c. The salient thing is that they are known to be immediately adjacent in the rendered output.

http://jsfiddle.net/xa13oq7z/
function getNext(id) {
var elm = $('#' + id);
return elm.children(':first')[0] || elm.next()[0] || elm.parent().next()[0];
}

This is how you can do it
$(function(){
console.log($('#c').parent().siblings().html());
});
NOTE: This is assuming that there is only one sibling for div with id "b"
Look at the Fiddle link

Will something like this do?
function getNext(elem){
var next = elem.next();
if(next.length){
if(next.get(0).offsetParent != null){
return next;
}else{
return getNext(next);
}
}else{
return getNext(elem.parent());
}
}

Related

Individually change element inside of a class through their ID [duplicate]

I am wanting something similar to this person, except the element I want to match might not be a direct sibling.
If I had this HTML, for example,
<h3>
<span>
<b>Whaddup?</b>
</span>
</h3>
<h3>
<span>
<b>Hello</b>
</span>
</h3>
<div>
<div>
<img />
</div>
<span id="me"></span>
</div>
<h3>
<span>
<b>Goodbye</b>
</span>
</h3>
I would want to be able to do something like this:
var link = $("#me").closestPreviousElement("h3 span b");
console.log(link.text()); //"Hello"
Is there an easy way to do this in jQuery?
EDIT: I should have made my specification a little bit clearer. $("#me") may or may not have a parent div. The code should not assume that it does. I don't necessarily know anything about the surrounding elements.
var link = $("#me").closest(":has(h3 span b)").find('h3 span b');
Example: http://jsfiddle.net/e27r8/
This uses the closest()[docs] method to get the first ancestor that has a nested h3 span b, then does a .find().
Of course you could have multiple matches.
Otherwise, you're looking at doing a more direct traversal.
var link = $("#me").closest("h3 + div").prev().find('span b');
edit: This one works with your updated HTML.
Example: http://jsfiddle.net/e27r8/2/
EDIT: Updated to deal with updated question.
var link = $("#me").closest("h3 + *").prev().find('span b');
This makes the targeted element for .closest() generic, so that even if there is no parent, it will still work.
Example: http://jsfiddle.net/e27r8/4/
see http://api.jquery.com/prev/
var link = $("#me").parent("div").prev("h3").find("b");
alert(link.text());
see http://jsfiddle.net/gBwLq/
I know this is old, but was hunting for the same thing and ended up coming up with another solution which is fairly concise andsimple. Here's my way of finding the next or previous element, taking into account traversal over elements that aren't of the type we're looking for:
var ClosestPrev = $( StartObject ).prevAll( '.selectorClass' ).first();
var ClosestNext = $( StartObject ).nextAll( '.selectorClass' ).first();
I'm not 100% sure of the order that the collection from the nextAll/prevAll functions return, but in my test case, it appears that the array is in the direction expected. Might be helpful if someone could clarify the internals of jquery for that for a strong guarantee of reliability.
No, there is no "easy" way. Your best bet would be to do a loop where you first check each previous sibling, then move to the parent node and all of its previous siblings.
You'll need to break the selector into two, 1 to check if the current node could be the top level node in your selector, and 1 to check if it's descendants match.
Edit: This might as well be a plugin. You can use this with any selector in any HTML:
(function($) {
$.fn.closestPrior = function(selector) {
selector = selector.replace(/^\s+|\s+$/g, "");
var combinator = selector.search(/[ +~>]|$/);
var parent = selector.substr(0, combinator);
var children = selector.substr(combinator);
var el = this;
var match = $();
while (el.length && !match.length) {
el = el.prev();
if (!el.length) {
var par = el.parent();
// Don't use the parent - you've already checked all of the previous
// elements in this parent, move to its previous sibling, if any.
while (par.length && !par.prev().length) {
par = par.parent();
}
el = par.prev();
if (!el.length) {
break;
}
}
if (el.is(parent) && el.find(children).length) {
match = el.find(children).last();
}
else if (el.find(selector).length) {
match = el.find(selector).last();
}
}
return match;
}
})(jQuery);
var link = $("#me").closest(":has(h3 span b)").find('span b').text();

Can I make an element "unfindable" from the DOM by redefining the native functions?

Suppose I have the following structure:
<html>
<head></head>
<body>
<div id="myDiv"></div>
</body>
</html>
By redefining some native JavaScript functions, can I make myDiv unfindable?
For example, I can do:
window.HTMLDocument.prototype.getElementById = (function() {
var oldefinition = window.HTMLDocument.prototype.getElementById;
return function() {
var returnValue = oldefinition.apply(this, arguments);
if (returnValue && returnValue.id === 'myDiv') {
return oldefinition.call(this, 'blablabla');
} else {
return returnValue;
}
}
})();
and I can do the same for the other functions such as:
querySelector
querySelectorAll
getElementsByTagName
getElementsByClassName
etc.
This works, but the div is still available by calling:
document.body.children[0]
Then is there a way to make my div unfindable, that is, can I redefine the value of the children field?
Well, this is an attempt that seems to work okay - at least, with regards to document.body.children. I tested a variation of the following code on MDN's website to hide all script tags that are immediate children of the document body.
The way this works is we tell document.body to use a new property called children. We then return the original contents of children, minus the ones we don't want.
var oldchildren = document.body.children;
Object.defineProperty(document.body, 'children', {
get() {
var lst = [];
for (var item of oldchildren) {
if (!(item.tagName === "div" && item.id === 'myDiv'))
lst.push(item);
}
return lst;
}
});
Any code that references document.body.children after this code runs won't see the div. This code might cause other code on your site to misbehave.

Select element by tag/classname length

I'd like to select an element using javascript/jquery in Tampermonkey.
The class name and the tag of the elements are changing each time the page loads.
So I'd have to use some form of regex, but cant figure out how to do it.
This is how the html looks like:
<ivodo class="ivodo" ... </ivodo>
<ivodo class="ivodo" ... </ivodo>
<ivodo class="ivodo" ... </ivodo>
The tag always is the same as the classname.
It's always a 4/5 letter random "code"
I'm guessing it would be something like this:
$('[/^[a-z]{4,5}/}')
Could anyone please help me to get the right regexp?
You can't use regexp in selectors. You can pick some container and select its all elements and then filter them based on their class names. This probably won't be super fast, though.
I made a demo for you:
https://codepen.io/anon/pen/RZXdrL?editors=1010
html:
<div class="container">
<abc class="abc">abc</abc>
<abdef class="abdef">abdef</abdef>
<hdusf class="hdusf">hdusf</hdusf>
<ueff class="ueff">ueff</ueff>
<asdas class="asdas">asdas</asdas>
<asfg class="asfg">asfg</asfg>
<aasdasdbc class="aasdasdbc">aasdasdbc</aasdasdbc>
</div>
js (with jQuery):
const $elements = $('.container *').filter((index, element) => {
return (element.className.length === 5);
});
$elements.css('color', 'red');
The simplest way to do this would be to select those dynamic elements based on a fixed parent, for example:
$('#parent > *').each(function() {
// your logic here...
})
If the rules by which these tags are constructed are reliably as you state in the question, then you could select all elements then filter out those which are not of interest, for example :
var $elements = $('*').filter(function() {
return this.className.length === 5 && this.className.toUpperCase() === this.tagName.toUpperCase();
});
DEMO
Of course, you may want initially to select only the elements in some container(s). If so then replace '*' with a more specific selector :
var $elements = $('someSelector *').filter(function() {
return this.className.length === 5 && this.className.toUpperCase() === this.tagName.toUpperCase();
});
You can do this in vanilla JS
DEMO
Check the demo dev tools console
<body>
<things class="things">things</things>
<div class="stuff">this is not the DOM element you're looking for</div>
</body>
JS
// Grab the body children
var bodyChildren = document.getElementsByTagName("body")[0].children;
// Convert children to an array and filter out everything but the targets
var targets = [].filter.call(bodyChildren, function(el) {
var tagName = el.tagName.toLowerCase();
var classlistVal = el.classList.value.toLowerCase();
if (tagName === classlistVal) { return el; }
});
targets.forEach(function(el) {
// Do stuff
console.log(el)
})

jQuery function similar to closest that will return elements outside of the parent chain

Is there any jQuery function similar to closest() that will return elements outside of the parent chain, traversing sideways? For example, I want to call a function foo() on the div source that would return the div target. I know I could navigate using parent() and siblings(), but I need something generic that would go as many levels as needed, up, sideways and down?
var allsources = $('.source');
allsources.click(function()){
$(this).closest('.target').hide();
});
<div class="row">
<div>
<div class="target" ></div>
</div>
<div>
<div>
<div class="source"></div>
</div>
</div>
</div>
<div class="row">
<div>
<div class="target" ></div>
</div>
<div>
<div>
<div class="source"></div>
</div>
</div>
</div>
EDIT:
My definition of closest: you have an element source. Try to find it down. If find more than one, return one that is less node hoops down/next/prev. If not found, go one level up, and try to find again. Repeat until no parent.
If, by closest, you mean "travel up as little as possible, then anywhere downwards", then you can do
$("#source")
.closest(":has(.target)")
.find(".target:first") //make sure we only select one element in case of a tie
In your case, it would be better to specify the common parent directly:
$(this)
.closest(".row")
.find(".target") //there's no tie here, no need to arbitrate
This is a tricky one. As has been commented, how do you define closest in this context? Assuming you can decide on some rules; for example:
Traverse up: 3pt
Traverse down: 2pts
Move sideways: 1pts
And then consider the item with the lowest points to be "closest" then it would be easy enough to author a plugin, named something such as closestAll, which would do the recursive traversal of the whole dom tree to determine the closest item.
However, looking at your recent edit, one (of many!) right solutions to the problem stated is:
var allsources = $('.source');
allsources.click(function(){
$(this).parents('.row').find('.target').hide();
});
Live example: http://jsfiddle.net/zCvJM/ (Source A only hides Target A, Same for B)
If you know exactly the structure of the dom and level of nesting, have you consider to use the eq() method
$(this).parents().eq(1).prev().children(".target")
I don't think there is a way to do this other than basically querying the whole DOM:
$('#target')
Because if you want to go up and across (never mind down as well) then the target element isn't related to the child element. If you also want to check for the presence of the child element you will have to do that separately.
-Edit:
After reading your comment on wanting to find the closest element regardless of whether it is a parent, I think you will have to write a custom function to crawl back up the dom one node at a time. I have tested the following and it works:
Markup
<div id="parent">
<div id="child1">
<div id="source"></div>
</div>
<div id="child2">
<div class="target" rel="right"></div>
</div>
<div id="child3">
<div>
<div class="target" rel="wrong"></div>
</div>
</div>
</div>
Script
$(document).ready(function () {
var tgt = findClosest($('#source'), '.target');
if (tgt != undefined) {
alert(tgt.attr('rel'));
}
});
function findClosest(source, targetSel) {
var crawledNodes = $();
var target = null;
// Go up
source.parents().each(function () {
console.log(crawledNodes.index($(this)));
if (crawledNodes.index($(this)) == -1 && target == null) {
crawledNodes.add($(this));
target = findTarget($(this), targetSel);
// Go across
$(this).siblings().each(function () {
console.log("Sibling");
if (crawledNodes.index($(this)) == -1 && target == null) {
crawledNodes.add($(this));
target = findTarget($(this), targetSel);
}
});
}
});
return target;
}
function findTarget(el, targetSel) {
console.log(targetSel);
var target = el.find(targetSel);
if (target.size() > 0) {
return target.eq(0);
}
else
{
return null;
}
}
If I understood the specification correctly you mean something like the function closest defined below:
var allsources = $(".source");
function closest($source,selector) {
if($source == null) return $([]);
var $matchingChildren = $source.find(selector);
if($matchingChildren.length != 0) return $($matchingChildren.get(0));
else return closest($source.parent(), selector)
}
allsources.click(closest($(this),'.target').hide();});
You can see it working at http://jsfiddle.net/y2wJV/1/
Your definition requires that when choosing among matching children the function must return one that is less node hoops down/next/prev. This requirement has not been met, but this function is quite flexible and seems to do what you want to do in the case of the example you provided.
I found this code that is simple but does not solve the tie issue (returns the first)...
(function ($) {
$.fn.findClosest = function (filter) {
var $found = $(),
$currentSet = this; // Current place
while ($currentSet.length) {
$found = $currentSet.find(filter);
if ($found.length) break; // At least one match: break loop
// Get all children of the current set
$currentSet = $currentSet.parent();
}
return $found.first(); // Return first match of the collection
};
})(jQuery);
I encountered a similar problem, i had a table i needed to find the next element which may be outside the current td, so i made a jquery function:
$.fn.nextAllLevels = function(sel) {
if ($(this).nextAll(sel).length != 0) {
return $(this).nextAll(sel).eq(0);
} else if ($(this).nextAll(':has(' + sel + ')').length != 0) {
return $(this).nextAll(':has(' + sel + ')').find(sel).eq(0);
} else {
return $(this).parent().nextAllLevels(sel);
}
So to use this you simply call
$('#current').nextAllLevels('.target');
To give you the element closest in the foward direction, regardsless of whether in is in the current parent or not.

find ID of previous button

I'm looking to find the id of the previous button. It is pretty far away - lots of table rows, tables, divs, etc. between the target and the button but I thought this would still work:
alert( $(this).prevAll("input[type=button]").attr('id') );
Unfortunately this returns alerts 'undefined'. Help?
function getPrevInput(elem){
var i = 0,
inputs = document.getElementsByTagName('input'),
ret = 'Not found';
while(inputs[i] !== elem || i >= inputs.length){
if(inputs[i].type === 'button'){
ret = inputs[i];
}
i++;
}
return (typeof ret === 'string') ? ret : ret.id;
}
That probably isn't the most efficient solution, but it's the only one I can think of. What it does is goes through all the input elements and finds the one right before the one you passed into the function. You can use it like this, assuming you're calling it correctly and this is the input element:
getPrevInput(this);
Demo
That kind of lookup might be expensive. What about doing a select for all your input[type=button] elements, and traversing that array until you find the element matching your id. Then you can simply reference the array index - 1 to get your answer.
Is the previous button a sibling of the current button? If not, prevAll() won't work. The description of prevAll():
Get all preceding siblings of each element in the set of matched elements, optionally filtered by a selector.
Depending on your DOM structure, you can use a combination of parents() and then followed by find().
This function looks up all input[type=button] elements and uses the jQuery index function to find your current element in this group.
If it could be found and there is a previous element it is returned.
$.fn.previousElem = function(lookup){
var $elements = $(lookup),
index = $elements.index(this);
if(index > 0){
return $elements.eq(index-1)
}else{
return this;
}
}
HTML:
<div><div><div><div>
<input type=button id=1 value=1 />
</div></div></div></div>
<div><div><div><div>
<input type=button id=2 value=2 />
</div></div></div></div>
JS:
alert ($("#2").previousElem('input[type=button]').attr('id'))
http://jsfiddle.net/SnScQ/1/
Here's a different version of Amaan's code, but jqueryfied and his solution wasn't looking for a button. The key to the solution is that jQuery returns the elements in document order, as do document.getElementsByTagName and similar functions.
var button = $('#c');
var prevNode;
$("input[type=button]").each(function() {
if (this == button[0]) {
return false;
}
prevNode = this;
});
alert(prevNode && prevNode.getAttribute('id'));
http://jsfiddle.net/crFy6/
have you tried .closest? ...
alert( $(this).closest("input[type=button]").attr('id') );

Categories