How do i access child nodes of a child node in javascript?
I need to target the img inside of each li and get its width and height.
I want to do this without using jquery. Just pure javascript.
<ul id="imagesUL">
<li>
<h3>title</h3>
<img src="someURL" />
</li>
</ul>
var lis = document.getElementById("imagesUL").childNodes;
The ideal way to do this is with the querySelectorAll function, if you can guarantee that it will be available (if you are designing a site for mobile browsers, for instance).
var imgs = document.querySelectorAll('#imagesUL li img');
If you can't guarantee that, however, you'll have to do the loop yourself:
var = lis = document.getElementById("imagesUL").childNodes,
imgs = [],
i, j;
for (i = 0; i < lis.length; i++) {
for (j = 0; j < lis[i].childNodes.length; j++) {
if (lis[i].childNodes[j].nodeName.toLowerCase() === 'img') {
imgs.push(lis[i].childNodes[j]);
}
}
}
The above snippet of code is an excellent argument for using a library like jQuery to preserve your sanity.
This works for me:
var children = document.getElementById('imagesUL').children;
var grandChildren = [];
for (var i = 0; i < children.length; i++)
{
var child = children[i];
for (var c = 0; c < child.children.length; c++)
grandChildren.push(child.children[c]);
}
grandChildren contains all childrens' children.
Libraries like jQuery and UnderscoreJS make this easier to write in a more declarative way.
You can provide a second parameter to the $() function to specify a scope in which to search for the images:
var lis = $('#imagesUL').children();
var images = $('img', lis)
images.each(function() {
var width = $(this).attr('width');
// ...
});
Related
What is the alternative way of doing something like
$(".myElement").each(function(){
//function
});
in plain Javascript?
This will iterate all divs in your current document. You can replace document.getElementsByClassName('someclass') etc. and do something with their attributes and values
var elements = document.getElementsByTagName('div');
for (var i = 0; i < elements.length; i++) {
doSomething(elements[i]);
}
Here is the jsfiddle: http://jsfiddle.net/allenski/p7w5btLa/
$(#myElement)
You are trying to iterate over a id selector. ID has to be unique in a HTML page.
If it's a class or element tags you want to iterate over you can use a for loop.
var $elems = $('.someClass');
for(var i=0; i< $elems.length; i++ ) {
// $elems[i] --> Gives you a `DOM` element
// $elems.eq(i) --> Gives you a `jQuery` object
}
Vanilla Javascript
var elems = document.getElementsByClassName('someClass');
for(var i=0;i< elems.length;i ++) {
elem[i] // Based on index
}
getElementsByTagName if you want to iterate over specific tags.
getElementsByName - get the elements based on name attribute.
You can also use document.querySelectorAll to get a list of objects and iterate over them.
var elems = document.querySelectorAll('.someClass')
for(var i=0; i<elems.length; i++) {
elems[i] // Give you DOM object
}
Alternative methods to the each function, here are two:
Setup
var $all = $('*');
var len = $all.length;
1
while(--len){
// use $($all[len]); for jQuery obj
var elem = $all[len];
doWork(elem);
}
2
//reset len for next loop
len = $all.length;
do {
var $elem = $all.filter(':eq('+ --len + ')');
doWork($elem);
} while (len);
var el = $(something);
for (var i = 0; i < el.length; i++) {
// do something with el[i] or more often with $(el[i])
}
el is a pseudo-array (or array-like Object) that has a length and elements accessible with the [] operator in the range 0...length-1. So you can do el[0], el[1] etc., but remember that el elements aren't jquery "objects", normally they are DOM elements, so you can't do el[0].text("Hello"). As in the each method, you have to do $(el[0]).text("Hello");
It's even wrong to do:
for (var i = 0; i < $(something).length; i++) {
// do something with el[i] or more often with $(el[i])
}
because in this way the $(something) will be recalculated every cycle.
You have to use a for loop. look at
http://www.w3schools.com/js/js_loop_for.asp
I'd like to create a select element with a list of a user's Facebook friends (obtained as a JSON object). I hardcode <select id="friends"></select> into my HTML, then use the following Javascript code to parse the JSON and insert each friend as an option of the select element:
var msgContainer = document.createDocumentFragment();
for (var i = 0; i < response.data.length; i++) {
msgContainer.appendChild(document.createTextNode('<option value="'+response.data[i].id+'">'+response.data[i].name+'</option>'));
}
document.getElementById("friends").appendChild(msgContainer);
This almost works, except that it inserts < and > instead of < and >. How can I fix it, and is there a more efficient way to insert multiple HTML elements using pure Javascript (not JQuery)?
Not sure why you're creating a text node, but it would seem that you want to create option elements, so you could use the Option constructor instead.
var msgContainer = document.createDocumentFragment();
for (var i = 0; i < response.data.length; i++) {
msgContainer.appendChild(new Option(response.data[i].name, response.data[i].id));
}
document.getElementById("friends").appendChild(msgContainer);
Or you can use the generic document.createElement().
var msgContainer = document.createDocumentFragment();
for (var i = 0; i < response.data.length; i++) {
var option = msgContainer.appendChild(document.createElement("option"));
option.text = response.data[i].name;
option.value = response.data[i].id;
}
document.getElementById("friends").appendChild(msgContainer);
It's nice to have a helper function for creating elements and setting properties at the same time.
Here's a simple example of one:
function create(name, props) {
var el = document.createElement(name);
for (var p in props)
el[p] = props[p];
return el;
}
It can be expanded to cover some specific needs, but this will work for most cases.
You'd use it like this:
var msgContainer = document.createDocumentFragment();
for (var i = 0; i < response.data.length; i++) {
msgContainer.appendChild(create("option", {
text: response.data[i].name,
value: response.data[i].id
}));
}
document.getElementById("friends").appendChild(msgContainer);
Try this in your for loop instead:
var o = document.createElement('option');
o.setAttribute('value', response.data[i].id);
o.appendChild(document.createTextNode(response.data[i].name));
msgContainer.appendChild(o);
For those who need similar functionality, you can generate an html snippet using template literals and insert it using innerHTML property. Plus you can set attributes and selected while iterating over the items:
const el = document.createElement('select');
el.innerHTML = ['John', 'Sally', 'Betty'].reduce((acc, prev, i) => {
if (i === 1) {
return acc + `<option selected>${prev}</option>`;
}
return acc + `<option>${prev}</option>`;
}, '');
const root = document.querySelector('#app');
root.appendChild(el);
In modern browsers this is faster than creating elements one by one imperatively.
The end result I'm after is a JavaScript array containing a list of tag names that are used in the HTML document eg:
div, span, section, h1, h2, p, etc...
I want the list to be distinct and I'm not interested in tags within the <head> of the document (but they can be there if it's a performance hog to exclude them).
This has to work in IE 6, 7, & 8 and I don't want to use jquery.
What would be the most efficient way of doing this?
What you're looking for is document.all.tagName
At the top of my head, a for loop like this should do it (providing that you're gonna filter the tags you don't want on that list)
for(i = 0; i < document.all.length; i++)
{
console.log(document.all[i].tagName);
}
Here is a cross-browser solution:
var tags = {}; // maintains object of tags on the page and their count
var recurse = function(el) {
// if element node
if(el.nodeType == 1) {
if(!tags[el.tagName])
tags[el.tagName] = 0;
tags[el.tagName]++;
}
// recurse through the children
for(var i = 0, children = el.childNodes, len = children.length; i < len; i++) {
recurse(children[i]);
}
}
recurse(document);
// if you want just what's in the body(included)
var bodies = document.getElementsByTagName("body");
for(var i = 0; i < bodies.length; i++)
recurse(bodies[i]);
To get a unique list of tagnames in a document as an array that works in all browsers back to IE 6 and equivalent:
function listTagNames() {
var el, els = document.body.getElementsByTagName('*');
var tagCache = {};
var tagname, tagnames = [];
for (var i=0, iLen=els.length; i<iLen; i++) {
tagname = els[i].tagName.toLowerCase();
if ( !(tagname in tagCache) ) {
tagCache[tagname] = tagname;
tagnames.push(tagname);
}
}
return tagnames;
}
If you think there might be an inherited object property that is the same as a tag name, use a propertyIsEnumerable test:
if (!tagCache.propertyIsEnumerable(tagname)) {
so it will work even in Safari < 2.
Get all tagnames in the document, unique, crossbrowser, plain js:
var els = document.getElementsByTagName('*'), tags = [], tmp = {}
for (var i=0;i<els.length;i+=1){
if (!(els[i].tagName in tmp)){
tags.push(els[i].tagName);
tmp[els[i].tagName] = 1;
}
}
use
if (!(els[i].tagName in tmp)
&& !/head/i.test(els[i].parentNode.tagName)
&& !/html|head/i.test(els[i].tagName))
to exclude the <head>
As you can see I am still a novice in javascript
Why is it so that you can append a Textnode only once? When you add it again somewhere else the first one disappears
I do not need a solution to a problem I was just curious what is causing this behavior.
Example where the textnode is only added to the last element of an array:
function hideAdd(){
var hide = document.createTextNode('Afbeelding verbergen');
var afb = collectionToArray(document.getElementsByTagName('img'));
afb.pop();
var divs = [];
for (i=0; i < afb.length; i++){
divs.push(afb[i].parentNode);
}
console.log(divs);
for ( i = 0; i < divs.length;i++){
divs[i].appendChild(hide);
}
}
This is where you use an unique textnode so it works:
function hideAdd(){
var hide = []
var afb = collectionToArray(document.getElementsByTagName('img'));
afb.pop();
var divs = [];
for (i=0; i < afb.length; i++){
divs.push(afb[i].parentNode);
hide[i] = document.createTextNode('Afbeelding verbergen');
}
console.log(divs);
for ( i = 0; i < divs.length;i++){
divs[i].appendChild(hide[i]);
}
}
Short answer is the DOM is a tree, not a network. Each node can have only one parent. If you could add a node in more than one location, it would have more than one parent.
I was asked a question in yestoday's interview:
How do you reverse a <li> list
efficiently?
for example, if there is a list:
<ul id="list">
<li>1</li>
<li>2</li>
...
<li>10000</li>
</ul>
then after the reverse, the list would look like:
<ul id="list">
<li>10000</li>
...
<li>2</li>
<li>1</li>
</ul>
The most efficient code I can come up with is this:
function reverse(){
var list = document.getElementById("list");
var node_list = list.childNodes;
var fragment = document.createDocumentFragment();
for(var i=node_list.length-1; i>=0; i--){
fragment.appendChild(node_list[i]);
}
list.appendChild(fragment);
}
But it's still really slow(takes about 10s in Chrome). Any idea?
UPDATE:
I think there is something wrong with my Chrome... I installed a Chromium and tested the code above in it, it takes less a second.
I guess the point of the interview question basically is the fact that innerHTML is way faster than any DOM operation in every browser. So, don't use DocumentFragment, use a simple string instead:
var ul = document.getElementById("list");
var lstLi = ul.childNodes;
var str = '';
for (var i=lstLi.length - 1; i >= 0; i--) {
str += '<li>' + lstLi[i].innerHTML + '</li>';
}
ul.innerHTML = str;
http://jsfiddle.net/bKeuD/
The way the DOM work, you don't need to re-create the zone. All you need to do is move your element inside the ul that already exist. An optimal solution would be something along this :
var ul = document.getElementById("lstLi");
var lstLi = ul.childNodes;
for (var i=0, c = lstLi.length; i < c; i++) {
ul.insertBefore(lstLi[i], ul.firstChild);
}
Basicly what this does is that it iterate over each element an put them in first. In the end your list will be reversed.
You can do it this way:
var list = document.getElementsByTagName("ul")[0],
items = list.childNodes,
itemsLen = items.length;
while (itemsLen--) {
list.appendChild(items[itemsLen]);
}
Test: http://jsbin.com/ohegu4/2/edit
And if your problem is that you don’t want to block the browser, you can do that:
var list = document.getElementsByTagName("ul")[0],
items = list.childNodes,
itemsLen = items.length;
(function reversePart() {
var iterations = 10; // Number of processed elements every 100ms
window.setTimeout(function(){
while (iterations-- && itemsLen--) {
list.appendChild(items[itemsLen]);
}
if (itemsLen) {
reversePart();
}
}, 100); // Delay between each process : 100ms
})();
Test (with 100000 li, yeah!): http://jsbin.com/ubugi3/2/edit
Pumbaa80's string-based method is the fastest way I found to reverse the list. But just in case you really want to reverse it using DOM methods (e.g. you don't want to lose the attributes of the list items), you can do it this way:
function reverseDom(){
var list = document.getElementById('list');
var items = list.childNodes;
var length = items.length;
var item0 = items[0];
var i;
list.style.display = 'none';
for(i = 0; i < length - 1; i++)
{
list.insertBefore(list.lastChild, item0);
}
list.style.display = 'block';
}
In Google Chrome in Linux, the method above took about a second to reverse a 10,000 item list. But Pumbaa80's method is faster. You can compare the two methods side by side on the same list if you go to this link:
http://jsbin.com/ubugi3/6