Need jQuery text() function to ignore hidden elements - javascript

I have a div set up something like this:
<div id="test"> <p>Hello</p> <p style="display: none">Goodbye</p> </div>
EDIT: To clarify, this is the simplest example. The div could have any arbitrary number of n deep nested children.
$('#test').getText() returns 'Hello Goodbye'. Here's a one liner to test in Firebug: jQuery('<div id="test"> <p>Hello</p> <p style="display: none">Goodbye</p> </div>').text()
This seems to be because what jQuery uses internally, textContent (for non IE), returns hidden elements as part of the text. Hrmph.
Is there a way to return the text content ignoring display:none'd elements? Basically I am trying to mimic the text you would get from highlighting the div with your mouse and copying to system clipboard. That ignores hidden text.
Interestingly, if you create a selection range and get the text from it, that also returns text inside display:none elements.
var range = document.body.createTextRange();
range.moveToElementText($('#test')[0]);
range.select();
console.log(range.toString()); // Also logs Hello Goodbye!
So creating a document selection range doesn't appear to do the same thing as highlighting with the mouse in terms of display:none elements. How do I get around this dirty pickle conundrum?
Edit: using .filter(':visible').text has been suggested, but it won't work for this scenario. I need the returned text to be EXACTLY what would come from a selection with the mouse. So for example:
$('<div>test1 <p>test2</p>\r\n <b>test3</b> <span style="display:none">none</span></div>').appendTo(document.body).children().filter(':visible').text()
returns
"test2test3"
When the output I actually want is
test1 test2
test3
linebreaks, whitespace and all, which come from the \r\n

Filter the elements using .filter(":visible").
Or use this:
$("#test :visible").text();
But the jQuery documentation advises us to use .filter() instead:
Because :visible is a jQuery extension and not part of the CSS specification,
queries using :visible cannot take advantage of the performance boost provided by the native DOM querySelectorAll() method. To achieve the best performance when using :visible to select elements, first select the elements using a pure CSS selector, then use .filter(":visible").

Use :visible in your selector as such:
$("#test > p:visible").text()
A Function example:
-- Edit:
http://jsfiddle.net/8H5ka/ ( Works on Chrome it displays "Hello" in Result )
If the above doesn't work:
http://jsfiddle.net/userdude/8H5ka/1/

If space isn't a major concern you could copy the markup, remove the hidden elements, and output that text.
var x = $('#test').clone();
x.filter(':not(:visible)').remove();
return x.text();

I had this problem and found this question, and it seems the actual solution is based on the provided answers but not actually written out. So here's a complete solution that worked for my situation, which is the same as the OP with the additional provision that elements may be invisible due to external styles based on DOM position. Example:
<style>.invisible-children span { display: none; }</style>
<div class="invisible-children">
<div id="test">Hello <span>Goodbye</span></div>
</div>
The solution is to:
Make a clone of the entire object.
Remove invisible objects in place; if we take #test out of the DOM before we remove invisible objects, jQuery might not know they're invisible because they will no longer match the CSS rules.
Get the text of the object.
Replace the original object with the clone we made.
The code:
var $test = $('#test');
// 1:
var $testclone = $test.clone();
// 2: We assume that $test is :visible and only remove children that are not.
$test.find('*').not(':visible').remove();
// 3:
var text = $test.text();
// 4:
$test.replaceWith($testclone);
// Now return the text...
return text;
// ...or if you're going to keep going and using the $test variable, make sure
// to replace it so whatever you do with it affects the object now in DOM and
// not the original from which we got the text after removing stuff.
$test = $testclone;
$test.css('background', 'grey'); // For example.

Here is how I did it with MooTools:
$extend(Selectors.Pseudo, {
invisible: function() {
if(this.getStyle('visibility') == 'hidden' || this.getStyle('display') == 'none') {
return this;
}
}
});
Element.implement({
getTextLikeTheBrowserWould = function() {
var temp = this.clone();
temp.getElements(':invisible').destroy();
return temp.get('text').replace(/ |&/g, ' ');
}
})

I search for that and found this question but without solution.
Solution for me is just get out of jquery to use DOM:
var $test = $('#test').get(0).innerText
or if more than on element in array of selector, you need a for loop and a merge but I guess that most of time it is the first version that you need.
var $test = $('#test').get().map(a => a.innerText).join(' ');

Related

javascript elements/tags array DOM node access

what's the different between using:
// assuming using elements/tags 'span' creates an array and want to access its first node
1) var arrayAccess = document.getElementsByTagName('elementName')[0]; // also tried property items()
vs
// assuming I assign an id value to the first span element/tag
// specifically calling a node by using it's id value
2) var idAccess = document.getElementById('idValue');
then if I want to change the text node....when using example 1) it will not work, for example:
arrayAccess.firstChild.nodeValue = 'some text';
or
arrayAccess.innerText/innerHTML/textContent = 'some text';
If I "access" the node through its id value then it seems to work fine....
Why is it that when using array it does not work? I'm new to javascript and the book I'm reading does not provide an answer.
Both are working,
In your first case you need to pass the tag name instead of the element name. Then only it will work.
There might be a case that you trying to set input/form elements using innerHTML. At that moment you need to use .value instead of innerHTML.
InnerHTML should be used for div, span, td and similar elements.
So your html markup example:
<div class="test">test</div>
<div class="test">test1</div>
<span id="test">test2</span>
<button id="abc" onclick="renderEle();">Change Text</button>
Your JS code:
function renderEle() {
var arrayAccess = document.getElementsByTagName('div')[0];
arrayAccess.innerHTML = "changed Text";
var idEle = document.getElementById('test');
idEle.innerHTML = "changed this one as well";
}
Working Fiddle
When you use document.getElementsByTagName('p'), the browser traverses the rendered DOM tree and returns a node list (array) of all elements that have the matching tag.
When you use document.getElementById('something'), the browser traverses the rendered DOM tree and returns a single node matching the ID if it exists (since html ID's are unique).
There are many differences when to use which, but one main factor will be speed (getElementById is much faster since you're only searching for 1 item).
To address your other question, you already have specified that you want the first element in the returned nodeList (index [0]) in your function call:
var arrayAccess = document.getElementsByTagName('elementName')[0];
Therefore, arrayAccess is already set to the first element in the returned query. You should be able to access the text by the following. The same code should work if you used document.getElementById to get the DOM element:
console.log(arrayAccess.textContent);
Here's a fiddle with an example:
http://jsfiddle.net/qoe30w2w/
Hope this helps!

How to select HTML with jQuery when it has no unique identifying attributes but has an HTML comment?

I am writing a custom script for a forum I frequently visit. It is designed to remove signatures from the board when I view it because they are distracting and annoying and they have no way to disable them in the options.
Anyway, I can run custom scripts using a helpful Chrome extension. I am able to modify any portions of the page where HTML nodes have classes, IDs, or even attributes with a little bit of unique information, but I can't seem to figure out how to select and remove the following HTML with jQuery.
<tr>
<td colspan="2">
<!--Signature-->
<div class="resultText">
<!-- sig -->
<div>Signature text</div>
<!-- / sig -->
</div>
</td>
</tr>
If there was a way I could grab the parent of <!--Signature--> that would be perfect but I'm not sure that's even possible.
There is one class resultText but that class is used wherever there is text entered by the user, not just in the signature. So I can't grab onto that.
Even if the resultText class is used elsewhere, I'd still recommend using a class selector as a starting point, otherwise you will be looking for comment nodes in the entire document.
From the matched elements, you can get their parents' contents(), use filter() to isolate the comment nodes (their nodeType property is equal to 8) and compare the value of these nodes to your Signature string:
$(".resultText").parent().each(function() {
var $this = $(this);
var signature = $this.contents().filter(function() {
return this.nodeType == 8 && this.nodeValue == "Signature";
});
if (signature.length) {
// Signature found, $this is the <td> element.
$this.closest("tr").remove(); // For example.
}
});
You can use .contents() to get all the child nodes of an element: http://api.jquery.com/contents
From the docs:
Get the children of each element in the set of matched elements,
including text and comment nodes.
$('tr').each(function (index, obj) {
$(this).children('td').contents();//this selects all the nodes in each TD element in the table, including comment nodes
});
Here is a demo: http://jsfiddle.net/NLhz9/1/
Since the script is basically just for you, use xpath to find the comments.
Try something like this:
var comment, comments = document.evaluate('//comment()', document);
while ((comment=comments.iterateNext())) {
if (comment.data=='Signature') { // found one
comment.parentNode.parentNode.removeChild(comment.parentNode);
}
}

jquery find() method

Please consider the following code :
<!DOCTYPE html>
<html>
<style>
p{ width:200px; }
</style>
<head>
<script src="http://code.jquery.com/jquery-latest.js"></script>
</head>
<body>
<p><span>Hello</span>, how are you?</p>
<p>Me? I'm <span>good</span>.</p>
<script>
$(document).ready(function(){$("p").find($("*")).andSelf().css("background- color","blue"); });
</script>
</body>
</html>
The output is the whole document turning into blue color while I only wanted the paragraph and span inside it to turn blue.
If I use $("p").find(" * ") instead of $("p").find($(" * ")) then everything shows according to my need. Can anyone work out the difference between the two approach?Thanks!
Note: Please everyone note that I know there are easier methods to do this stuff,but I just want to know why this didn't work..
Disclaimer: The other answers already suggest better selectors to
achieve your goal, but I understand you want to know why andSelf()
ends up matching all the elements in the document, so I'll try to
explain that.
First, as you know, andSelf() adds the previous set of elements on the stack to the current set. So, in your case, it seems it should add the <p> elements to the set containing their descendants:
$("p") // match the paragraphs
.find($("*")) // match all the elements that descend from a paragraph
.andSelf() // add the paragraphs to the elements above
However, the above assumes that find($("*")) is the previous set of elements, and that's simply not the case here. The first hint about this comes from the documentation for find():
As of jQuery 1.6, we can also filter the selection with a given jQuery
collection or element. With the same nested list as above, if we start
with:
var $allListElements = $('li');
And then pass this jQuery object to find:
$('li.item-ii').find( $allListElements );
This will return a jQuery collection which contains only the list
elements that are descendants of item II.
The last sentence is particularly interesting: it seems to imply that the jQuery object passed to find() is filtered in order to match the descendants of the elements in the original set. If that's indeed the case, the logic would be inverted, and the previous element set would end up being $allListElements instead of the set returned by find().
A look at the jQuery source code shows that's exactly what happens:
find: function(selector) {
var self = this, i, l;
if (typeof selector !== "string") {
return jQuery(selector).filter(function() {
for (i = 0, l = self.length; i < l; i++) {
if (jQuery.contains(self[i], this)) {
return true;
}
}
});
}
// [...]
}
So, when you write:
var elements = $("p").find($("*")).andSelf();
You're actually writing the equivalent of:
var self = $("p"), i, l;
var elements = $("*").filter(function() {
for (i = 0, l = self.length; i < l; i++) {
if ($.contains(self[i], this)) {
return true;
}
}
}).andSelf();
As you can see, the previous element set is actually $("*") instead of the set returned by find($("*")) because of the logic inversion. Therefore, all the elements in the document end up being legitimately added to the current set by andSelf().
You just need
$("p").css("background-color","blue");
To change the color or all the p tags in the document. Is there some specific reason for the way you have done it?
The $("*") has no context, so it selects every element in the document. You want to find all elements within the currently selected element, so you need to just pass the string to find.
However, it's completely unnecessary and you could just apply the style to the p (as the span is a child you don't to apply it to that too):
$("p").css("background-color","blue");
Note that in that line above I've used background-color with no spaces, unlike in your question. I'm guessing it was just a typo when you wrote the question, but it won't work if you put spaces in the property name.
You don't have to do any of that. Just do this.
$(function() { $("p").css('background-color', 'blue'); });
Note: $(function() {}); is the same as $(document).ready(function(){});
Edit: Since you have two, you may have to do this:
$(function() { $("p").each(item, function() { item.css('backround-color', 'blue'); })});
Edit2: Based on your comments, you want this:
$(function() { $("p").find('span').andSelf().css('background-color', 'blue'); });
The difference between $("p").find(" * ") and $("p").find($(" * ")) is that in the second one you're passing find() a jquery object instead of a regular selector string.
EDIT: I just tried it out. Looks like adding the andSelf() makes it select the entire document somehow. The logical process would be select p > find all elems inside that match everything in the document > select self(p) > color, but it seems to be going wrong at the select self bit.
I think it(the andSelf()) just selects the object passed to find(), which is $('*'), and so selects everything.
If you want to select all P's simply use
$(document).ready(function(){
$("p").css("background-color","blue");
});
No reason to complicate stuff
If you want to select the spans inside you can do something like
$(document).ready(function(){
$("p > span").css("background-color","blue");
});
** Update **
Your selector attribute in your find query is bad, you shouldn't have it like $("*") but only "*". However the $("p").find("*")... will only select any elements inside the <p> tag so trailing the find method with an andSelf will make the selection ambiguous.

How can I select an element based on its position?

How is it possible to select an element based on its position?
<div id="parent">
<p id="text">This is a text</p>
</div>
//CSS
#parent{width:100px;height:100px;position:absolute;left:100px;top:100px;}
Now using the left=100px,top=100px how can I select the div element using JQuery?
Something like this might work:
var el = document.elementFromPoint(100, 100);
var $el = $(el); // if you really want to use jQuery.
Documentation for document.elementFromPoint.
This might not be what you're looking for: it will only get the visible, topmost element at that point, and it will get whatever element occupies that space, not just one whose top-left corner is at that point. But, chances are, it'll help :).
It is supported by all major browsers, although you could get wacky results with Safari 4 or Opera 10.10.
The jQuery css() function allows you to retrieve the value of CSS properties, for example css('left') will return the left position of your element.
However, in your question you as about selecting the div element based on its location. I presume you mean something like $('left=100px'). This is not possible. jQuery uses CSS selectors which query the structure, not the style of the DOM.
You could loop through every element to check the position. Here is a quick sample function:
function getElementsByPosition(x,y) {
var elements=new Array();
$('*').each(function() {
if($(this).css('top')==x && $(this.css('left'))==y) {
elements[elements.length]=this;
}
});
return elements;
}
Update: Domenic's solution is much more elegant. Please consider his before using mine.

How to remove only the parent element and not its child elements in JavaScript?

Let's say:
<div>
pre text
<div class="remove-just-this">
<p>child foo</p>
<p>child bar</p>
nested text
</div>
post text
</div>
to this:
<div>
pre text
<p>child foo</p>
<p>child bar</p>
nested text
post text
</div>
I've been figuring out using Mootools, jQuery and even (raw) JavaScript, but couldn't get the idea how to do this.
Using jQuery you can do this:
var cnt = $(".remove-just-this").contents();
$(".remove-just-this").replaceWith(cnt);
Quick links to the documentation:
contents( ) : jQuery
replaceWith( content : [String | Element | jQuery] ) : jQuery
The library-independent method is to insert all child nodes of the element to be removed before itself (which implicitly removes them from their old position), before you remove it:
while (nodeToBeRemoved.firstChild)
{
nodeToBeRemoved.parentNode.insertBefore(nodeToBeRemoved.firstChild,
nodeToBeRemoved);
}
nodeToBeRemoved.parentNode.removeChild(nodeToBeRemoved);
This will move all child nodes to the correct place in the right order.
You should make sure to do this with the DOM, not innerHTML (and if using the jQuery solution provided by jk, make sure that it moves the DOM nodes rather than using innerHTML internally), in order to preserve things like event handlers.
My answer is a lot like insin's, but will perform better for large structures (appending each node separately can be taxing on redraws where CSS has to be reapplied for each appendChild; with a DocumentFragment, this only occurs once as it is not made visible until after its children are all appended and it is added to the document).
var fragment = document.createDocumentFragment();
while(element.firstChild) {
fragment.appendChild(element.firstChild);
}
element.parentNode.replaceChild(fragment, element);
$('.remove-just-this > *').unwrap()
More elegant way is
$('.remove-just-this').contents().unwrap();
Use modern JS!
const node = document.getElementsByClassName('.remove-just-this')[0];
node.replaceWith(...node.childNodes); // or node.children, if you don't want textNodes
oldNode.replaceWith(newNode) is valid ES5
...array is the spread operator, passing each array element as a parameter
Replace div with its contents:
const wrapper = document.querySelector('.remove-just-this');
wrapper.outerHTML = wrapper.innerHTML;
<div>
pre text
<div class="remove-just-this">
<p>child foo</p>
<p>child bar</p>
nested text
</div>
post text
</div>
Whichever library you are using you have to clone the inner div before removing the outer div from the DOM. Then you have to add the cloned inner div to the place in the DOM where the outer div was. So the steps are:
Save a reference to the outer div's parent in a variable
Copy the inner div to another variable. This can be done in a quick and dirty way by saving the innerHTML of the inner div to a variable or you can copy the inner tree recursively node by node.
Call removeChild on the outer div's parent with the outer div as the argument.
Insert the copied inner content to the outer div's parent in the correct position.
Some libraries will do some or all of this for you but something like the above will be going on under the hood.
And, since you tried in mootools as well, here's the solution in mootools.
var children = $('remove-just-this').getChildren();
children.replaces($('remove-just-this');
Note that's totally untested, but I have worked with mootools before and it should work.
http://mootools.net/docs/Element/Element#Element:getChildren
http://mootools.net/docs/Element/Element#Element:replaces
I was looking for the best answer performance-wise while working on an important DOM.
eyelidlessness's answer was pointing out that using javascript the performances would be best.
I've made the following execution time tests on 5,000 lines and 400,000 characters with a complexe DOM composition inside the section to remove. I'm using an ID instead of a class for convenient reason when using javascript.
Using $.unwrap()
$('#remove-just-this').contents().unwrap();
201.237ms
Using $.replaceWith()
var cnt = $("#remove-just-this").contents();
$("#remove-just-this").replaceWith(cnt);
156.983ms
Using DocumentFragment in javascript
var element = document.getElementById('remove-just-this');
var fragment = document.createDocumentFragment();
while(element.firstChild) {
fragment.appendChild(element.firstChild);
}
element.parentNode.replaceChild(fragment, element);
147.211ms
Conclusion
Performance-wise, even on a relatively big DOM structure, the difference between using jQuery and javascript is not huge. Surprisingly $.unwrap() is most costly than $.replaceWith().
The tests have been done with jQuery 1.12.4.
if you'd like to do this same thing in pyjamas, here's how it's done. it works great (thank you to eyelidness). i've been able to make a proper rich text editor which properly does styles without messing up, thanks to this.
def remove_node(doc, element):
""" removes a specific node, adding its children in its place
"""
fragment = doc.createDocumentFragment()
while element.firstChild:
fragment.appendChild(element.firstChild)
parent = element.parentNode
parent.insertBefore(fragment, element)
parent.removeChild(element)
If you are dealing with multiple rows, as it was in my use case you are probably better off with something along these lines:
$(".card_row").each(function(){
var cnt = $(this).contents();
$(this).replaceWith(cnt);
});
The solution with replaceWith only works when there is one matching element.
When there are more matching elements use this:
$(".remove-just-this").contents().unwrap();

Categories

Resources