How can I get text from a node so that it returns it with whitespace formatting like "innerText" does, but excludes descendant nodes that are hidden (style display:none)?
UPDATE: As the OP points out in comments below, even though MDN clearly states that IE introduced innerText to exclude hidden content, testing in IE indicates that is not the case. To summarize:
Chrome: innerText returns text only from visible elements.
IE: innerText returns all text, regardless of the element's visibility.
Firefox: innerText is undefined (as indicated by the W3C, and in my testing).
Add all of this up, and you have a property to avoid like the plague. Read on for the solution....
If you want cross-browser compatibility, you'll have to roll your own function. Here's one that works well:
function getVisibleText( node ) {
if( node.nodeType === Node.TEXT_NODE ) return node.textContent;
var style = getComputedStyle( node );
if( style && style.display === 'none' ) return '';
var text = '';
for( var i=0; i<node.childNodes.length; i++ )
text += getVisibleText( node.childNodes[i] );
return text;
}
If you want to get painfully clever, you can create a property on the Node object so that this feels more "natural". At first I thought this would be a clever way to polyfill the innerText property on Firefox, but that property is not created as a property on the Node object prototype, so you would really be playing with fire there. However, you can create a new property, say textContentVisible:
Object.defineProperty( Node.prototype, 'textContentVisible', {
get: function() {
return getVisibleText( this );
},
enumerable: true
});
Here's a JsFiddle demonstrating these techniques: http://jsfiddle.net/8S82d/
This is interesting, I came here because I was looking for why the text of display:none elements was omitted in Chrome.
So, this was ultimately my solution.
Basically, I clone the node and remove the classes/styling that set display:none.
How to add hidden element's innerText
function getInnerText(selector) {
let d = document.createElement('div')
d.innerHTML = document.querySelector(selector).innerHTML.replaceAll(' class="hidden"', '')
return d.innerText
}
Related
This should be really simple but I'm having trouble with it. How do I get a parent div of a child element?
My HTML:
<div id="test">
<p id="myParagraph">Testing</p>
</div>
My JavaScript:
var pDoc = document.getElementById("myParagraph");
var parentDiv = ??????????
I would have thought document.parent or parent.container would work but I keep getting not defined errors. Note that the pDoc is defined, just not certain variables of it.
Any ideas?
P.S. I would prefer to avoid jQuery if possible.
You're looking for parentNode, which Element inherits from Node:
parentDiv = pDoc.parentNode;
Handy References:
DOM2 Core specification - well-supported by all major browsers
DOM2 HTML specification - bindings between the DOM and HTML
DOM3 Core specification - some updates, not all supported by all major browsers
HTML5 specification - which now has the DOM/HTML bindings in it
If you are looking for a particular type of element that is further away than the immediate parent, you can use a function that goes up the DOM until it finds one, or doesn't:
// Find first ancestor of el with tagName
// or undefined if not found
function upTo(el, tagName) {
tagName = tagName.toLowerCase();
while (el && el.parentNode) {
el = el.parentNode;
if (el.tagName && el.tagName.toLowerCase() == tagName) {
return el;
}
}
// Many DOM methods return null if they don't
// find the element they are searching for
// It would be OK to omit the following and just
// return undefined
return null;
}
Edit 2021
Element.closest is part of the DOM standard. It takes a selector as an argument and returns the first matching ancestor or null if there isn't one.
The property pDoc.parentElement or pDoc.parentNode will get you the parent element.
var parentDiv = pDoc.parentElement
edit: this is sometimes parentNode in some cases.
https://developer.mozilla.org/en-US/docs/Web/API/Node/parentElement
This might help you.
ParentID = pDoc.offsetParent;
alert(ParentID.id);
Knowing the parent of an element is useful when you are trying to position them out the "real-flow" of elements.
Below given code will output the id of parent of element whose id is provided. Can be used for misalignment diagnosis.
<!-- Patch of code to find parent -->
<p id="demo">Click the button </p>
<button onclick="parentFinder()">Find Parent</button>
<script>
function parentFinder()
{
var x=document.getElementById("demo");
var y=document.getElementById("*id of Element you want to know parent of*");
x.innerHTML=y.parentNode.id;
}
</script>
<!-- Patch ends -->
I had to do recently something similar, I used this snippet:
const getNode = () =>
for (let el = this.$el; el && el.parentNode; el = el.parentNode){
if (/* insert your condition here */) return el;
}
return null
})
The function will returns the element that fulfills your condition. It was a CSS class on the element that I was looking for. If there isn't such element then it will return null
In case somebody would look for multiple elements it only returns closest parent to the element that you provided.
My example was:
if (el.classList?.contains('o-modal')) return el;
I used it in a vue component (this.$el) change that to your document.getElementById function and you're good to go. Hope it will be useful for some people ✌️
I have a problem to get DOM element's attributes in JS. Here is code:
return elem.getAttribute(attr) || elem[attr] || "";
elem.getAttribute(attr) is used to get attributes like name or id, elem[attr] is used to get attribute like tagName. It works fine, until style came out.
In my case, I want to have "" when style attribute not set. But with above code, it will try elem[attr] when elem.getAttribute(attr) returns null. So if style is not set, I get all browser supported styles instead of "".
How to deal with this problem? Is there any better way than enum attributes?
Edit:
I want to write a general function to get element's attributes (such as name, style) or properties(such as tagName).
The main difference is elem.getAttribute(attr) try to get an attribute in the tag element, but elem[attr] try to get a property from an object, is important to know that elem inherits all properties from the Element Object, this properties are declared and in some cases defined, one of this properties is style.
In the particular case of the style property, by default this has been defined with an CSSStyleDeclaration, that's the reason you get attributes of style.
If you want only check if the attribute is in the tag, I suggest you only use this code:
return elem.getAttribute(attr) || "";
This is a code I use on my applications, so I'll just copy & paste it:
Object.defineProperty( Element.prototype, "hashAttr", { get: function(){
/* Bekim Bacaj 2008 */
var hash=[], i = 0, x = this.attributes;
while( x[i] ){hash[x[i].name] = x[i++].value};
return hash;
}})
;
which is, to my knowledge, the fastest possible.
This is a sample return from an element that has no inline or JavaScript assigned styles on its tag.:
>> media.hashAttr
{
width : "100%",
height : "100%",
id : "media",
src : "http://*****.***/stream/*****.mp4",
autoplay : "false",
poster : "http://******.***/thumb/*****.jpg",
type : "video/mp4"
}
Notice that, therefore, no offline style-attribute is present in the property list.
I have an answer. It handles tagName specially. It's not great. But it can get the job done.
var value = elem.getAttribute(attr);
if (!value) {
if (attr == "tagName") {
value = elem["tagName"] || "";
} else {
value = "";
}
}
return value;
When I don't have access to JQuery for whatever reason, I usually perform attribute selection manually using element.hasAttribute or element.getAttribute.
However, there seems to be some complication here because older browsers (IE <= 8) don't support hasAttribute. So if you want to check whether an element has a certain attribute, you need to use getAttribute and check the return value.
if ((element.hasAttribute && element.hasAttribute("foo"))
|| (element.getAttribute("foo") != null))
{
....
}
This makes me think you may as well just forget about using hasAttribute at all, and just always use getAttribute. The problem is that I can't find consistent documentation on the return value of getAttribute. In practice, it returns null on most browsers if the attribute doesn't exist - but it also might return empty string, because that is what it's supposed to do according to the DOM 3 specification.
Unfortunately, returning an empty string leaves us with no way to disambiguate between:
<div data-my-attribute = ""></div>
and
<div></div>
So, in practice - it seems the most portable thing to do is to first check if the browser supports hasAttribute, and if not, use getAttribute - since IE 6-8 implement getAttribute so that it returns null (instead of empty string) if the attribute doesn't exist.
Is this really the best way to go about doing this? Or is there a better way to write cross-browser attribute detection in plain Javascript?
The following works well in IE6-10 (tested it in IETester), Firefox, Chrome and Safari:
function hasAttrib(element, attributeName) {
return (typeof element.attributes[attributeName] != 'undefined');
}
Here are jsfiddle and its standalone result page (for testing in older browsers).
This will probably need some testing, but would not the length of the String describing the Element change if you tried to setAttribute an attribute it doesn't already have vs remain the same if you tried for one it does have?
var hasAttribute = (function () {
if (HTMLElement && HTMLElement.prototype
&& HTMLElement.prototype.hasAttribute)
return function (node, attrib) { // use if available
return node.hasAttribute(attrib);
};
return function (node, attrib) {
var d = document.createElement('div'), // node for innerHTML
e = node.cloneNode(false), // id attribute will be lost here
i;
if (attrib.toLowerCase() === 'id') return !!node.getAttribute('id');
d.appendChild(e);
i = d.innerHTML.length; // get original length
e.setAttribute(attrib, e.getAttribute(attrib)); // set attrib to test
return d.innerHTML.length === i; // see if length changed
};
}());
I have the following setup:
var el = $("#overParent");
// Do whatever with el here.
var cls = $(".valueElement", "#parent"); // Get elements with class valueElement inside element with id parent
// Do whatever
This is working but I was wondering if I can make this faster. I know that #parent is an element inside the #overParent which is already selected. Can I somehow use this to scan only the el #overparent for #parent then get the elements with the specified class?
Something like: $(".valueElement", "#parent", el) but according to documentation $ takes only 2 parameters.
If you are finding an element by ID then using just:
var $element = $('#id');
without providing any context to search will always
be the fastest way.
Similarly here where you are providing an ID as a context for your search then "#parent" is the fastest selector. You could theoretically use "#overParent > #parent" to achieve what you mean but it would actually mean more work to do and would be slower.
Just stick with the regular id selector:
$('#element');
It's still the fastest, even if you already have the parent element selected: http://jsperf.com/id-test-123
I'm just guessing here, but I think browsers use a lookup table to find elements by their id attribute.
Selectors like '#theId' don't make jQuery scan the document, as it uses document.getElementById.
If you want to look for an element knowing its id, even if you know its parent, always use $('#theid').
In fact, if you provide the parent as context, jQuery will call document.getElementById and check after that that the parent contains the found element. This is thus much slower.
From the source code :
// Speed-up: Sizzle("#ID")
if ( (m = match[1]) ) {
if ( nodeType === 9 ) {
elem = context.getElementById( m );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
// Handle the case where IE, Opera, and Webkit return items
// by name instead of ID
if ( elem.id === m ) {
results.push( elem );
return results;
}
} else {
return results;
}
} else {
// Context is not a document
if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
contains( context, elem ) && elem.id === m ) {
results.push( elem );
return results;
}
}
Similarly, if you want to select all elements with a class, don't specify the parent, this doesn't help.
In your case, as you seem to want to use the parent to restrict the set, simply use
$(".valueElement", "#parent");
according to the docs...
var el = $("#overParent");
this is extremly efficient thn having another selector attached to it....
For id selectors, jQuery uses the JavaScript function document.getElementById(), which is extremely efficient. When another selector is attached to the id selector, such as h2#pageTitle, jQuery performs an additional check before identifying the element as a match.
#Blender
If you are using classes and cache the context element it is faster providing the context:
http://jsperf.com/id-test-123/3
I am creating this JS function that creates an element
The function takes two parameters strName and objAttributes
function createElement( strName, objAttributes )
{
var elem = document.createElement(strName);
for ( var i in objAttributes )
elem.setAttribute(i, objAttributes[i]);
return elem;
}
This works fine in Fx, but not in MSIE
I know that the setAttibute method is buggy and the proposed workaround is
elem.attr = 'val';
But right now I have no idea how to write this inside my loop.
I have tried both elem.style and elem['style'] but none of them works.
Can anyone give me some advice,
thanks in advance
t
Use elem[i].
function createElement( strName, objAttributes )
{
var elem = document.createElement(strName);
for ( var i in objAttributes )
elem[i] = objAttributes[i];
return elem;
}
You can't just swap setting properties and setAttribute.
You have to be careful with setting properties on an element in place of using setAttribute.
Style properties and event handlers need to be carefully written, and those attributes that used to be minimized in html (disabled, multiple, readonly) have browser specific valid values.
Also, if you set element.class="mynewclass", you'll get an error, because class is a reserved javascript word, though it is perfectly safe to use it as a string in a setAttribute assignment. THe property name is '.className', and the proper name for a label's 'for' attribute is 'htmlFor'.
Let jQuery handle the cross-browser nonsense...
$(elem).attr(i, objAttributes[i]);