CSS - Parent child selector not working - javascript

I have the following code:
.recipe
.ingredients
= f.simple_fields_for :ingredients do |ingredient|
= render 'ingredient_fields', f: ingredient
.row#links
.col-xs-12
= link_to_add_association "", f, :ingredients
%hr
I need to select the ingredients div using jquery in the format of $("#links")["closest"](".recipe > .ingredients") but this doesn't select anything.
It's frustrating though as $("#links")["closest"](".recipe > .row") will return the correct div.
Fiddle of what works and what I want: https://jsfiddle.net/yL6dr4s1/

According to jQuery documentation, closest method tries to find element matching the selector by testing the element itself and
traversing up through DOM.
It does not go through siblings of the element.
Based on your requirements, it seems like you want to traverse the tree for getting match in siblings. jQuery has siblings method to do that. So one solution would be to use siblings method like:
$("#links")["siblings"](".recipe > .ingredients")
Another soultion would be to get closest parent and then use children as answered by #mhodges
As for the query $("#links")["closest"](".recipe > .row"):
It works fine because closest method finds the match in the element itself.
Here is the example to showcase that:
$(document).ready(function() {
// Match found because it is parent
console.log($("#links")["closest"](".wrapper").length);
// No match found because element is sibling
console.log($("#links")["closest"](".row1").length);
// No match found because element is sibling
console.log($("#links")["closest"](".row3").length);
// Match found because it is element itself
console.log($("#links")["closest"](".row2").length);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div class="row1">
<span>Content1</span>
</div>
<div class="row2" id="links">
<span>Content2</span>
</div>
<div class="row3">
<span>Content3</span>
</div>
</div>

I am not sure of your requirements on using the exact selector/syntax you provided, but this selector works exactly how you want it to.
$(this).closest(".recipe").children(".ingredients").append('<br/><input type="text" value="Flour">');
Edit
This is the closest I could get:
$(this)["closest"](".recipe").children(".ingredients").append('<br/><input type="text" value="Flour">');

I don't think you can use the selectors in the way you propose.
As far as the DOM is concerned (and jQuery), the element defined by ingredient and the element defined by row are not related. You have to traverse up to the parent element, then back down to get to the child.
Here is a fiddle that hopefully demonstrates the issue.
If you can change it so that ingredient and row are both within the same parent div, you might have more luck with your test selector syntax.

When jQuery gets to buggy, doesn't have a certain option or just becomes to messy to use for a certain operation, it is good we also have access to good old plain javascript.
document.querySelector('#addToIngredients').addEventListener('click' , function(e) {
var recipe = getClosest(e.target,'recipe');
if (recipe) {
var ingred = recipe.querySelector('.ingredients');
ingred.innerHTML += '<br/><input type="text" value="Flour">';
}
});
function getClosest(elem,cls) {
var el = elem.parentNode;
while (el){
if (el.className.indexOf(cls) > -1) {
return el;
}
el = el.parentNode;
}
return false;
}
<div class="recipe">
<div class="ingredients">
<input type="text" value="Eggs"><br/>
<input type="text" value="Flour">
</div>
<div class="row">
<div class="col-xs-12">
Add to .ingredients
</div>
</div>
<hr/>
</div>
Of course they can be combined
$(function() {
$("#addToIngredients").on('click', function(e) {
var recipe = getClosest(e.target,'recipe');
if (recipe) {
var ingred = recipe.querySelector('.ingredients');
ingred.innerHTML += '<br/><input type="text" value="Flour">';
}
});
})

Related

How to select an element with jQuery using a variable in "contains" and remove the nearest el with class Foo

I'm trying to select an element using a variable in the selector to remove the nearest element with class flag-container. However, the element isn't removed when I trigger the function.
function remove_div() {
let comment_pk = 1
$("div:contains('" + comment_pk + "')").closest('.flag-container').remove()
}
$('#trigger').click(function() {
remove_div()
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div hidden class="comment-pk">1</div>
<div class="drilled-hierarchy">
<div class="flag-container">
To be removed
</div>
</div>
<button id="trigger"> Remove </button>
The issue in your logic is that closest() travels up the DOM along ancestor elements. The .flag-container you're looking to target is instead a child of a sibling to the original div.
Therefore you can use next()* and find() instead:
function remove_div() {
let comment_pk = 1
$("div:contains('" + comment_pk + "')").next('.drilled-hierarchy').find('.flag-container').remove()
}
$('#trigger').click(function() {
remove_div()
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div hidden class="comment-pk">1</div>
<div class="drilled-hierarchy">
<div class="flag-container">
To be removed
</div>
</div>
<button id="trigger">Remove</button>
*using a selector string argument in the next() call is optional in this case, but saves unexpected bugs later. siblings() may also be appropriate depending on your exact use case.
In vanilla JS, will only find exact matches:
[...document.querySelectorAll('div')]
.find(d=>d.textContent===String(comment_pk))
.nextElementSibling
.querySelector('.flag-container')
.remove();

How to delete current element if previous element is empty in jquery?

I want to delete element with class "tehnicneinfo" but only if the element I'm checking ( with class "h2size") has no child. I have a bunch of those elements, generated by a plugin and I want to delete only the ones that have the next element without child. I wrote jquery code, but it delets all of my elements, not only the ones that have the next element without child. Here is my jquery code:
$('.news .h2size > div').each(function() {
var ul = $(this).find('ul');
if(!ul.length) $(this).remove();
var h1 = $('.news').find('.tehnicneinfo');
var h2size = $('.news').find('.h2size');
if(h2size.prev().is(':empty'))
{
h1.remove();
}
});
this code is inside $(document).ready(function(). Can you tell me what I'm doing wrong? The code is for something else also, so I'm having truble only from var h1 = $('.news').find('.tehnicneinfo'); this line on. Thanks in advance!
Html:
<div class="news">
<h1 class="tehnicneinfo">xxx</h1>
<div class="h2size">
<div id="xyxyxy">
.......
</div>
</div>
<h1 class="tehnicneinfo">yyy</h1>
<div class="h2size"></div>
....
</div>
That's the html, only that there is like 20 more lines that are the same, but with different values (not yyy and xxx). I would need to delete all 'yyy' (they are not all with same value).
You can use filter to filter the ones you want to remove then remove them
"I want to delete only the ones that have the next element without child"
$('.tehnicneinfo').filter(function(){
return !$(this).next().children().length;
// only ones with next sibling with no children
}).remove();
JSFIDDLE

How to get sibling element (div) in Javascript?

So I have this HTML:
<div class="tip-box">
<div class="tip-title" onclick="toggleTip()">
<h2>Tip 1</h2>
</div>
<div class="tip-content hidden">
<p>Tip 1 content</p>
</div>
</div>
And this Javascript:
function toggleTip() {
$(this).siblings(".tip-content").toggleClass("hidden");
}
Hopefully it's obvious what this is supposed to do, but it doesn't work. Using .siblings() just doesn't seem to work in this way.
What's the correct solution for this? To get the next sibling of a certain type or with a certain class and then hide/show it?
You can use Jquery function.
<div class="tip-box">
<div class="tip-title">
<h2>Tip 1</h2>
</div>
<div class="tip-content hidden">
<p>Tip 1 content</p>
</div>
</div>
$(document).ready(function(){
$('.tip-title').click(function(){
$(this).siblings(".tip-content").toggleClass("hidden");
});
});
you can also use this
<div class="tip-box">
<div class="tip-title" onclick="toggloTip(this)">
<h2>Tip 1</h2>
</div>
<div class="tip-content hidden">
<p>Tip 1 content</p>
</div>
</div>
<script>
function toggloTip(elm) {
$(elm).siblings(".tip-content").toggleClass("hidden");
}
</script>
You can use pure javaScript with nextElementSibling property of node something like below,
I suppose you want do this operation with siblings.
function getChildrens(n, selector) {
var nodes = [];
while (n.nextElementSibling != null) {
if (n.nextElementSibling.hasOwnProperty('classList')) {
if (n.nextElementSibling.classList.contains(selector)) {
//return n.nextElementSibling;
nodes.push(n.nextElementSibling);
}
}
n = n.nextElementSibling;
}
return nodes;
};
function getSiblings(n, selector) {
return getChildrens(n, selector);
}
function toggleTip(elem) {
var siblings = getSiblings(elem, "tip-content");
if (siblings.length > 0) {
for (var i = 0; i < siblings.length; i++) {
siblings[i].classList.toggle("hidden");
}
}
}
.hidden {
display: none;
}
<div class="tip-box">
<div class="tip-title" onclick="toggleTip(this)">
<h2>Tip 1</h2>
</div>
<div class="tip-content hidden">
<p>Tip 1 content</p>
</div>
</div>
Here is another non JQuery answer.
To get the next element sibling use:
var nextElement = element.nextElementSibling;
To get the previous element sibling use:
var previousElement = element.previousElementSibling;
To get the element index use:
var index = Array.prototype.slice.call(element.parentElement.children).indexOf(element);
If you are at the first element the previousElementSibling value will be null.
If you are at the last element the nextElementSibling value will be null.
How about this JavaScript:
$(function(){
$('.tip-box').on('click', '.tip-title', function(){
$(this).next('.tip-content').toggleClass('hidden');
});
});
Remove the idea of working with onclick attributes when you use jQuery.
None of the previous answers, not even that serial-upvoted one ;), actually explains the problem and why their solutions work.
The problem is that an inline onclick handler does not pass on its current context. Inside the onclick="" JavaScript code this is the element clicked. Once you call a global function (like your toggleTip), that context is lost. The this the function receives is window and not the element.
The usual quick fix, for raw JavaScript code, is to pass this as a parameter to the global function.
e.g.
onclick="toggleTip(this)"
and receive a parameter in the function like this:
function toggleTip(element) {
$(element).siblings(".tip-content").toggleClass("hidden");
}
However, as you are using jQuery, inline event handlers are actually a bad idea. They separate the event registration from the event handler code for no reason and do not allow for multiple event handlers, of the same type, on the same element. They also bypass the rather cool event bubbling system jQuery uses.
The preferred alternative, with jQuery, is to use jQuery to select the element and jQuery to connect the event in one step:
jQuery(function($){
$('.tip-title').click(function(){
$(this).siblings(".tip-content").toggleClass("hidden");
});
});
As you only want the element that follows, and potentially will add more pairs, the better option would be using nextAll and first(), with the same jQuery filter, instead of siblings:
e.g.
jQuery(function($){
$('.tip-title').click(function(){
$(this).nextAll(".tip-content").first().toggleClass("hidden");
});
});
Or, of you can guarantee it is the next element, use next as #Tim Vermaelen did (with or without the selector makes no difference, so might as well leave it out):
jQuery(function($){
$('.tip-title').click(function(){
$(this).next().toggleClass("hidden");
});
});
Note: In this example jQuery(function($){ is a DOM ready event handler which is the rather handy shortcut version of $(document).ready(function(){YOUR CODE});, which also passes a locally scoped $ value. For those that mistake this code for an incorrect IIFE, here is a working example: http://jsfiddle.net/TrueBlueAussie/az4r27uz/

Find followers of an element in a certain scope, which aren't siblings

I am trying to select all following elements of an element I choose. They don't necessarily have to be direct siblings of my chosen Element, so .nextAll() won't work.
Here's an example:
<div class="scope">
<div> 1 </div>
<div> 2 </div>
<div> 3 </div>
<div> 4 </div>
</div>
NOT THIS
My element is a[href="2"], so I want to select a[href="3"] and a[href="4"], but not a[href="x"] because it's not in my scope.
I found this, but it only fetches one follower, but I need all of them.
I just wrote this, which works great, but it seems odd to me and I am sure that there have to be better solutions than this one:
var $two = $('a[href="2"]');
var selection = [];
var comes_after_2 = false;
$two.closest('.scope').find('a').each(function(){
console.log(this, $two.get(0));
if(comes_after_2){
selection.push(this);
}
if(this == $two.get(0)){
comes_after_2 = true;
}
});
$(selection).css('background', 'red');
Here is a Fiddle to test it: http://jsfiddle.net/mnff40fy/1/
Please feel free to modify it, if there's a better solution. Thank you!
var $all_a = $two.closest('.scope').find('a');
// Get the position of the selected element within the set
var a_index = $all_a.index($two);
// Select all the remaining elements in the set
var $followers = $all_a.slice(a_index+1);
$followers.css('background', 'red');
DEMO
How about this?
JSFiddle
I changed the markup a little to have the href='#' so you could click each one and see how the other elements respond.
$('a').click(function(){
$('a').css('background', 'none');
var scopeDiv = $(this).closest('div.scope');
var thisIndex = $(scopeDiv).find('a').index(this);
$(scopeDiv).find('a').not(this).each(function(index){
if(index >= thisIndex)
$(this).css('background', 'red');
});
});
As an alternative, you can use .nextAll() if you modify it a bit.
In your html code, you placed the a elements as children of the div tags. In order to incorporate .nextAll() you should select for the wrapper div elements and then call .nextAll() and then select for the children a elements.
Here is what I mean.
html
<div class="scope">
<div>
1
</div>
<!-- Start Here -->
<div class="start">
2
</div>
<div>
3
</div>
<div>
4
</div>
</div>
NOT THIS
js
$( '.start' ).nextAll().children( 'a' ).css( 'background-color', 'red' );
Explanation:
I select the wrapper div with $( '.start' )
I then select all of its subsequent siblings with .nextAll()
Of those siblings, I select their children that match 'a'
I apply the css
And here is the Fiddle

Finding child element by class from parent with pure javascript cross browser

Following my code:
<div onclick="/*Here I would like to select the child element with the class 'vxf'*/">
<div class="abc"></div>
<div class="cir"></div>
<!--... other elements-->
<div class="vxf"></div>
<!--... other elements-->
</div>
<div onclick="/*Here I would like to select the child element with the class 'vxf'*/">
<div class="abc"></div>
<div class="cir"></div>
<!--... other elements-->
<div class="vxf"></div>
<!--... other elements-->
</div>
How to select the child element with the class "vxf" with pure javascript?
Pass this into your handler...
onclick="clickHandler(this)"
...and then for maximum browser compatibility, just look in the child nodes:
function clickHandler(element) {
var child;
for (child = element.firstNode; child; child = child.nextSibling) {
if (child.className && child.className.match(/\bvxf\b/)) {
break; // Found it
}
}
// ...
}
(Or keep looping and build up an array, if you want all matching children.)
On most modern browsers, another alternative is to use querySelector (to find the first) or querySelectorAll (to get a list) of matching child elements. Sadly, this requires a bit of a trick:
function clickHandler(element) {
var child, needsId;
needsId = !element.id;
if (needsId) {
element.id = "TEMPID____" + (new Date()).getTime();
}
child = document.querySelector("#" + element.id + " > .vxf");
if (needsId) {
element.id = "";
}
// ...
}
We have to play the id game because we only want direct children (not descendants), and unfortunately you can't use a child combinator without something on the left of it (so element.querySelector("> .vxf"); doesn't work).
If you didn't care whether it was a direct child or a descendant, then of course it's a lot easier:
function clickHandler(element) {
var child = element.querySelector(".vxf");
// ...
}
Just use this.getElementsByClassName('vxf')[0] in the div's onclick, and you have the element. See this fiddle.
in HTML5 you can use document.querySelector('.vxf')
As pointed out in other answers you can also use document.getElementsByClassName('vxf') for this specific requirement but the document.querySelector() and document.querySelectorAll() methods allow you to provide more complex selectors and therefore give you more power, so worth looking at for future.
See here for more information.

Categories