How can I get the child node based on clickevent - javascript

I have a have several divs on a page called parent_container inside it I have a heading, image and a button
how can I get the value for the specific heading of the container it was clicked from?
<div class="parent_container">
<img class="news_image" src="/" />
<h1 class="product_title">title 1</h1>
<a class="cta-btn" href="#">button1</a>
</div>
<div class="parent_container">
<img class="news_image" src="/" />
<h1 class="news_title">title 2</h1>
<a class="cta-btn" href="#">button2</a>
</div>
//getting the elements
var update_buttons = document.getElementsByClassName( 'cta-btn' ),
parentElement = document.getElementsByClassName( 'parent_container' ),
itemTitle = document.getElementsByClassName( 'news_title' );
//trying to get the title from the div it was clicked in
var getTitle = function( evt ) {
var title = this.itemTitle;
console.log( title ); //undefined
}
//setting up a event listener on all the buttons on the page
for( var i=0;i<update_buttons.length;i++ ){
update_buttons[i].addEventListener('click', getTitle);
}

Get the clicked element using evt.toElement (In this case, this works too, though).
From there, get the parent node, and then select the child h1 element.
Access the text using textContent:
var getTitle = function (evt) {
var el = evt.toElement, // or this
parent = el.parentNode,
header = parent.querySelector('h1[class*="title"]'),
headerText = header.textContent;
console.log(headerText);
}
Example Here
..and if you prefer not to cache variables:
evt.toElement.parentNode.querySelector('h1[class*="title"]').textContent;
//getting the elements
var update_buttons = document.getElementsByClassName('cta-btn'),
parentElement = document.getElementsByClassName('parent_container'),
itemTitle = document.getElementsByClassName('news_title');
//trying to get the title from the div it was clicked in
var getTitle = function (evt) {
var el = evt.toElement,
parent = el.parentNode,
header = parent.querySelector('h1[class*="title"]'),
headerText = header.textContent;
console.log(headerText);
}
//setting up a event listener on all the buttons on the page
for (var i = 0; i < update_buttons.length; i++) {
update_buttons[i].addEventListener('click', getTitle);
}
<div class="parent_container">
<img class="news_image" src="/" />
<h1 class="product_title">title 1</h1>
<a class="cta-btn" href="#">button1</a>
</div>
<div class="parent_container">
<img class="news_image" src="/" />
<h1 class="news_title">title 2</h1>
<a class="cta-btn" href="#">button2</a>
</div>

Solution with the least steps. It seams that your title is always the previous element of the button.
Use:
var getTitle = function( evt ) {
var title = this.previousElementSibling.textContent || this.previousElementSibling.innerText;
console.log( title ); //undefined
}
To be sure that you always return the correct element you need a bit more than the sample above. Why am I saying this. If you (or someone else) edits the html the solution above could break. A better solution is to give the title element a class name or better an attribute that is only used for those titles. Take a look at this solution:
<div class="parent_container">
<img class="news_image" src="/" />
<h1 title-element class="news_title">title 2</h1>
<a class="cta-btn" href="#">button2</a>
</div>
var getTitle = function( evt ) {
var title = this.parentElement.querySelector("h1[title-element]");
return title.textContent || title.innerText;
}
The above answer makes it less likely that your function breaks when the html gets updated in the future.
PS. The || element.innerText is a fall back for older browsers (read IE) that don't support textContent.

First you got a typo: console.log(title). And here is what you need to do, get the parent, then the child for the header:
var title = this.parentNode.children[1].innerHTML;
console.log( title );
Example Here
Note this assumes that you have the same structure for the <div class="parent_container"> item, such that the title is the second item.
Edit
If the structure is changed you can select the item with querySelector('h1'):
var title = this.parentNode.querySelector('h1').innerHTML;
console.log( title );
As #JoshCrozier proposed you can use .querySelector('h1[class*="title"]') which means it selects some class with the word "title" in it. So product_title, news_title, something_title will work. That way you can have other <h1> elements in the <div> as well if you happen to want to add them (you just have to make sure they don't have a class with the word "title" in those).

Related

Iteratively print JavaScript dictionary of objects into HTML divs

I'd appreciate some help with iteratively generating the beneath div based on the amount of items in a Javascript dictionary.
<div class="container" style="padding-bottom: 10px;">
<div class="dropdown" style="padding: 10px;">
<a href="#">TOP 3 PS5 HEADSETS<i class="fa fa-chevron-down"></i>
</a>
<ul>
<div id="links">
<center>
<p>3: ↓ INSERT TITLE FOR STEELSERIES ↓</p>
</center>
<div class="product">
<img src="img/products/h-steelseries.png">
<a class="link" href="INSERT LINK HERE">Read More</a>
</div>
<center>
<p>3: ↓ INSERT TITLE FOR OTHER↓</p>
</center>
<div class="product">
<img src="img/products/h-other.png">
<a class="link" href="INSERT LINK HERE">Read More</a>
</div>
</div>
</ul>
</div>
</div>
Beneath is the read.js file that contains the items in which I wish to generate the div class "product" for.
I'd really apprecaite any help with this.
var prod_obj = {
"headphone_products" : {
"title": "Steelseries",
"IMAGE": "h-steelseries.png",
"HREF" : "steelseries.html"
},
"other_products" : {
"title": "Other product",
"IMAGE": "h-other.png",
"HREF" : "other.html"
}
};
I have looked at other answers and couldn't find an example of a dictionary of object that was used to automatically generate divs. I intend on using this to list items on a website and would like to append objects to the dictionary and them to automatically generate a new div for each object once the script is executed.
Thank you for your time.
You can simply loop over the object and create the desired nodes inside the loop.
Here's a simpler version of the same.
var prod_obj = {
"headphone_products": {
"title": "Steelseries",
},
"other_products": {
"title": "Other product",
}
};
for (let keys in prod_obj) {
const div = document.createElement("div");
div.innerText = prod_obj[keys].title
document.body.appendChild(div)
}
You can use for-in loops and template literals to achieve what you want to achieve here.
const prod_obj = {
"headphone_products": {
"title": "Steelseries",
"image": "h-steelseries.png",
"href": "steelseries.html"
},
"other_products": {
"title": "Other product",
"image": "h-other.png",
"href": "other.html"
}
};
const div = document.getElementById('insertHere');
for (let products_key in prod_obj) {
let {title, image, href} = prod_obj[products_key];
let html = `<p>Title: ${title}, Image: ${image}, href: ${href}</p>`;
div.insertAdjacentHTML('beforeend', html);
}
<div id="insertHere">
</div>
What you describe sounds like a suitable candidate for a template which, according to the documentation on MDN says:
The HTML Content Template () element is a mechanism for
holding HTML that is not to be rendered immediately when a page is
loaded but may be instantiated subsequently during runtime using
JavaScript.
The following uses a simple class to load a new instance of the designated template for each product found within the source data ( what you refer to as a dictionary ). Once the template has been loaded from the shadows you can manipulate the contents as you wish. If you change the design of the template you change the design of the final layout. In the original HTML there is no span element surrounding the individual products but the way I wrote the template loader( for a specific job ) clones the first child element entirely - so a span will not affect layout unless styled specifically to do so.
class TemplateLoader{
constructor( id ){
this.id=id;
return this.create();
};
gettemplate(){
return document.querySelector( 'template[ data-id="'+this.id+'" ]' ) || false
};
clone(){
let tmpl=this.gettemplate();
return tmpl ? tmpl.content.firstElementChild.cloneNode( true ) : false;
};
gettarget(){
return document.querySelector( 'div[ id="'+this.id+'" ]' ) || false;
};
create(){
let tmpl=this.clone();
if( tmpl ){
let target=this.gettarget();
target.appendChild( tmpl );
return tmpl;
}
return false;
};
};
var prod_obj = {
'headphone_products' : {
'title': 'steelseries',
'image': 'h-steelseries.png',
'href' : 'steelseries.html'
},
'other_products' : {
'title': 'other product',
'image': 'h-other.png',
'href' : 'other.html'
},
'banana':{
'title':'curvy & yellow',
'image':'b-a-nana.png',
'href':'banana.html'
}
};
let id='links';
Object.keys( prod_obj ).forEach( cat => {
let data=prod_obj[ cat ];
let oTmpl=new TemplateLoader( id );
oTmpl.querySelector('center > p').textContent=data.title;
oTmpl.querySelector('div.product > img').src=['img/products',data.image].join('/');
oTmpl.querySelector('div.product > a.link').href=data.href;
});
<!-- regular HTML -->
<div class='container'>
<div class='dropdown'>
<a href='#'>TOP 3 PS5 HEADSETS<i class='fa fa-chevron-down'></i></a>
<ul>
<div id='links'>
<!-- items will be populated here -->
</div>
</ul>
</div>
</div>
<!-- our template that will be used to generate new content within the above, regular HTML' -->
<template data-id='links'>
<span>
<center>
<p></p>
</center>
<div class='product'>
<img />
<a class='link'>Read More</a>
</div>
</span>
</template>
The traditional way to add content to the DOM on-the-fly is to use a series of calls to createElmenent and appendChild (which is less error-prone than just inserting HTML strings). And you can loop through your data object's keys and extract the details you need to configure your new DOM elements. This script does both of these things in a function called updateDOM, which invokes the appendProductDetails function once per product.
I changed the hrefs to create functional (if arbitrary) links, and of course the images don't show up because they don't exist on StackOverflow's server. See the in-code comments for further explanation.
const currentProds = getProductsToShow();
updateDOM(currentProds);
function updateDOM(prod_obj) {
// Identifies parent div
const linksDiv = document.getElementById("links");
// Clears parent div
linksDiv.innerHTML = "";
// Loops through productName (keys) in prod_obj
const productNames = Object.keys(prod_obj);
for (let productName of productNames) {
// Gets details (inner object) for each product
const details_obj = prod_obj[productName];
// Creates, configures, and appends new elements for each product
appendProductDetails(linksDiv, details_obj);
}
}
function appendProductDetails(parentElement, detailsObject) {
const
// Gets local copies of values via "destructuring"
{ title, image, href } = detailsObject,
path = "img/products/", // Defines path to images
// Creates elements to add to the DOM
productDiv = document.createElement("div"),
titleP = document.createElement("p"),
img = document.createElement("img"),
anchor = document.createElement("a");
// Configures newly created elements
productDiv.classList.add("product");
titleP.textContent = title;
img.src = path + image;
img.alt = image;
anchor.classList.add("link");
anchor.href = href;
anchor.textContent = "Read More";
// Puts children into productDiv
productDiv.appendChild(titleP);
productDiv.appendChild(img);
productDiv.appendChild(anchor);
// Attaches everything to the DOM
parentElement.appendChild(productDiv);
}
// Provides demo data
function getProductsToShow() {
const productsObj = {
"headphone_products": {
"title": "Steelseries",
"image": "h-steelseries.png", // In img/products/
"href": "https://stackoverflow.com"
},
"other_products": {
"title": "Other product",
"image": "h-other.png",
"href": "https://eloquentjavascript.net/"
}
};
return productsObj;
}
.container{ width: 250px; text-align: center; }
.dropdown > a{ text-decoration: none; }
p{ margin: -0.1rem 0; font-size: 1.2rem; }
.product{ padding: 0.5rem ; }
.link{ margin-left: 1rem; }
<div class="container">
<div class="dropdown">
PS5 HEADSETS
<div id="links"></div>
</div>
</div>
(A more modern approach would be to repeatedly clone the contents of a template element and to use slot elements to insert corresponding product details into each new instance.)

passing this.id to another function returns null error

I am trying to pass an ID with a function to another function. The console then informs me that there is a TypeError: document.getElementById(...) is null. The javascript file is appended at the end and I also tried adding the code into a self executing function, but that didn't solve the problem.
Basically I would like to an addEventListener to the sub-category divs (buttons) and they should pass their value to another function.
The error is pointing at this line let mainCategory = document.getElementById(recived_value).parentNode.firstChild(this.id); so recived_value is null.
Any help would be welcome. Thank you.
HTML:
<div class="wrapper">
<div class="main-category" id="box">
Boxes
</div>
<div class="sub-category" id="b_small">
Small Boxes
</div>
<div class="sub-category" id="b_medium">
Medium Boxes
</div>
<div class="sub-category" id="b_large">
Large Boxes
</div>
</div>
JS:
var subCategoryClass = document.querySelectorAll(".sub-category");
var subCategoryArray = Array.from(subCategoryClass);
for ( let i = 0; i < subCategoryArray.length; i++ ){
subCategoryArray[i].addEventListener("click", PassValue(this.id));
}
function PassValue(recived_value){
let subCategory = recived_value;
let mainCategory = document.getElementById(recived_value).parentNode.firstChild(this.id);
TwoArgFunc(mainCategory, subCategory);
}
There were a couple of problems. I described them below in // comments.
Edit: Based on your comment, I changed to a normal function definition.
var subCategoryClass = document.querySelectorAll(".sub-category");
var subCategoryArray = Array.from(subCategoryClass);
for ( let i = 0; i < subCategoryArray.length; i++ ){
subCategoryArray[i].addEventListener("click", function(ev) { PassValue(ev.target.id)} ); // need a function here, not just a statement
}
function PassValue(recived_value){
console.log(recived_value);
let subCategory = recived_value;
let mainCategory = document.getElementById(recived_value).parentNode.firstElementChild.id; // use firstElementChild because firstChild is a newline text node
console.log(mainCategory);
// TwoArgFunc(mainCategory, subCategory);
}
<div class="wrapper">
<div class="main-category" id="box">
Boxes
</div>
<div class="sub-category" id="b_small">
Small Boxes
</div>
<div class="sub-category" id="b_medium">
Medium Boxes
</div>
<div class="sub-category" id="b_large">
Large Boxes
</div>
</div>
Above PassValue function put:
var that = this;
And then in your PassValue function pass that.id:
... .firstChild(that.id);

How can I add the same XML tags multiple times, with different content?

I have some problems with my code. I want to create an XML Document with JQuery / JavaScript. I am now at the point, where I want to create a few Tags and populate them each with the same tags but different content inside the tags.
Here is the code for better understand
function setItems(xmlDoc, channelTag){
const itemList = [];
const itemTitle = xmlDoc.createElement("title");
const itemLink = xmlDoc.createElement("link");
const itemGuid = xmlDoc.createElement("guid");
const itemMediaContent = xmlDoc.createElement("media:content");
const itemMediaDescription = xmlDoc.createElement("media:description");
itemList.push(itemTitle, itemLink, itemGuid, itemMediaContent, itemMediaDescription);
for (var i = 0; i < jsonObj.length; i++){
var item = xmlDoc.createElement("item");
channelTag.appendChild(item);
//Populate the <item> with the tags from "itemList" and content from "jsonObj"
$.each(itemList, function(index) {
$(channelTag).children('item')[i].appendChild(itemList[index]).textContent = jsonObj[0].title;
})
}
}
The Output of the code looks like this:
<item></item>
<item></item>
<item>
<title>Something</title>
<guid>Something</guid>
<link>Something</link>
<media:content>Something</media:description>
<media:description>Something</media:description>
</item>
It always populates the last item-Tag but not the ones above. What I want is that every item-Tag has the same child-Tags (e.g. title, link, guid and so on). Is there something i am missing some unique tags or something like that?
Edited:
Here is some minimal HTML and XML. The values for the function "xmlDoc" and "channelTag" just contains some Document Elements, where my items should be appended, like so:
<rss>
<channel>
<title>SomeTitle</title>
<atom:link href="Link" rel="self" type="application/rss+xml"/>
<link>SomeLink</link>
<description>SomeDesc</description>
<item></item>
<item></item>
<item></item>
</channel>
</rss>
<div class="col-5 col-sm-5 col-lg-3 order-2 count">
<a class="guid1"><img class="card-img image1"></a>
</div>
<div class="col-7 col-sm-7 col-lg-5 order-2">
<div class="card-body">
<a class="guid1">
<h5 class="card-title title1 overflow-title"></h5>
</a>
<p class="card-text body1 text-body overflow-body"></p>
<div class="card-body subtitle">
</div>
</div>
</div>
There are several issues with your code but the area we mostly want to focus on is this:
for (var i = 0; i < jsonObj.length; i++){
var item = xmlDoc.createElement("item");
channelTag.appendChild(item); // you're adding a node here
$.each(itemList, function(index) {
$(channelTag).children('item')[i].appendChild(... // and here
})
}
Instead of appending nodes multiple times per iteration, you should create and populate your node before add it it to channelTag.
Here's a way your could do it:
// use a "$" sign as a variable name prefix, so you know it's a Document Element and not a regular javascript variable
var $item = xmlDoc.createElement("item");
// you don't need jQuery for this iteration
itemList.forEach(function (item, index) {
$item.appendChild(itemList[index]).textContent = jsonObj[0].title;
});
// if "channelTag" is a Document Element, rename it "$channelTag"
$channelTag.appendChild(item);
Couple things about the code above:
you don't need jQuery, use forEach instead
there is no way telling what type is channelTag. If it is a selector (of type string), use $(selector), but you are using the appendChild() method before, suggesting it's actually a Document Element. In that case you don't need to wrap it with $()
I don't have the context needed to test this code, so no guarantee it'll work out of the box. But try and re-read your code and go through it top-to-bottom. For each variable, describe its type and value. I found that to be helpful when I'm lost in code.

Find next DOM Object that exists in an Array

So, I have created an array of all instances of certain classes.
anchors = [];
$('.a-all').each(function() {
anchors.push($(this));
});
if ( viewport().width > 1366 ) {
sub_anchors = $('.a-lg');
} else if ( viewport().width > 1024 ) {
sub_anchors = $('.a-md');
} else if ( viewport().width > 768 ) {
sub_anchors = $('.a-sm');
} else {
sub_anchors = $('.a-xs');
}
sub_anchors.each(function() {
anchors.push($(this));
});
Then I set a variable 'current' and made it the object with the class '.active'.
current = $('.active');
Now, with jQuery, I want to be able to find the next and previous DOM object relative to .active that exists inside the array I have created.
The array is not in order, and will change at different widths.
Is this possible, or is there a better logic to use here?
EDIT: Adding markup for context.
<div class="website-wrapper w-d-100 h-d-100">
<div class="page-wrapper">
<section id="landing-slider" class="a-all active">
<div class="w-d-100 h-d-100">
This is the homepage landing slider... thing.
</div>
</section>
<section id="about" class="a-all">
<div class="w-d-100 h-d-50 w-sm-75 h-sm-100 dark">
About Panel 1 (75)
</div>
<div class="w-d-100 h-d-50 w-sm-25 h-sm-100">
About Panel 2 (25)
</div>
</section>
<section id="clients" class="a-all">
<div class="w-d-100 h-d-50 w-sm-50 h-sm-100">
Clients Panel 1 (50)
</div>
<div class="w-d-100 h-d-50 w-sm-50 h-sm-100 dark">
Clients Panel 2 (50)
</div>
</section>
<section id="services" class="a-md">
<section class="a-sm">
<div class="w-d-100 h-d-100 w-sm-50 h-sm-100 dark">
Services Panel 1 (50)
</div>
</section>
<section class="a-sm">
<div class="w-d-100 h-d-100 w-sm-50 h-sm-100">
Services Panel 2 (50)
</div>
</section>
</section>
<section id="lets-work" class="a-all">
<div class="w-d-100 h-d-100 dark">
Lets work together! (100)
</div>
</section>
</div>
</div>
Updated answer (now you've shown your HTML)
Since your .a-all elements are siblings (sometimes non-adjacent), you can use prevAll and nextAll, no need for the anchors array at all:
var next = $(".active")..nextAll(".a-all").first();
// or
var previous = $(".active").prevAll(".a-all").first();
If you want to find a .a-md or .a-sm, just use that as the prevAll/nextAll selector.
Original answer
Now, with jQuery, I want to be able to find the next and previous DOM object relative to .active that exists inside the array I have created.
It would be easier if you didn't make an array out of your initial jQuery object. Instead, just remember the object:
var anchors = $(".a-all");
Later, if you want to know where an element is in that array, you can use index(element):
var index = anchors.index($(".active")[0]);
Then you can get the previous like this:
var prev = index > 0 ? anchors.eq(index - 1) : $();
...or the next like this:
var next = index < anchors.length - 1 ? anchors.eq(index + 1) : $();
But if you want to use an array of jQuery instances (like the one you built) instead, you can use findIndex:
var anchors = $(".a-all").map(function() { return $(this); }).get();
// ...
var active = $(".active")[0]; // Note the [0] to get raw element
var index = anchors.findIndex(function(entry) {
return entry[0] === active;
});
// ...
var prev = index > 0 ? anchors[index - 1] : $();
// ...
var next = index < anchors.length - 1 ? anchors[index + 1] : $();

Get values from elements inside DIV

I need to get values inside div on clicking a button, wich locates inside this div. Here is the html structure:
<div class="products__item">
<div class="products__content">
<a class="products__title" href="#">Altec Lansing Octiv Duo M202 акустическая система акустическая система</a>
<div class="products__priceholder">
<p class="products__price"><strong>86 590</strong> руб.</p>
<small class="products__id">ID. 10906</small>
</div>
<p class="exist">В наличии</p>
<div class="products__buttonholder">
Купить
</div>
</div>
</div>
When I click on .button-buy-modal, I need to get values from .products__title .products__price and .products__id, but the problem is that we have a lot same div's (product cards), and a lot of buttons inside them. I think that I should use something like $(this), but actually I don't know how.
I'm trying to test something like this, but it doesn't work:
$("a.button-buy-modal").click(function () {
$(this).find().closest('.products__priceholder').addClass('test1');
})
Here is a solution:
$("a.button-buy-modal").click(function () {
var prTitle = $(this).parent().parent().children('.products__title').html();
var prPrice = $(this).parent().parent().children('.products__priceholder').children('.products__price').children('strong').html();
var prId = $(this).parent().parent().children('.products__priceholder').children('.products__id').html();
var prImage = $(this).parent().parent().parent().children('.products__imageholder').children('.products__thumbnail').attr('src');
console.log(prTitle);
console.log(prPrice);
console.log(prId);
console.log(prImage);
})
$(this).parent().parent().find('.products__price').text()
will give you this - "86 590 руб."
$(this).parent().parent().find('.products__price').html()
will give you this -- "<strong>86 590</strong> руб."
To learn more about it as in how you can select any particular DOM element and read it or manipulate it etc, read here - http://api.jquery.com/category/selectors/
You can add an id to your parent div or use eq(x) to select the right group.
<div id="mydiv1" class="products__item">
<div class="products__content">
<a class="products__title" href="#">Altec Lansing Octiv Duo M202 акустическая система акустическая система</a>
<div class="products__priceholder">
<p class="products__price"><strong>86 590</strong> руб.</p>
<small class="products__id">ID. 10906</small>
</div>
<p class="exist">В наличии</p>
<div class="products__buttonholder">
Купить
</div>
</div>
</div>
Then with jQuery:
var title = $('#div1 .products__title').html();
/// OR
var title = $('.products__item:eq(0) .products__title').html();
var price = $('#div1 .products__price').html();
/// OR
var price = $('.products__item:eq(0) .products__price').html();
price = price.replace(/<[^>]+>/g, ""); // regex to remove tags
EDIT
If you want to use .closest(), you don't need .find()
$("a.button-buy-modal").click(function () {
$(this).closest('.products__priceholder').addClass('test1');
});
Thank's all for advices, I found a solution:
$("a.button-buy-modal").click(function () {
var prTitle = $(this).parent().parent().children('.products__title').html();
var prPrice = $(this).parent().parent().children('.products__priceholder').children('.products__price').children('strong').html();
var prId = $(this).parent().parent().children('.products__priceholder').children('.products__id').html();
var prImage = $(this).parent().parent().parent().children('.products__imageholder').children('.products__thumbnail').attr('src');
console.log(prTitle);
console.log(prPrice);
console.log(prId);
console.log(prImage);
})

Categories

Resources