I\'m using a javascript snippet in order for visitors to my site to increase the font size on all paragraphs using the following javascript:
function increas
No, you can't select multiple tags with a single call to getElementsByTagName
. You can either do two queries using getElementsByTagName
or use querySelectorAll
.
JSFiddle
var elems = document.querySelectorAll('p,li')
A year late, but if you intend on using the desired functionality multiple times in your project, and you don't have access to querySelector(), it might be worth extending the Node
object with a simple function:
JavaScript
/**
* @param {Array} tags - The array of tagNames to search for.
* @return {Array} - The elements with matching tagNames.
*/
Node.prototype.getElementsByTagNames = function (tags) {
var elements = [];
for (var i = 0, n = tags.length; i < n; i++) {
// Concatenate the array created from a HTMLCollection object
elements = elements.concat(Array.prototype.slice.call(this.getElementsByTagName(tags[i])));
}
return elements;
};
Working demo on JSFiddle.
All it does is iterating over an array of tag names, then getting the corresponding elements using getElementsByTagName()
for each iteration.
This can then of course be used on any element the same exact way you use similar functions - for example, getElementById()
- on any Node
object, you are not limited to document
.
Can I select multiple tags using getElementsByTagName?
Yes, but you will have to use getElementsByTagName multiple times.
Although your example only specifies Document.getElementsByTagName() I have assumed you would like this to work with element.getElementsByTagName() as well.
getElementsByTagName returns a HTMLCollection object so the ideal outcome would be a method which returns a HTMLCollection object of elements for all tag names provided.
Things to note about HTMLCollection's
getElementsByTagName
, getElementsByClassName
and getElementsByTagNameNS
nodeList.children
As HTMLCollection's cannot be modified the best we can do is either return an object which resembled a HTMLCollection's as much as possible, see Create a HTMLCollection or to create an nodeList
and return the children
property.
Firstly we need to collect all the matching elements for our HTMLCollection
The simplest way would be to use the querySelectorAll function which returns a nodeList
.
var nodeList = document.querySelectorAll(selector);
An alternative would be to use the getElementsByTagName
method for each tag, convert the returned HTMLCollection object to an array so they can be merged together.
Like so .
var HTMLCollectionArray = [];
var names = selector.split(",");
for (var i = 0, n = names.length; i < n; i++){
HTMLCollectionArray = HTMLCollectionArray.concat(Array.prototype.slice.call(document.getElementsByTagName(names[i])));
}
The nodeList can also be converted to an array using the same method.
HTMLCollectionArray = Array.prototype.slice.call(nodeList);
We can now either return all the elements as an array or try to return a HTMLCollection.
If we were to return a HTMLCollection it would have to be either move or copy the elements to a single parentNode so we can access parentNode.children
.
I found using document.createDocumentFragment
works best.
var createDocumentFragment = document.createDocumentFragment();
for (var i = 0; i < HTMLCollectionArray.length; i++) {
createDocumentFragment.appendChild(HTMLCollectionArray[i]);
};
HTMLCollection = createDocumentFragment.children;
return HTMLCollection;
Although this would return the correct type(HTMLCollection) it does not return the actual state of the elements when the method was called. The DOM has been modified to achieve this. Not a good idea.
So this leaves us with making a Fake HTMLCollection
window.MyNodeList = function(elements) {
for ( var i = 0; i < elements.length; i += 1 ) {
this[i] = elements[i];
}
Object.defineProperty( this, 'length', {
get: function () {
return elements.length;
}
});
Object.freeze( this );
};
window.MyNodeList.prototype.item function ( i ) {
return this[i] != null ? this[i] : null;
}
window.MyHTMLCollection = function(elements) {
MyNodeList.call(this, elements);
}
MyHTMLCollection.prototype = Object.create(MyNodeList.prototype);
MyHTMLCollection.prototype.constructor = MyHTMLCollection;
window.MyHTMLCollection.prototype.namedItem = function ( name ) {
for ( var i = 0; i < this.length; i += 1 ) {
if ( this[i].id === name || this[i].name === name ) {
return this[i];
}
}
return null;
}
Usage
var HTMLCollection = new MyHTMLCollection(elementsArray);
Now to piece it all together.
Ive also implemented a 'getElementsByClassNames' method and well as 'getElementsByTagNames' which both use the same core method getElementsBySelector
.
Element.prototype.getElementsByTagNames = Document.prototype.getElementsByTagNames = function(selector){
return this.getElementsBySelector(selector, 'getElementsByTagName');
}
Element.prototype.getElementsByClassNames = Document.prototype.getElementsByClassNames = function(selector){
return this.getElementsBySelector(selector, 'getElementsByClassName');
}
We ONLY want the Document and Element interfaces to inherit our new methods because they call prototype methods which do not exist in all Node interfaces. e.g. getElementsByClassName
,querySelectorAll
, etc.
If you want to minify your code then you could use Node.prototype instead of stating Element.prototype.
and Document.prototype.
Node.prototype.getElementsByTagNames = function(selector){
return this.getElementsBySelector(selector, 'getElementsByTagName');
}
Node.prototype.getElementsByClassNames = function(selector){
return this.getElementsBySelector(selector, 'getElementsByClassName');
}
Just make sure you don't try to use it on any node which isn't Document or Element.
Element.prototype.getElementsBySelector = Document.prototype.getElementsBySelector = function (selector, HTMLCollectionType) {
var HTMLCollectionArray = [];
if(typeof this.querySelectorAll !== 'undefined'){
var nodeList = this.querySelectorAll(selector);
HTMLCollectionArray = Array.prototype.slice.call(nodeList);
} else {
if(typeof HTMLCollectionType !=='undefined' && typeof this[HTMLCollectionType] !== 'undefined'){
var names = selector.split(",");
for (var i = 0, n = names.length; i < n; i++){
HTMLCollectionArray = HTMLCollectionArray.concat(Array.prototype.slice.call(this[HTMLCollectionType](names[i])));
}
}
}
return new MyHTMLCollection(HTMLCollectionArray);
/*
var createDocumentFragment = document.createDocumentFragment();
for (var i = 0; i < HTMLCollectionArray.length; i++) {
createDocumentFragment.appendChild(HTMLCollectionArray[i]);
};
HTMLCollection = createDocumentFragment.children;
return HTMLCollection;
*/
}
Usage
var element = document.getElementById('id');
element.getElementsbyClassNames('class1,class2,class2');
element.getElementsbyTagNames('li,div,p');
document.getElementsbyClassNames('class1,class2,class2');
document.getElementsbyTagNames('li,div,p');
to select two tags at once? Yes we can create one loop for both at once.
This is your whole example. But one loop (one array) with both P and LI:
function increaseFontSize() {
Array.from(document.getElementsByTagName('p'))
.concat(Array.from(document.getElementsByTagName('li'))).forEach(el => {
if(el.style.fontSize) {
var s = parseInt(el.style.fontSize.replace("px",""));
} else {
var s = 14;
}
if(s != max) {
s += 1;
}
el.style.fontSize = s+"px"
});
}
Explanation:
Array.from()
this is official way how to make real Array from a collection.
Some browsers might support forEach on HTMLCollection but it's not in specs.
Even so, they probably don't support concat() method.
Using from() will create a shallow copy of the collection.
That's an advantage on HTMLCollection because items could be modified during iteration
and modification to elements inside the Array will still be a modification on original objects.
Now we have two standard Arrays and can use concat() to join them and forEach to iterate the joined result.