How do I access a specific element in the inner HTML? - javascript

I am trying to use JS or jQuery to access an html value called "data-button". I can access the whole HTML div and pull out class name as well as the text content from the button, but I cant get the data-button value.
In the code below I have a captureRecipeButtons() function that can get the "recipe-1-container" div.
function captureRecipeButtons(){
let theWholeDiv = document.getElementsByClassName("recipe-1-container")[0];
let buttonValue = ?;
}
<div class="recipe-1-container">
<button class="listed-recipe-link" data-button="1">Element</button>
</div>
In my captureRecipeButtons() function I want buttonValue to equal "1" in my above code. Any help would be appreciated thanks.

You can use the full power of CSS selectors with querySelector:
function captureRecipeButtons(){
let button = document.querySelector(".recipe-1-container [data-button]");
}
querySelector returns the first matching element (or null if none do).
If you wanted the value of data-selector on that element, then getAttribute or dataset:
function captureRecipeButtons(){
let buttonValue = document.querySelector(".recipe-1-container [data-button]").getAttribute("data-button");
// or
let buttonValue = document.querySelector(".recipe-1-container [data-button]").dataset.button;
}
Live Copy:
function captureRecipeButtons(){
const button = document.querySelector(".recipe-1-container [data-button]");
console.log(button.getAttribute("data-button"));
// or
console.log(button.dataset.button);
}
captureRecipeButtons();
<div class="recipe-1-container">
<button class="listed-recipe-link" data-button="1">Element</button>
</div>
But note that dataset does some transformations.
But there are lots of different ways to do this. More to explore in the DOM.

So you could just get the button element by class or tag name as I have done. then data-button is legit just an attribute so just use getAttribute('data-button');
The way I've written below will just get the first button that is a direct child of theWholeDiv element.
function captureRecipeButtons(){
let theWholeDiv = document.getElementsByClassName("recipe-1-container")[0];
let buttonValue = theWholeDiv.getElementsByTagName('button')[0].getAttribute('data-button');
console.log(buttonValue);
}
captureRecipeButtons();
<div class="recipe-1-container">
<button class="listed-recipe-link" data-button="1">Element</button>
</div>

According to Mozilla Docs,
You can access the Data attributes via the dataset object.
function captureRecipeButtons(){
let theButton = document.querySelector("recipe-1-container > button");
let buttonValue = theButton.dataset.button;
}

Another way using the DOM, including some short-cuts to avoid excessive horizontal scrolling. The code precisely targets the first DIV element and its first BUTTON element, using the getAttribute() method to return the value of the indicated property. What is nice about JavaScript is the fabulous amount of chaining that one can do between parent and child elements.
function captureRecipeButtons() {
let d = document;
d.g = d.getElementsByTagName;
let buttonValue = d.g("div")[0].getElementsByTagName("button")[0].getAttribute("data-button");
console.log(buttonValue);
};
captureRecipeButtons();
<div class="recipe-1-container">
<button class="listed-recipe-link" data-button="1">Element</button>
</div>
Alternatively, you could write code as follows:
function captureRecipeButtons() {
let d = document;
d.g = d.getElementsByTagName;
let button = d.g("div")[0].childNodes[1];
button.g = button.getAttribute;
let buttonValue = button.g("data-button");
console.log(buttonValue);
}
captureRecipeButtons();
<div class="recipe-1-container">
<button class="listed-recipe-link" data-button="1">Element</button>
</div>
The DIV element's first child node as per the format of the code is not the BUTTON element but a text object. The childNode[1] holds the BUTTON element, so you can use its getAttribute() method to retrieve the value of the data-button attribute.

Related

How can i add same multiple html element with js?

I want to build a note taker app with html css and js but when i want add second note there is a problem.
let myNote = "";
let myTitle = "";
let noteInput = document.getElementById("note-input");
let titleInput = document.getElementById("title-input");
let title = document.getElementById("title");
let note = document.getElementById("first-note-p");
let addButton = document.getElementById("addButton");
let removeButton = document.getElementById("remove-button");
let newDiv = document.createElement("div");
let newP = document.createElement("p");
let newH3 = document.createElement("h3");
let newButton = document.createElement("button");
let notePlace = document.getElementById("note-place");
let button = document.getElementsByTagName("button");
let div = document.getElementsByTagName("div");
let paragrapgh = document.getElementsByTagName("p");
let head3 = document.getElementsByTagName("h3");
let tally = 0;
const addNote = () => {
myNote = noteInput.value;
myTitle = titleInput.value;
notePlace.appendChild(newDiv);
div[tally].appendChild(newH3);
div[tally].appendChild(newP);
div[tally].appendChild(newButton);
notePlace = document.getElementById("note-place");
head3[tally].innerText = myTitle;
paragrapgh[tally].innerText = myNote;
button[tally + 1].innerText = "remove";
tally += 1;
};
const removeNote = () => {
title.innerHTML = "";
note.innerHTML = "";
};
addButton.onclick = addNote;
<h1>Take your notes</h1>
<input id="title-input" onfocus="this.value=''" type="text" value="title" />
<input id="note-input" onfocus="this.value=''" type="text" value="note" />
<button id="addButton">add</button>
<div id="note-place"></div>
I use addNote function to add a new note but for second note I encounter to the following error.
Cannot set properties of undefined (setting 'innerText')
at HTMLButtonElement.addNote (notetaker.js:37:26)
Sorry for my bad English.
The main problem with your attempt is that you're selecting the elements before actually creating and appending them to the DOM and that will lead to problems because those elements that were initially selected are no longer there when a new note is added.
The fix is fairly easy, select the elements at the time you create a new note. Actually, I won't just stop here and I will happily invite you to follow along with my answer as we approach your task (of making notes and showing them in the screen) in a better approach that, i think, will be more helpful than just giving a fix.
So, here's what we're going to do, we're firstly go by tackling the task and see what are the main sub-tasks to do in order to have a working demo (with add and remove notes features):
To have a better performance, we'll select and cache the elements that we will use extensively in our task. Mainly, the element div#note-place should be cached because we're going to use many times when we add and remove notes.
The inputs, for the note title and text, the button that adds a note, those elements should be cached as well.
The main thing we will be doing is creating some elements and appending them to div#note-place so we can assign that sub-task to a separate function (that we will create). This function will create an element, add the wanted attributes (text, class etc...) then it returns that created element.
At this stage, our solution has started to take shape. Now, to create a note we will listen for the click event on the add note button and then we will have a listener that will handle the creation of the new note based on the values found on the inputs and then append that to the DOM. We will use addEventListener to attach a click event listener on the add note button (modern JS, no more onclicks!).
Now, for the remove note feature. The initial thinking that comes to mind is that we will listen for click events on the remover buttons and then do the work. This can work, but here's a better solution, Event Delegation, which basically allow us to have 1 listener set on div#note-place element that will call the remove note logic only when a remove button was clicked (see the code below for more info).
So, let's not take more time, the live demo below should allow you to easily understand what's being said:
/** cache the elemnts that we know we will use later on */
const notesContainer = document.getElementById('note-place'),
titleInp = document.getElementById('title-input'),
noteInp = document.getElementById('note-input'),
addNoteBtn = document.getElementById('add-note-btn'),
/** this class will be added to all remove note buttons This will allow us to catch clicks on those buttons using event delegation */
noteRemoverBtnClass = 'note-remover-btn',
/**
* a simple function that create an element, add the requested attribute and return the newly created element.
* tag: the tag name of the element to create (like div, h3 etc...).
* text: the text to show on the element (using textContent attribute).
* attributes: an object that holds "key: value" pairs where the keys are the attributes (like id, type etc...) and the values are the values for each attribute set on that parameter (see usage below).
*/
createElement = (tag, text, attributes) => {
const el = document.createElement(tag);
attributes = attributes || {};
!!text && (el.textContent = text);
for (let attr in attributes)
attributes.hasOwnProperty(attr) && el.setAttribute(attr, attributes[attr]);
return el;
};
/** listen for click events on the add note button */
addNoteBtn.addEventListener('click', () => {
/** create a div that will wrap the new note */
const noteEl = createElement('div');
/**
* create an "h3" for the note title, a "p" for the note text and a "button" that acts as the remove note button
* then loop through them and add them to the note wrapper that we just created
*/
[
createElement('h3', titleInp.value),
createElement('p', noteInp.value),
createElement('button', 'Remove', {
type: 'button',
class: noteRemoverBtnClass
})
].forEach(el => noteEl.appendChild(el));
/** append the entire note element (including the "h3", "p"p and "button" to "div#note-place" */
notesContainer.appendChild(noteEl);
});
/** implement event delegation by listening to click events on "div#note-place" and execute a set of logic (to remove a note) only when the clicked element is actually a remove button (thanks to "noteRemoverBtnClass" that we add to each created remove button) */
notesContainer.addEventListener('click', e => e.target.classList.contains(noteRemoverBtnClass) && e.target.parentNode.remove());
<h1>Take your notes</h1>
<input id="title-input" onfocus="this.value=''" type="text" value="title" />
<input id="note-input" onfocus="this.value=''" type="text" value="note" />
<button id="add-note-btn">add</button>
<div id="note-place"></div>
The above code sample is definitely NOT the only way to get things done, it only aims to be simple while recommending the use of some modern JS technics and logics. There always be more ways to do the task and even some better ways to do it.

get second element by class name if it exists

I'm trying to get the ID of an element by class name like this
var prod_id2 = document.getElementsByClassName('select-selected')[1].id
document.getElementById('hidden-input-2').value = prod_id2;
This works fine, but my issue is that if there's only one element with that class it breaks the functionality, so I need some sort of if statement to only define this var if there is a second div with that class.
Any ideas?
Try this:
const elements = document.querySelectorAll('.test');
if (elements[1]) {
elements[1].innerText = 'Hithere';
}
<div class="test">hi</div>
<div class="test">hi</div>
<div class="test">hi</div>
document.querySelectorAll('.test'); selects all elements with the class test and returns a nodelist.
Then we can access the second element via of the nodelist with elements[1].
Here is how to check for the second element.
You can also set another fallback , different to null:
document.addEventListener("DOMContentLoaded", function(event) {
var selectedElements = document.querySelectorAll('.selected-selected'),
prod_id2 = selectedElements[1] || null;
alert(prod_id2)
});
<div id="test" class="selected-selected"></div>
You can also check that value then:
if (prod_id2) { // do some other stuff with the set value }
It breaks the functionality I think because you are grabbing the 2nd element specifically. You can do:
const prod_id2 = document.querySelectorAll('.select-selected');
and loop over the elements and grab the ID
prod_id2.forEach((prod, index) => {
if(index === 2) {
document.getElementById('hidden-input-2').value = prod.id;
}
})

In JavaScript and HTML, how can I get the access to the <div> that the current button locates?

In HTML, I have
<div>
<button onclick="action()">button</button>
</div>
Without giving an ID or class to the div element, what can I do in JavaScript to get an access to it and use it?
Pass this into action:
<button onclick="action(this)">button</button>
and then in action
function action(btn) {
var div = btn.parentNode;
// ...
}
or if you want a bit more flexibility, use the (relatively-new) closest method:
function action(btn) {
var div = btn.closest("div");
// ...
}
Side note: Rather than onxyz-attribute-style event handlers, consider using modern event handling (addEventListener, attachEvent on obsolete browsers). If you have to support obsolete browsers, my answer here provides a function you can use to deal with the lack of addEventListener.
Grab the event then get the currentTarget then find the parentNode. Very simple way to do it. This will work no matter which element is clicked. Please see code snippet demonstration.
function getParentNode(event) {
console.log(event.currentTarget.parentNode);
}
<div id="div1"><button onclick="getParentNode(event)">Click</button></div>
<div id="div2"><button onclick="getParentNode(event)">Click</button></div>
using this in the onclick=action() and then the parent is .parentNode
then your method would look like :
<div>
<button onclick="action(this);">button</button>
</div>
function action(el) {
console.log(el.parentNode);
}
but I rather prefer .addEventListenerand for your next question then :
var $el = document.getElementById("a");
$el.addEventListener("click", function(e) {
// Create a <button> element
var btn = document.createElement("BUTTON");
// Create a text node
var t = document.createTextNode("CLICK ME");
// Append the text to <button>
btn.appendChild(t);
// Append <button> to the parentNode
this.parentNode.appendChild(btn);
});
<div>
<button id="a">Button</button>
</div>
Also in case you wonder for a shorter version and probably introducing to the jQuery :
;$(function(){
$("#a").on("click", function(e) {
$(this).parent().append('<button>Click Me</button>');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<button id="a">Button</button>
</div>
Another but not straight way to get the parent instead of .parentNode is .closest() method:
element.closest("div"); // also supported in jQuery
Returns the first ancestor of element, that is a <div> element:
If it is possible, pass this (button) to action. Then you can access parent by element.parentNode. Example: https://jsbin.com/fidufa/edit

Why is my javascript file not re-setting a div's attribute?

I have a script that is adding and removing a class to a couple divs when a link is clicked on. Each div has a set class that does not need to be removed. However, said class is being removed. How do I stop this from happening?
HTML
<div id="home" class="page pageShowing"></div>
<div id="portfolio" class="page"></div>
JS
let holder = document.getElementById("main");
let pageShowingClass = holder.getElementsByClassName("pageShowing");
let pages = holder.getElementsByClassName("page");
Navigation.Links.forEach(function(value){
let createNavLink = document.createElement("li");
let createNavText = document.createTextNode(value.title);
createNavLink.appendChild(createNavText);
createNavList.appendChild(createNavLink);
createNavLink.addEventListener("click", function(){
let link = createNavLink.innerHTML;
link = link.toLowerCase().replace(" ", "_");
let page = document.getElementById(link);
page.setAttribute("class", "page");
for(let i = 0; i < pageShowingClass.length; i++){
Here, the click handler should only be removing the pageShowing class
if it exists but is also removing the page class
if(pageShowingClass[i].getAttribute("class") == "pageShowing"){
pageShowingClass[i].removeAttribute("class");
}
}
Here, the click handler should be readding the page class when the
link is clicked on.
page.setAttribute("class", "page");
page.setAttribute("class", "pageShowing");
page.style.display = "block";
});
});
I know it's easier to do this in jQuery, but I don't want it to be in jQuery. I also already have it to where it will add and remove the pageShowing class dynamically, so that's not an issue.
As Siguza said in the reply, you're removing the class attribute, which is what you DON'T want to be doing in this case.
Let's put the element in question here for reference:
<div id="home" class="page pageShowing"></div>
class is an attribute of the element div. When you call removeAttribute('class'), it will do as it says:
<div id="home"></div>
If you check the element in chrome's dev tools or whatever you use, you'll be seeing the element as it says above.
You're probably looking for Element.className to modify your classes, so instead of
if(pageShowingClass[i].getAttribute("class") == "pageShowing"){
pageShowingClass[i].removeAttribute("class");
}
you'll want
if(pageShowingClass[i].getAttribute("class") == "pageShowing"){
pageShowingClass[i].className = "page";
}
and if you want to add the pageShowing class again, you'd just say pageShowingClass[i].className = "page pageShowing"
Element.setAttribute() adds a new attribute or changes the value of an existing attribute on the specified element.
Use Element.classList.add(String [, String]), adds specified class values. If these classes already exist in attribute of the element, then they are ignored.
page.classList.add('page', 'pageShowing')

Append multiple items in JavaScript

I have the following function and I am trying to figure out a better way to append multiple items using appendChild().
When the user clicks on Add, each item should look like this:
<li>
<input type="checkbox">
<label>Content typed by the user</label>
<input type="text">
<button class="edit">Edit</button>
<button class="delete">Delete</button>
</li>
and I have this function to add these elements:
function addNewItem(listElement, itemInput) {
var listItem = document.createElement("li");
var listItemCheckbox = document.createElement("input");
var listItemLabel = document.createElement("label");
var editableInput = document.createElement("input");
var editButton = document.createElement("button");
var deleteButton = document.createElement("button");
// define types
listItemCheckbox.type = "checkbox";
editableInput.type = "text";
// define content and class for buttons
editButton.innerText = "Edit";
editButton.className = "edit";
deleteButton.innerText = "Delete";
deleteButton.className = "delete";
listItemLabel.innerText = itemText.value;
// appendChild() - append these items to the li
listElement.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
if (itemText.value.length > 0) {
itemText.value = "";
inputFocus(itemText);
}
}
But you can notice that I am repeating three times the appendChild() for listItem. Is it possible to add multiple items to the appendChild() ?
You can do it with DocumentFragment.
var documentFragment = document.createDocumentFragment();
documentFragment.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
listElement.appendChild(documentFragment);
DocumentFragments allow developers to place child elements onto an
arbitrary node-like parent, allowing for node-like interactions
without a true root node. Doing so allows developers to produce
structure without doing so within the visible DOM
You can use the append method in JavaScript.
This is similar to jQuery's append method but it doesnot support IE and Edge.
You can change this code
listElement.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
to
listElement.append(listItem,listItemCheckbox,listItemLabel,editButton,deleteButton);
Personally, I don't see why you would do this.
But if you really need to replace all the appendChild() with one statement, you can assign the outerHTML of the created elements to the innerHTML of the li element.
You just need to replace the following:
listElement.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
With the following:
listItem.innerHTML+= listItemCheckbox.outerHTML + listItemLabel.outerHTML + editButton.outerHTML + deleteButton.outerHTML;
listElement.appendChild(listItem);
Explanation:
The outerHTML attribute of the element DOM interface gets the serialized HTML fragment describing the element including its descendants. So assigning the outerHTML of the created elements to the innerHTML of the li element is similar to appending them to it.
Merging the answers by #Atrahasis and #Slavik:
if (Node.prototype.appendChildren === undefined) {
Node.prototype.appendChildren = function() {
let children = [...arguments];
if (
children.length == 1 &&
Object.prototype.toString.call(children[0]) === "[object Array]"
) {
children = children[0];
}
const documentFragment = document.createDocumentFragment();
children.forEach(c => documentFragment.appendChild(c));
this.appendChild(documentFragment);
};
}
This accepts children as multiple arguments, or as a single array argument:
foo.appendChildren(bar1, bar2, bar3);
bar.appendChildren([bar1, bar2, bar3]);
Update – June 2020
Most all current browsers support append and the "spread operator" now.
The calls above can be re-written as:
foo.append(bar1, bar2, bar3);
bar.append(...[bar1, bar2, bar3]);
Let's try this:
let parentNode = document.createElement('div');
parentNode.append(...[
document.createElement('div'),
document.createElement('div'),
document.createElement('div'),
document.createElement('div'),
document.createElement('div')
]);
console.log(parentNode);
You need to append several children ? Just make it plural with appendChildren !
First things first :
HTMLLIElement.prototype.appendChildren = function () {
for ( var i = 0 ; i < arguments.length ; i++ )
this.appendChild( arguments[ i ] );
};
Then for any list element :
listElement.appendChildren( a, b, c, ... );
//check :
listElement.childNodes;//a, b, c, ...
Works with every element that has the appendChild method of course ! Like HTMLDivElement.
You can use createContextualFragment, it return a documentFragment created from a string.
It is perfect if you have to build and append more than one Nodes to an existing Element all together, because you can add it all without the cons of innerHTML
https://developer.mozilla.org/en-US/docs/Web/API/Range/createContextualFragment
// ...
var listItem = document.createElement("li");
var documentFragment = document.createRange().createContextualFragment(`
<input type="checkbox">
<label>Content typed by the user</label>
<input type="text">
<button class="edit">Edit</button>
<button class="delete">Delete</button>
`)
listItem.appendChild(documentFragment)
// ...
You could just group the elements into a single innerHTML group like this:
let node = document.createElement('li');
node.innerHTML = '<input type="checkbox"><label>Content typed by the user</label> <input type="text"><button class="edit">Edit</button><button class="delete">Delete</button>';
document.getElementById('orderedList').appendChild(node);
then appendChild() is only used once.
It's possible to write your own function if you use the built in arguments object
function appendMultipleNodes(){
var args = [].slice.call(arguments);
for (var x = 1; x < args.length; x++){
args[0].appendChild(args[x])
}
return args[0]
}
Then you would call the function as such:
appendMultipleNodes(parent, nodeOne, nodeTwo, nodeThree)
Why isn't anybody mentioning the element.append() function ?!
you can simply use it to append multiple items respectively as so:
listItem.append(listItemCheckbox, listItemLabel, editButton, deleteButton);
This is a quick fix
document.querySelector("#parentid .parenClass").insertAdjacentHTML('afterend', yourChildElement.outerHTML);
Guys I really recommend you to use this one.
[listItemCheckbox, listItemLabel, editButton, deleteButton]
.forEach((item) => listItem.appendChild(item));
Since you can't append multiple children at once. I think this one looks better.
Also here's a helper function that uses the fragment technique as introduced in the #Slavik's answer and merges it with DOMParser API:
function createHtmlFromString(stringHtml) {
const parser = new DOMParser();
const htmlFragment = document.createDocumentFragment();
const children = parser.parseFromString(stringHtml, "text/html").body
.children;
htmlFragment.replaceChildren(...children);
return htmlFragment;
}
Now to append multiple children with this, you can make the code much more readable and brief, e.g.:
const htmlFragment = createHtmlFromString(`<div class="info">
<span></span>
<h2></h2>
<p></p>
<button></button>
</div>
<div class="cover">
<img />
</div>
`);
Here's also a working example of these used in action: example link.
Note1: You could add text content in the above tags too and it works, but if it's data from user (or fetched from API), you'd better not trust it for better security. Instead, first make the fragment using the above function and then do something like this:
htmlFragment.querySelector(".info > span").textContent = game.name;
Note2: Don't use innerHTML to insert HTML, it is unsecure.
Great way to dynamically add elements to a webpage. This function takes 3 arguments, 1 is optional. The wrapper will wrap the parent element and it's elements inside another element. Useful when creating tables dynamically.
function append(parent, child, wrapper="") {
if (typeof child == 'object' && child.length > 1) {
child.forEach(c => {
parent.appendChild(c);
});
} else {
parent.appendChild(child);
}
if (typeof wrapper == 'object') {
wrapper.appendChild(parent);
}
}
I would like to add that if you want to add some variability to your html, you can also add variables like this:
let node = document.createElement('div');
node.classList.add("some-class");
node.innerHTML = `<div class="list">
<div class="title">${myObject.title}</div>
<div class="subtitle">${myObject.subtitle}
</div>`;

Categories