Since the getElementsByTagName() function is new (DOM-1?) I wanted another more reliable method to get a reference to an element based on its tag name/id.
Edit- Without using a framework, since I need to cut down on size; so 10-20K for a framework is unacceptable. I just need the JS code that can fetch an element
getElementsByTagName is not new. It is supported since IE5, FF1 and Opera 7 according to w3schools
[edit]
Thanks for pointing this out. It was indeed supported since Opera 7.
As mentioned, getElementsByTagName is not new...
I think you're going to get about 10 references to jQuery.
Returns all the paragraph elements:
$('p').length
If 19kb is too big, and you just want to do element selection, something like sizzle works well, at about 4kb. The only thing I would note is that you're probably going to end up needing something that's in jQuery anyway.
http://sizzlejs.com/
Queries are very similar:
Sizzle("li");
19kb is a really small one-time price to pay for the power of jQuery.
If all you want to do is select elements, it may be smart to just use the sizzle selector engine and not a full blown library. I would go with the full library, but, going with a selector engine might be useful in limited circumstances.
Sizzle is the CSS selector engine that powers jQuery.
http://sizzlejs.com/
Or prototype, etc. You'll need to use one of these javascript glue libraries to achieve this. All of them will call this function if it exists, but fake it otherwise.
Here is an implementation based on the jQuery 1.12.4 implementation. It uses getElementsByTagName if available. If not, it uses querySelectorAll if available. If not, it falls back on recursively traversal. jQuery 1.12.4 supports older browsers, such as IE6, according to themselves.
function getElementsByTagName( node, tagName ) {
if (tagName == '*') {
tagName = undefined;
}
var merge = function( first, second ) {
var len = +second.length,
j = 0,
i = first.length;
while ( j < len ) {
first[ i++ ] = second[ j++ ];
}
// Support: IE<9
// Workaround casting of .length to NaN on otherwise arraylike objects (e.g., NodeLists)
if ( len !== len ) {
while ( second[ j ] !== undefined ) {
first[ i++ ] = second[ j++ ];
}
}
first.length = i;
return first;
},
nodeName = function( elem, name ) {
return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
},
elems, elem,
i = 0,
context = node,
tag = tagName,
found = typeof context.getElementsByTagName !== "undefined" ?
context.getElementsByTagName( tag || "*" ) :
typeof context.querySelectorAll !== "undefined" ?
context.querySelectorAll( tag || "*" ) :
undefined;
if ( !found ) {
for ( found = [], elems = context.childNodes || context;
( elem = elems[ i ] ) != null;
i++
) {
if ( !tag || nodeName( elem, tag ) ) {
found.push( elem );
} else {
merge( found, getElementsByTagName( elem, tag ) );
}
}
}
return found;
/* return tag === undefined || tag && nodeName( context, tag ) ?
merge( [ context ], found ) :
found;*/
}
I took the getAll() internal function of jQuery 1.12.4 and copied in the two helper functions it needs (jQuery.nodeName and jQuery.merge). I also made sure you can call it with "*" as tagName by adding a few lines in the top of the function. Finally, at the end of the function I commented out some functionality, which adds current node to result (if it matches), and simply returns the found nodes.
Be aware that the function in some cases returns an HTMLCollection, and in other circumstances returns an Array. Also beware that when "*" is passed as tagname, output differs depending on browser: The Element.prototype.getElementsByTagName does not return TextNodes, but the recursive traversal does.
Alternatively, you could use picoQuery. picoQuery is an implementation of jQuery, where you can select which methods you need in an online builder. in this case, you need no methods, as selection is part of core, and the build is only 1kb gzipped. picoQuery is written for modern browsers, but falls back to jQuery 1.12.4 for older browsers.
Related
jQuery's .append() function can take multiple arguments, either flat or in an array. I have some code where I need to append 3 items, one of which might not exist, like:
whatever.append(always).append(maybe).append(alwaysToo);
/* or */
whatever.append(always, maybe, alwaysToo);
/* or */
var arrayOfThoseThree = [ always, maybe, alwaysToo ];
whatever.append(arrayOfThoseThree);
I can not make out from the jQuery docs what, if anything, the value of maybe should be to say "just ignore this one":
maybe = '';
maybe = null;
maybe = undefined;
maybe = ???
as in:
maybe = needMaybe ? $('<blah...>') : ignoreThisValue;
I could, of course, do something like:
whatever.append(always);
if (maybe) whatever.append(maybe);
whatever.append(alwaysToo);
but that's ugly (especially as this is part of a larger chain).
And I could experiment with different values until I find one that "works", but I was hoping there was an "official" documented way that won't fail to work some future day because I was using an "undocumented feature".
Point me in the right direction?
[EDIT]
I was wondering in general, but the concrete example in front of me is:
var titl = this.dataset.title; /* optional */
var ifr = $('<iframe>');
var bas = $('<base href="' + document.baseURI + '">');
var ttl = titl ? $('<title>' + titl + '</title>') : null; /* HERE */
var lnk = $('<link rel="stylesheet" href="/css/print.css">');
/* ... */
ifr.contents().find('head').append(bas, ttl, lnk);
How about
whatever.append([always, maybe, alwaysToo].filter(item => !!item));
Here's what happens in the jQuery code (the version I'm using anyway).
Note that this defines what "works" today, not what is documented to work and continue working in the future.
The .append() function is written similarly to many others in that domManip() does much of the work:
append: function() {
return this.domManip( arguments, function( elem ) {
if ( this.nodeType === 1 ||
this.nodeType === 11 ||
this.nodeType === 9 ) {
var target = manipulationTarget( this, elem );
target.appendChild( elem );
}
});
},
and the first thing domManip() does is:
domManip: function( args, callback ) {
// Flatten any nested arrays
args = concat.apply( [], args );
then it calls buildFragment():
fragment = jQuery.buildFragment( args, ... );
which does:
buildFragment: function( elems, context, scripts, selection ) {
var /* ..., */ i = 0;
for ( ; i < l; i++ ) {
elem = elems[ i ];
if ( elem || elem === 0 ) {
/* ... process this argument ... */
}
}
So empty arrays get squashed by Array.prototype.concat() and then anything that fails the test ( elem || elem === 0 ) gets ignored.
So, in fact, when ttl could be null, all of these (currently) do "the right thing":
whatever.append( bas, ttl, lnk);
whatever.append([bas, ttl, lnk]);
whatever.append([bas],[ttl], [lnk]);
whatever.append( bas, [ttl], lnk);
whatever.append(bas).append( ttl ).append(lnk);
whatever.append(bas).append([ttl]).append(lnk);
But, as near as I can find, the documentation makes no statements about a value or values which you can use which will safely be ignored (now and forever).
Thus the safest course of action (at least where => is supported) is the Answer from Assan:
whatever.append( [bas, ttl, lnk].filter( e => !!e ) );
Using jQuery I can create a real DOM node, but it is not located in the DOM. So how does jQuery do it? My guess that it might create it first in the DOM and then removes it.
// here `el` is not a jQuery object
el = $('<div></div>')[0];
console.log(el.tagName); // "DIV"
console.log(el.nodeType); // "1"
I believe jQuery uses document.createElement() to create the specified element. This method creates the node and returns it, but does not attach it to the DOM. See the related MDN article for details on how it works.
It is not a miracle, when you pass <div></div> to jQuery it matches it against the regular expression and tag check conditions like below
if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3
// Assume that strings that start and end with <> are HTML and skip the regex check
match = [ null, selector, null ];
}
else matches it with below regular expressions to find out the element in the selector string.
/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/
Once it find the element, then jQuery uses document.createElement("stripped Tag from selector") and will return the element with necessary jQuery methods added.
I would say until and unless you need it for your requirement, use document.createElement('div') instead of $('<div></div>').
it uses the createElement function which does create a DOM object that is not part of the page. - Just read https://developer.mozilla.org/en-US/docs/Web/API/document.createElement to create DOM objects. For text nodes see https://developer.mozilla.org/en-US/docs/Web/API/document.createTextNode
This is for new items
You can attach it to existing DOM items (part of the page) with appendChild - https://developer.mozilla.org/en-US/docs/Web/API/Node.appendChild
To find such items use https://developer.mozilla.org/en-US/docs/Web/API/element.getElementsByTagName etc.
JQuery creates an element but it doesn't add it to DOM automatically.
I order to see it in the DOM you have to add it to DOM
you could use $().after, $().appendTo and so on...
Jquery parse the HTML string using regular expressions and creates a dom element using the code below:
parseHTML: function( data, context, keepScripts ) {
if ( !data || typeof data !== "string" ) {
return null;
}
if ( typeof context === "boolean" ) {
keepScripts = context;
context = false;
}
context = context || document;
var parsed = rsingleTag.exec( data ),
scripts = !keepScripts && [];
// Single tag
if ( parsed ) {
return [ context.createElement( parsed[1] ) ];
}
parsed = jQuery.buildFragment( [ data ], context, scripts );
if ( scripts ) {
jQuery( scripts ).remove();
}
return jQuery.merge( [], parsed.childNodes );
}
It may be easier to inspect the code at this page.
http://james.padolsey.com/jquery/#v=1.10.2&fn=jQuery.parseHTML
I am writing a UserScript that will remove elements from a page that contain a certain string.
If I understand jQuery's contains() function correctly, it seems like the correct tool for the job.
Unfortunately, since the page I'll be running the UserScript on does not use jQuery, I can't use :contains(). Any of you lovely people know what the native way to do this is?
http://codepen.io/coulbourne/pen/olerh
This should do in modern browsers:
function contains(selector, text) {
var elements = document.querySelectorAll(selector);
return [].filter.call(elements, function(element){
return RegExp(text).test(element.textContent);
});
}
Then use it like so:
contains('p', 'world'); // find "p" that contain "world"
contains('p', /^world/); // find "p" that start with "world"
contains('p', /world$/i); // find "p" that end with "world", case-insensitive
...
Super modern one-line approach with optional chaining operator
[...document.querySelectorAll('*')].filter(element => element.childNodes?.[0]?.nodeValue?.match('❤'));
And better way is to search in all child nodes
[...document.querySelectorAll("*")].filter(e => e.childNodes && [...e.childNodes].find(n => n.nodeValue?.match("❤")))
If you want to implement contains method exaclty as jQuery does, this is what you need to have
function contains(elem, text) {
return (elem.textContent || elem.innerText || getText(elem)).indexOf(text) > -1;
}
function getText(elem) {
var node,
ret = "",
i = 0,
nodeType = elem.nodeType;
if ( !nodeType ) {
// If no nodeType, this is expected to be an array
for ( ; (node = elem[i]); i++ ) {
// Do not traverse comment nodes
ret += getText( node );
}
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
// Use textContent for elements
// innerText usage removed for consistency of new lines (see #11153)
if ( typeof elem.textContent === "string" ) {
return elem.textContent;
} else {
// Traverse its children
for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
ret += getText( elem );
}
}
} else if ( nodeType === 3 || nodeType === 4 ) {
return elem.nodeValue;
}
// Do not include comment or processing instruction nodes
return ret;
};
SOURCE: Sizzle.js
The original question is from 2013
Here is an even older solution, and the fastest solution because the main workload is done by the Browser Engine NOT the JavaScript Engine
The TreeWalker API has been around for ages, IE9 was the last browser to implement it... in 2011
All those 'modern' and 'super-modern' querySelectorAll("*") need to process all nodes and do string comparisons on every node.
The TreeWalker API gives you only the #text Nodes, and then you do what you want with them.
You could also use the NodeIterator API, but TreeWalker is faster
function textNodesContaining(txt, root = document.body) {
let nodes = [],
node,
tree = document.createTreeWalker(
root,
4, // NodeFilter.SHOW_TEXT
{
node: node => RegExp(txt).test(node.data)
});
while (node = tree.nextNode()) { // only return accepted nodes
nodes.push(node);
}
return nodes;
}
Usage
textNodesContaining(/Overflow/);
textNodesContaining("Overflow").map(x=>console.log(x.parentNode.nodeName,x));
// get "Overflow" IN A parent
textNodesContaining("Overflow")
.filter(x=>x.parentNode.nodeName == 'A')
.map(x=>console.log(x));
// get "Overflow" IN A ancestor
textNodesContaining("Overflow")
.filter(x=>x.parentNode.closest('A'))
.map(x=>console.log(x.parentNode.closest('A')));
This is the modern approach
function get_nodes_containing_text(selector, text) {
const elements = [...document.querySelectorAll(selector)];
return elements.filter(
(element) =>
element.childNodes[0]
&& element.childNodes[0].nodeValue
&& RegExp(text, "u").test(element.childNodes[0].nodeValue.trim())
);
}
Well, jQuery comes equipped with a DOM traversing engine that operates a lot better than the one i'm about to show you, but it will do the trick.
var items = document.getElementsByTagName("*");
for (var i = 0; i < items.length; i++) {
if (items[i].innerHTML.indexOf("word") != -1) {
// Do your magic
}
}
Wrap it in a function if you will, but i would strongly recommend to use jQuery's implementation.
I have a table. When the item is dropped I need to apply padding to a single table cell. I have flagged that cell with a class. How do I select it?
droppedRow contains the table row that is has just been dropped.
If it was an id I would do droppedRow.getElementById('..'); Is there something similar for class names. Needs to support >= IE7
Thanks
Using vanilla JavaScript, you'll probably need to load up all of the element's by tag name and then locate it by evaluating each element's classname.
For example (the styles are just for example)...
var tableCells = document.getElementsByTagName('td');
for(var i = 0, l = tableCells.length; i < l; i++) {
if(tableCells[i].className === 'droppedRow') {
tableCells[i].style.padding = '1em';
}
}
If, on the other hand, you're using jQuery, then you should be able to use:
$('.droppedRow').css('padding', '1em');
Note however that in both of these examples, all cells that have the droppedRow class name will receive this styling (rather than just a single element).
If you're not using a library, I'd say stick with the vanilla variant of this functionality - libraries would be too much overhead just to condense this to a single line.
Maxym's answer also provides a solid implementation of getElementsByClassName for older browsers.
There exists getElementsByClassName but it is not supported in IE. Here is what you can do:
var element;
// for modern browsers
if(document.querySelector) {
element = droppedRow.querySelector('.yourClass');
}
else if(document.getElementsByClassName) { // for all others
element = droppedRow.getElementsByClassName('yourClass')[0];
}
else { // for IE7 and below
var tds = droppedRow.getElementsByTagName('td');
for(var i = tds.length; i--; ) {
if((" " + tds[i].className + " ").indexOf(" yourClass ") > -1) {
element = tds[i];
break;
}
}
}
Reference: querySelector, getElementsByClassName, getElementsByTagName
Clientside getElementsByClassName cross-browser implementation:
var getElementsByClassName = function(className, root, tagName) {
root = root || document.body;
if (Swell.Core.isString(root)) {
root = this.get(root);
}
// for native implementations
if (document.getElementsByClassName) {
return root.getElementsByClassName(className);
}
// at least try with querySelector (IE8 standards mode)
// about 5x quicker than below
if (root.querySelectorAll) {
tagName = tagName || '';
return root.querySelectorAll(tagName + '.' + className);
}
// and for others... IE7-, IE8 (quirks mode), Firefox 2-, Safari 3.1-, Opera 9-
var tagName = tagName || '*', _tags = root.getElementsByTagName(tagName), _nodeList = [];
for (var i = 0, _tag; _tag = _tags[i++];) {
if (hasClass(_tag, className)) {
_nodeList.push(_tag);
}
}
return _nodeList;
}
Some browsers support it natively (like FireFox), for other you need provide your own implementation to use; that function could help you; its performance should be good enough cause it relies on native functions, and only if there is no native implementation it will take all tags, iterate and select needed...
UPDATE: script relies on hasClass function, which can be implemented this way:
function hasClass(_tag,_clsName) {
return _tag.className.match(new RegExp('(\\s|^)'+ _clsName +'(\\s|$)'));
}
It sounds like your project is in need of some JQuery goodness or some Dojo if you need a more robust and full-fledged javascript framework. JQuery will easily allow you to run the scenario you have described using its selector engine.
If you are using a library, why not use:
JQuery - $("#droppedRow > .paddedCell")
Thats the dropped row by ID and the cell by class
Prototype - $$("#droppedRow > .paddedCell")
var objects = document.getElementsByTagName('object');
for (var i=0, n=objects.length;i<n;i++) {
objects[i].style.display='none';
var swfurl;
var j=0;
while (objects[i].childNodes[j]) {
if (objects[i].childNodes[j].getAttribute('name') == 'movie') {
/* DO SOMETHING */
}
j++;
}
var newelem = document.createElement('div');
newelem.id = '678297901246983476'+i;
objects[i].parentNode.insertBefore(newelem, objects[i]);
new Gordon.Movie(swfurl, {id: '678297901246983476'+i, width: 500, height: 400});
}
It says that getAttribute is not a function of childNodes[j]. What's wrong? I don't see the point.
Remember that childNodes includes text nodes (and comment nodes, if any, and processing instructions if any, etc.). Be sure to check the nodeType before trying to use methods that only Elements have.
Update: Here in 2018, you could use children instead, which only includes Element children. It's supported by all modern browsers, and by IE8-IE11. (There are some quirks in older IE, see the link for a polyfill to smooth them over.)
Check the nodeType property is 1 (meaning the node is an element) before calling element-specific methods such as getAttribute(). Also, forget getAttribute() and setAttribute(): you almost never need them, they're broken in IE and they don't do what you might think. Use equivalent DOM properties instead. In this case:
var child = objects[i].childNodes[j];
if (child.nodeType == 1 && child.name == 'movie') {
/* DO SOMETHING */
}
What browser are you using ? If it's IE then you need to use readAttribute instead.