forcing firefox skip "text nodes" in DOM parsing by javascript - javascript

Hi
I'm writing a javascript code to traverse HTML dom and highlight elements.
My problem is firefox returns whitespaces as text node.
Is there any solution to force it to just return tags? for example I need "firstChild" always return first tag and not any text!
Thanks

You can check if a node is an element with node.nodeType === 1.
You can also implement the new DOM Travelsal API as functions.
var dummy = document.createElement("div");
var firstElementChild = ('firstElementChild' in dummy)
? function (el) {
return el.firstElementChild;
}
: function (el) {
el = el.firstChild;
while (el && el.nodeType !== 1)
el = el.nextSibling;
return el;
}
usage
firstElementChild(el)

You can use element.firstElementChild instead. Unfortunately, this isn't supported in IE8 and below.
Alternatively, you might want to write a small function to crawl the childNodes until you find the next element node.

Maybe you could try one of the other DOM traversal methods, such as a TreeWalker.

Related

Compare node and query selector [duplicate]

Is there any way to test if a selector would match a given DOM Element? Preferably, without the use of an external library like Sizzle. This is for a library and I would like to minimize the amount of third party plugins required for the "core" library. If it ends up requiring Sizzle I'll just add that as a plugin to the library for those who want the feature it would enable.
For example, I would be able to do something like:
var element = <input name="el" />
matches("input[name=el]", element) == true
EDIT: After thinking about it more, I came up with a solution, this technically works, but it doesn't seem optimal in terms of efficiency:
function matchesSelector(selector, element) {
var nodeList = document.querySelectorAll(selector);
for ( var e in nodeList ) {
return nodeList[e] === element;
}
return false;
}
Basically the function queries the entire document with the given selector, and then it iterates over the nodeList. If the given element is in the nodeList, then it returns true, and if it isn't it will return false.
If anyone can come up with a more efficient answer I would gladly mark their response as the answer.
EDIT: Flavius Stef pointed me towards a browser specific solution for Firefox 3.6+, mozMatchesSelector. I also found the equivalent for Chrome (version compatibility unknown, and it may or may not work on Safari or other webkit browsers): webkitMatchesSelector, which is basically the same as the Firefox implementation. I have not found any native implementation for the IE browsers yet.
For the above example, the usage would be:
element.(moz|webkit)MatchesSelector("input[name=el]")
It seems the W3C has also addressed this in the Selectors API Level 2 (still a draft at this moment) specification. matchesSelector will be a method on DOM Elements once approved.
W3C Usage: element.matchesSelector(selector)
Since that specification is still a draft and there is a lag time before popular browsers implement the methods once it becomes the standard, it may be a while until this actually usable. Good news is, if you use any of the popular frameworks, chances are they probably implement this functionality for you without having to worry about cross browser compatibility. Although that doesn't help those of us who can't include third party libraries.
Frameworks or libraries that implement this functionality:
http://www.prototypejs.org/api/element/match
http://developer.yahoo.com/yui/docs/YAHOO.util.Selector.html
http://docs.jquery.com/Traversing/is
http://extjs.com/deploy/dev/docs/output/Ext.DomQuery.html#Ext.DomQuery-methods
http://base2.googlecode.com/svn/doc/base2.html#/doc/!base2.DOM.Element.matchesSelector
http://wiki.github.com/jeresig/sizzle/
For the benefit of those visiting this page after lo these many years, this functionality is now implemented in all modern browsers as element.matches without vendor prefix (except for ms for MS browsers other than Edge 15, and webkit for Android/KitKat). See http://caniuse.com/matchesselector.
For best performance, use the browser implementations ((moz|webkit|o|ms)matchesSelector) where possible. When you can't do that, here is a manual implementation.
An important case to consider is testing selectors for elements not attached to the document.
Here's an approach that handles this situation. If it turns out the the element in question is not attached to the document, crawl up the tree to find the highest ancestor (the last non-null parentNode) and drop that into a DocumentFragment. Then from that DocumentFragment call querySelectorAll and see if the your element is in the resulting NodeList.
Here is the code.
The document
Here's a document structure we'll be working with. We'll grab the .element and test whether it matches the selectors li and .container *.
<!DOCTYPE html>
<html>
<body>
<article class="container">
<section>
<h1>Header 1</h1>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
</section>
<section>
<h1>Header 2</h1>
<ul>
<li>one</li>
<li>two</li>
<li class="element">three</li>
</ul>
</section>
<footer>Footer</footer>
</article>
</body>
</html>
Searching with document.querySelectorAll
Here is a matchesSelector function that uses document.querySelectorAll.
// uses document.querySelectorAll
function matchesSelector(selector, element) {
var all = document.querySelectorAll(selector);
for (var i = 0; i < all.length; i++) {
if (all[i] === element) {
return true;
}
}
return false;
}
This works as long as that element is in the document.
// this works because the element is in the document
console.log("Part 1");
var element = document.querySelector(".element");
console.log(matchesSelector("li", element)); // true
console.log(matchesSelector(".container *", element)); // true
However, it fails if the element is removed from the document.
// but they don't work if we remove the article from the document
console.log("Part 2");
var article = document.querySelector("article");
article.parentNode.removeChild(article);
console.log(matchesSelector("li", element)); // false
console.log(matchesSelector(".container *", element)); // false
Searching within a DocumentFragment
The fix requires searching whatever subtree that element happens to be in. Here's an updated function named matchesSelector2.
// uses a DocumentFragment if element is not attached to the document
function matchesSelector2(selector, element) {
if (document.contains(element)) {
return matchesSelector(selector, element);
}
var node = element;
var root = document.createDocumentFragment();
while (node.parentNode) {
node = node.parentNode;
}
root.appendChild(node);
var all = root.querySelectorAll(selector);
for (var i = 0; i < all.length; i++) {
if (all[i] === element) {
root.removeChild(node);
return true;
}
}
root.removeChild(node);
return false;
}
Now we see that matchesSelector2 works even though the element is in a subtree that is detached from the
document.
// but they will work if we use matchesSelector2
console.log("Part 3");
console.log(matchesSelector2("li", element)); // true
console.log(matchesSelector2(".container *", element)); // true
You can see this working at jsfiddle.
Putting it all together
Here's the final implementation I came up with:
function is(element, selector) {
var node = element;
var result = false;
var root, frag;
// crawl up the tree
while (node.parentNode) {
node = node.parentNode;
}
// root must be either a Document or a DocumentFragment
if (node instanceof Document || node instanceof DocumentFragment) {
root = node;
} else {
root = frag = document.createDocumentFragment();
frag.appendChild(node);
}
// see if selector matches
var matches = root.querySelectorAll(selector);
for (var i = 0; i < matches.length; i++) {
if (this === matches.item(i)) {
result = true;
break;
}
}
// detach from DocumentFragment and return result
while (frag && frag.firstChild) {
frag.removeChild(frag.firstChild);
}
return result;
}
An important note is that jQuery's is implementation is much faster. The first optimization I would look into is avoiding crawling up the tree if we don't have to. To do this you could look at the right-most part of the selector and test whether this matches the element. However, beware that if the selector is actually multiple selectors separated by commas, then you'll have to test each one. At this point you're building a CSS selector parser, so you might as well use a library.
In the absence of xMatchesSelector, I'm thinking to try adding a style with the requested selector to a styleSheet object, along with some arbitrary rule and value that is not likely to be already in use. Then check the computed/currentStyle of the element to see if it has inherited the added CSS rule. Something like this for IE:
function ieMatchesSelector(selector, element) {
var styleSheet = document.styleSheets[document.styleSheets.length-1];
//arbitrary value, probably should first check
//on the off chance that it is already in use
var expected = 91929;
styleSheet.addRule(selector, 'z-index: '+expected+' !important;', -1);
var result = element.currentStyle.zIndex == expected;
styleSheet.removeRule(styleSheet.rules.length-1);
return result;
}
There's probably a handbag full of gotcha's with this method. Probably best to find some obscure proprietary CSS rule that is less likely to have a visual effect than z-index, but since it is removed almost immediately after it is set, a brief flicker should be the only side effect if that. Also a more obscure rule will be less likely to be overridden by a more specific selector, style attribute rules, or other !important rules (if IE even supports that). Anyway, worth a try at least.
The W3C selectors API (http://www.w3.org/TR/selectors-api/) specifies document.querySelectorAll(). This is not supported on all browsers, so you'd have to google the ones that do support it: http://www.google.com/search?q=browsers+implementing+selector+api
I'm dealing with this issue now. I have to support IE8 with native Javascript, which presents a curious challenge: IE8 supports both querySelector and querySelectorAll, but not matchesSelector. If your situation is similar, here's an option for you to consider:
When you're handed the DOM node and a selector, make a shallow copy of the node as well as its parent. This will preserve all of their attributes but won't make copies of their respective children.
Attach the cloned node to the cloned parent. Use querySelector on the cloned parent -- the only thing it needs to search is the only child node it has so this process is constant time. It will either return the child node or it won't.
That'd look something like this:
function matchesSelector(node, selector)
{
var dummyNode = node.cloneNode(false);
var dummyParent = node.parent.cloneNode(false);
dummyParent.appendChild(dummyNode);
return dummyNode === dummyParent.querySelector(selector);
}
It may be worth creating a complete chain of shallow-copied parents all the way up to the root node and querying the (mostly empty) dummy root if you'd like to be able to test your node's relationship to its ancestors.
Off the top of my head I'm not sure what portion of selectors this would work for, but I think
it'd do nicely for any that didn't worry about the tested node's children. YMMV.
-- EDIT --
I decided to write the function to shallow copy everything from the node being tested to root. Using this, a lot more selectors are employable. (Nothing related to siblings, though.)
function clonedToRoot(node)
{
dummyNode = node.cloneNode(false);
if(node.parentNode === document)
{
return {'root' : dummyNode, 'leaf' : dummyNode};
}
parent = clonedToRoot(node.parentNode).root;
parent.appendChild(dummyNode);
return {'root' : parent, 'leaf' : dummyNode};
}
function matchesSelector(node, selector)
{
testTree = clonedToRoot(node)
return testTree.leaf === testTree.root.querySelector(selector)
}
I'd welcome an expert to explain what kinds of selectors there are that this wouldn't cover!
Modern browsers can do it with the document.querySelectorAll function.
http://www.w3.org/TR/selectors-api/
Just use an id for your element? HTML-IDs have to be unique…

document.querySelector via textContent

Is it possible in JS to select the first element whatsoever with document.querySelector solely by a given textDocument, if the that textDocument is available on viewport when the code is executed?
I am looking for a way without the following code because it brings out all relevant tagNames and filters them via textContent, but I want to select them by text content, not to filter.
document.querySelectorAll('tagName').forEach( (e)=>{
if (e.textContent.includes('Delete')) {
e.click();
}
});
There is no CSS selector targeting on textContent.
Also, as your code is currently written, it's quite easy to grab the first element which textContent includes this string, it will always be document.documentElement or null.
You should make your query a bit more strict.
You could probably build an XPath query to this very extent, but that would end up slower than iterating over all the nodes by yourself.
So if performance is an issue, a TreeWalker is the way to go.
Here is a function that will grab elements by textContent.
It has different optional arguments which will allow you to tell
if the query should be strict ("string === textContent" which is the default),
a node to start the search from (defaults to document.documentElement)
if you are only interested in the elements without children
function getElementByTextContent(str, partial, parentNode, onlyLast) {
var filter = function(elem) {
var isLast = onlyLast ? !elem.children.length : true;
var contains = partial ? elem.textContent.indexOf(str) > -1 :
elem.textContent === str;
if (isLast && contains)
return NodeFilter.FILTER_ACCEPT;
};
filter.acceptNode = filter; // for IE
var treeWalker = document.createTreeWalker(
parentNode || document.documentElement,
NodeFilter.SHOW_ELEMENT, {
acceptNode: filter
},
false
);
var nodeList = [];
while (treeWalker.nextNode()) nodeList.push(treeWalker.currentNode);
return nodeList;
}
// only the elements whose textContent is exactly the string
console.log('strict', getElementByTextContent('This should be found'))
// all elements whose textContent contain the string (your code)
console.log('partial', getElementByTextContent('This should', true))
// only the elements whose textContent is exactly the string and which are the last Element of the tree
console.log('strict onlyLast', getElementByTextContent('This should be found', false, null, true))
<p><span>This should be found</span></p>
<span>This should only in partial mode</span><br>
<span>This must not be found</span>
<!-- p should not be found in onlyLast mode -->
No, there is not. document.querySelector can only accept a string argument that describes one or more CSS selectors separated by commas. You cannot provide document.querySelector a textDocument.
You will have to check the content of a node different, with one way being the way you've described in the question.

jQuery append() vs appendChild()

Here's some sample code:
function addTextNode(){
var newtext = document.createTextNode(" Some text added dynamically. ");
var para = document.getElementById("p1");
para.appendChild(newtext);
$("#p1").append("HI");
}
<div style="border: 1px solid red">
<p id="p1">First line of paragraph.<br /></p>
</div>
What is the difference between append() and appendChild()?
Any real time scenarios?
The main difference is that appendChild is a DOM method and append is a jQuery method. The second one uses the first as you can see on jQuery source code
append: function() {
return this.domManip(arguments, true, function( elem ) {
if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
this.appendChild( elem );
}
});
},
If you're using jQuery library on your project, you'll be safe always using append when adding elements to the page.
No longer
now append is a method in JavaScript
MDN documentation on append method
Quoting MDN
The ParentNode.append method inserts a set of Node objects or DOMString objects after the last child of the ParentNode. DOMString objects are inserted as equivalent Text nodes.
This is not supported by IE and Edge but supported by Chrome(54+), Firefox(49+) and Opera(39+).
The JavaScript's append is similar to jQuery's append.
You can pass multiple arguments.
var elm = document.getElementById('div1');
elm.append(document.createElement('p'),document.createElement('span'),document.createElement('div'));
console.log(elm.innerHTML);
<div id="div1"></div>
append is a jQuery method to append some content or HTML to an element.
$('#example').append('Some text or HTML');
appendChild is a pure DOM method for adding a child element.
document.getElementById('example').appendChild(newElement);
I know this is an old and answered question and I'm not looking for votes I just want to add an extra little thing that I think might help newcomers.
yes appendChild is a DOM method and append is JQuery method but practically the key difference is that appendChild takes a node as a parameter by that I mean if you want to add an empty paragraph to the DOM you need to create that p element first
var p = document.createElement('p')
then you can add it to the DOM whereas JQuery append creates that node for you and adds it to the DOM right away whether it's a text element or an html element
or a combination!
$('p').append('<span> I have been appended </span>');
appendChild is a DOM vanilla-js function.
append is a jQuery function.
They each have their own quirks.
The JavaScript appendchild method can be use to append an item to another element. The jQuery Append element does the same work but certainly in less number of lines:
Let us take an example to Append an item in a list:
a) With JavaScript
var n= document.createElement("LI"); // Create a <li> node
var tn = document.createTextNode("JavaScript"); // Create a text node
n.appendChild(tn); // Append the text to <li>
document.getElementById("myList").appendChild(n);
b) With jQuery
$("#myList").append("<li>jQuery</li>")
appendChild is a pure javascript method where as append is a jQuery method.
I thought there is some confusion here so I'm going to clarify it.
Both 'append' and 'appendChild' are now native Javascript functions and can be used concurrently.
For example:
let parent_div = document.querySelector('.hobbies');
let list_item = document.createElement('li');
list_item.style.color = 'red';
list_item.innerText = "Javascript added me here"
//running either one of these functions yield same result
const append_element = parent_div.append(list_item);
const append_child_element = parent_div.appendChild(list_item);
However, the key difference is the return value
e.g
console.log(append_element) //returns undefined
whereas,
console.log(append_child_element) // returns 'li' node
Hence, the return value of append_child method can be used to store it in a variable and use it later, whereas, append is use and throw (anonymous) function by nature.

JS DOM equivalent for JQuery append

What is the standard DOM equivalent for JQuery
element.append("<ul><li><a href='url'></li></ul>")?
I think you have to extend the innerHTML property to do this
element[0].innerHTML += "<ul><li><a href='url'></a></li></ul>";
some explanation:
[0] needed because element is a collection
+= extend the innerHTML and do not overwrite
closing </a> needed as some browsers only allow valid html to be set to innerHTML
Hint:
As #dontdownvoteme mentioned this will of course only target the first node of the collection element. But as is the nature of jQuery the collection could contain more entries
Proper and easiest way to replicate JQuery append method in pure JavaScript is with "insertAdjacentHTML"
var this_div = document.getElementById('your_element_id');
this_div.insertAdjacentHTML('beforeend','<b>Any Content</b>');
More Info - MDN insertAdjacentHTML
Use DOM manipulations, not HTML:
let element = document.getElementById('element');
let list = element.appendChild(document.createElement('ul'));
let item = list.appendChild(document.createElement('li'));
let link = item.appendChild(document.createElement('a'));
link.href = 'https://example.com/';
link.textContent = 'Hello, world';
<div id="element"></div>
This has the important advantage of not recreating the nodes of existing content, which would remove any event listeners attached to them, for example.
from the jQuery source code:
append: function() {
return this.domManip(arguments, true, function( elem ) {
if ( this.nodeType === 1 ) {
this.appendChild( elem ); //<====
}
});
},
Note that in order to make it work you need to construct the DOM element from the string, it's being done with jQuery domManip function.
jQuery 1.7.2 source code
element.innerHTML += "<ul><li><a href='url'></li></ul>";

innerHTML including tags [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How do I do OuterHTML in firefox?
Could someone show me a method using javascript with which I can get the innerHTML of an element including the tags?
P.S. No jQuery please.
Edit:
Best Method:
function outerHTML(node){
// if IE, Chrome take the internal method otherwise build one
return node.outerHTML || (
function(n){
var div = document.createElement('div'), h;
div.appendChild( n.cloneNode(true) );
h = div.innerHTML;
div = null;
return h;
})(node);
}
Thanks to #Joel below for the solution.
The standard way is just to use the innerHTML property.
document.getElementById("element").innerHTML;
That will get you the full text of all of the HTML inside of the element. To get the element itself you use the outerHTML property.
document.getElementById("element").outerHTML;
I didn't like the posted function so here's something I consider better. Note that IE has different handling of event listeners for the innerHTML and outerHTML properties (that is a general comment, it is not specific to the following), so be careful. There are also differences in the serialisation algorithms so you probably won't get exactly the same inner or outerHTML from all browsers.
The first version below is essentially a more efficient version of the one posted earlier, it uses a better test (in my opinion) for the existence of an outerHTML property and it is more efficient because it doesn't create a new function every time and re-uses the div kept in a closure rather than creating a new one each time. Note that it only does this for browsers that don't have native support for outerHTML, otherwise the temporary div is not kept.
The second version is to be preferred, it is very similar to the above but rather than getting the innerHTML of a clone, it uses the actual node by temporarily replacing it with a shallow clone of itself, moving it to a div, getting the div's innerHTML, then putting it back. The shallow clone is necessary so the temporary replacement maintains a valid DOM (e.g. might be getting the outerHTML of a tr which can only be replaced with a tr).
xLib = {};
xLib.outerHTML = (function() {
var d = document.createElement('div');
// Use native outerHTML if available
if (typeof d.outerHTML == 'string') {
d = null;
return function(el) {
return el.outerHTML;
}
}
// Otherwise, use clone of node and innerHTML
return function(el) {
var html, t = el.cloneNode(true);
// Don't make a new div every time,
// use div in closure
d.appendChild(t);
html = d.innerHTML;
// Remove unwanted fragment
d.removeChild(t);
// Remove reference to fragment
t = null;
return html;
}
}());
xLib.outerHTML2 = (function() {
var d = document.createElement('div');
// Use native outerHTML if available
if (typeof d.outerHTML == 'string') {
d = null;
return function(el) {
return el.outerHTML;
}
}
// Otherwise, use a placeholder and
// remove node, add to temp element,
// get innerHTML and return node to document
return function(el) {
var html;
var d2 = el.cloneNode(false);
// Temporarily move el
el.parentNode.replaceChild(d2, el);
d.appendChild(t);
html = d.innerHTML;
// Put element back
el.parentNode.replaceChild(el, d2);
d2 = null;
return html;
}
}());

Categories

Resources