Add class to first child using javascript - javascript

is there any reason this chain does not work? It does not add the class:
document.getElementsByTagName('nav')[0].firstChild.className = "current"
It should return the first child of the nav element which is an <a> which does not happen.
Thanks for your help!

That's because you have text nodes between nav and a. You can filter them by nodeType:
var childNodes = document.getElementsByTagName('nav')[0].childNodes;
for (var i = 0; i < childNodes.length; i++) {
if (childNodes[i].nodeType !== 3) { // nodeType 3 is a text node
childNodes[i].className = "current"; // <a>
break;
}
}
It may seem strange but, for example, if you have the following markup:
<nav>
<a>afsa</a>
</nav>
Here's a DEMO.
Why does this happen? Because some browsers may interpret the space between <nav> and <a> as an extra text node. Thus, firstChild will no longer work since it'll return the text node instead.
If you had the following markup, it'd work:
<nav><a>afsa</a></nav>

You can simply document.querySelectorAll to select the list.
use "firstElementChild" to get first child node and add class.
const firstChild = document.querySelectorAll('nav').firstElementChild;
firstChild.classList.add('current');

The statement:
document.getElementsByTagName('nav')[0].firstChild.className = "current"
is somewhat fragile as any change in the assumed document structure breaks your code. So more robust do do something like:
var links,
navs = document.getElementsByTagName('nav');
if (navs) links = nav[0].getElementsByTagName('a');
if (links) links[0].className = links[0].className + ' ' + 'current';
You should also have robust addClassName and removeClassName functions.

Jquery can make this very easy:
$("#nav:first-child").addClass("current");

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();

Get an element's value without an id

I've got following HTML:
<span class="testClass1" >
wanted Text
<a class="ctx" href="#"></a>
</span>
Now I want to get the text "wanted Text".
How can I achieve this?
I tried with:
document.getElementsByClassName("testClass1");
I also tried with document.getElementsByTagName() but I don't know how to use them properly.
You can use querySelectorAll
hence:
document.querySelectorAll('.testclass1 a')
will return all the <a> items children of a .testclass1
Snippet example:
var elements = document.querySelectorAll('.testClass1 a')
console.log(elements) // open the console to see this
console.log(elements[0].text) // this gets the first <a> text `wanted Text`
<span class="testClass1" >
wanted Text
<a class="ctx" href="#"></a>
</span>
The getElementsByClassName() function returns an array of matching elements, so if you need to access them, you could do so using a loop :
// Get each of the elements that have the class "testClass1"
var elements = document.getElementsByClassName("testClass1");
// Iterate through each element that was found
for(var e = 0; e < elements.length; e++){
// Get the inner content via the innerHTML property
var content = elements[e].innerHTML;
}
If you need to actually access the <a> tags directly below some of the elements as your edit indicates, then you could potentially search for those wihtin each of your existing elements using the getElementsbyTagName() function :
// Get each of the elements that have the class "testClass1"
var elements = document.getElementsByClassName("testClass1");
// Iterate through each element that was found
for(var e = 0; e < elements.length; e++){
// Find the <a> elements below this element
var aElements = elements[e].getElementsByTagName('a');
// Iterate through them
for(var a = 0; a < aElements.length; a++){
// Access your element content through aElements[a].innerHTML here
}
}
You can also use an approach like squint's comment or Fred's which take advantage of the querySelectorAll() function as the getElementsByClassName() and getElementsByTagName() are better served when accessing multiple elements instead of one specifically.
Try this:
document.getElementsByClassName("testClass1")[0].getElementsByTagName('a')[0].innerText
var testClass1 = document.getElementsByClassName("testClass1");
console.log(testClass1[0].innerHTML);

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>`;

Select only the root item of a html node

I have this html code.
<div class="breadcrumb">
Home
<a class="breadcrumb" href="#">About</a>
<a class="breadcrumb" href="#">History</a>
Message from our Founding Members
</div>
Using javascript I want to get the text from the div ".breadcrumb". The problem is the a tag under the div also has a class with the same name, when I run this code:
var names = document.querySelectorAll('.breadcrumb');
return [].map.call(names, function(name) {
return name.textContent;
});
My first element of the array gets the textContent of all the a elements and also the div.
How can I do to get the text of only the div. In this case I want to return only "Message from our Founding Members".
Is there a way to select only the root item of the html, when they have all the same class ?
Thanks
If you want to get the text from the <a> tags with the class="breadcrumb", you can do that by using more specific selectors that include the tag type like this:
var items = document.querySelectorAll("div.breadcrumb a.breadcrumb");
var text = [];
for (var i = 0; i < items.length; i++) {
text.push(items[i].textContent);
}
Working demo: http://jsfiddle.net/jfriend00/kVwH8/
If, what you're trying to do is to get the "Message from our Founding Members" text (I wasn't entirely clear from your original question), then you can do that like this::
var items = document.querySelectorAll("div.breadcrumb a.breadcrumb");
// get node after the last item (that should be the desired text node)
var txtNode = items[items.length - 1].nextSibling;
console.log(txtNode.nodeValue); // Message from our Founding Members
Working demo: http://jsfiddle.net/jfriend00/kynuE/
use div.breadcrumb because that will give you divs with class breadcrumb, not a tags.
You can do this:
var names = document.querySelectorAll('div.breadcrumb')[0].childNodes;
var text = Array.prototype.reduce.call(names,function(prev,node){
if(node.nodeType === 3) return (prev || '' + node.textContent.trim());
});
console.log(text);
There are a lot of ES5 stuff here like trim and reduce so better have those polyfills handy.

Remove hidden elements from cloned element

There are some elements that are hidden in this web page. Now if I want to find the hidden elements:
var node = jQuery('body')[0];
$(node).find(":hidden").remove();
This removes the hidden elements from the main node (which further changes the layout of the page). What I want to do is to copy(clone) the elements which are not hidden. For which I am trying this:
var clone = node.cloneNode(true);
$(clone).find(":hidden").remove();
But this removes all the elements inside the clone and not just the hidden elements (as expected, since its not in the dom). What's the best possible way to remove hidden elements from the clone.
I assume the issue is that until your clone is re-inserted into the DOM, then all of it is being considered hidden.
Maybe you could mark the hidden elements for removal first, then clone and then remove the marked elements:
var $node = ... ; // jQuery object of node to be cloned
$node.find(':hidden').addClass('markedForRemoval');
var $clone = $node.clone();
$clone.find('.markedForRemoval').remove();
// tidy up:
$clone.find('.markedForRemoval').removeClass('markedForRemoval');
$node.find('.markedForRemoval').removeClass('markedForRemoval');
demo: http://jsfiddle.net/BYossarian/6ysq8/
Sometimes the :visible selector will not be enough, and you will also want a selector for styles with height:0px, since display:none; and height:0px; are not equivalent.
Before cloning, we need to mark the elements as visible or invisible, because once cloned, the clone is in a variable, but not on the page, so everything inside it will qualify as :hidden. (Bonus: Let's make this as efficient as possible, by not hijacking the class or id fields, and instead using a custom data-attribute.)
Identify truly hidden elements:
$(node).find(':hidden').attr('data-hidden', 'true');
$(node).find('
*[style*="height:0px"],
*[style*="height: 0px"]
').attr('data-hidden', 'true');
Deep clone the node:
var clone = node.clone(true, true);
Remove hidden elements:
clone.find('*[data-hidden="true"]').remove();
I would stick to jQuery clone. My method is kinda rough, but it works.
<div class="bla" >
<span class="hidden">hidden</span>
<span class="hidden">hidden</span>
<span class="hidden">hidden</span>
<span > visible </span>
</div>
So, first, clone the parent.
var a = $('.bla').clone()
Then clone the visible children.
var b = $('.bla > :visible').clone() ;
Then add them to each other.
a.html(b)
The whole thing will be like so:
var a = $('.bla').clone()
var b = $('.bla > :visible').clone() ;
a.html(b)
Here is an example : http://jsfiddle.net/4Dky9/1/
var clone = node.cloneNode(true);
var hiddenElements = clone.querySelectorAll('.hidden'); // if hidden elements are applied the css class hidden
for(var i = 0; i < hiddenElements.length; i++){
clone.removeChild(hiddenElements[i])
}
If there is no hidden class, iterate through all the child elements and check for the display property.
var children = clone.childNodes;
for(var i = 0; i < children.length; i++){
if(children[i].style && children[i].style.display == 'none'){
clone.removeChild(children[i]);
}
}
try using :
$(clone).children(':hidden').remove();

Categories