jQuery selector for an element that directly contains text? - javascript

I was able to get this partially working using the :contains selector, but my problem is if an element contains an element that contains the text it is still returned. For example:
$('div:contains("test")')
Will select both divs below:
<div>something else
<div>test</div>
</div>
fiddle: http://jsfiddle.net/TT7dR/
How can I select only divs that "directly" contain the text? Meaning that in the above example only the child div would be selected.
UPDATE:
Just to clarify, if I were searching for the text "something else" instead of "test" then I would like to only find the parent div.

$('div>:contains("test")') is not a general solution, it only works for your specific example. It still matches any element whose descendants contain the text test, as long as its parent is a div.
There is in fact currently no selector that will select only direct parents of text nodes containing your target text. To do it you would have to walk the DOM tree yourself checking each text node you find for the target text, or write a plugin to do the same. It'd be slow, but then not as slow as :contains already is (it's not a standard CSS selector so you don't get browser-native fast selector support).
Here's a plain DOM function you could use as a starting point. It might be improved to find text in adjacent (non-normalised) text nodes, or to hide it in a plugin/selector-extension.
function findElementsDirectlyContainingText(ancestor, text) {
var elements= [];
walk(ancestor);
return elements;
function walk(element) {
var n= element.childNodes.length;
for (var i= 0; i<n; i++) {
var child= element.childNodes[i];
if (child.nodeType===3 && child.data.indexOf(text)!==-1) {
elements.push(element);
break;
}
}
for (var i= 0; i<n; i++) {
var child= element.childNodes[i];
if (child.nodeType===1)
walk(child);
}
}
}

Just to complete the knowledge base. If you need to get all DOM elements within the body (not only DIVs) that contain specific text or characters you can use:
function getNodesThatContain(text) {
var textNodes = $(document).find(":not(iframe, script)")
.contents().filter(
function() {
return this.nodeType == 3
&& this.textContent.indexOf(text) > -1;
});
return textNodes.parent();
};
console.log(getNodesThatContain("test"));
Hope that helps.
jsfiddle: http://jsfiddle.net/85qEh/2/
Credits: DMoses

You might have to do an in-efficient query. Do not use this solution if someone finds a selector that manages to filter out child elements: http://viralpatel.net/blogs/2011/02/jquery-get-text-element-without-child-element.html
$("div:contains('test')")
.clone() //clone the element
.children() //select all the children
.remove() //remove all the children
.end() //again go back to selected element
.filter(":contains('test')")
edit: that snippet above is just to test the element, in implementation it would look more like this: http://jsfiddle.net/rkw79/TT7dR/6/
$("div:contains('test')").filter(function() {
return (
$(this).clone() //clone the element
.children() //select all the children
.remove() //remove all the children
.end() //again go back to selected element
.filter(":contains('test')").length > 0)
}).css('border', 'solid 1px black');

try adding the greater than:
$('div>:contains("test")')

Finds specific element, but not parents
var elementsContainingText = ($(':contains("' + text + '")', target)).filter(function() {
return $(this).contents().filter(function() {return this.nodeType === 3 && this.nodeValue.indexOf(text) !== -1; }).length > 0;
});

This seems to work for me:
$('div >:contains("test")');
http://jsfiddle.net/TT7dR/1/
This forces the matched :contains selector to be a direct child of the <div> element

Try the following:
$("div>div:contains(test):only-of-type")

Add more alternative:
if($(selector).text().trim().length) {
var thetext = $(selector).contents().filter(function(){
return this.nodeType === 3;
}).text().trim();
console.log(thetext);
}
It will select the text only and remove any element with tag!
Reference

You can simply select the element that doesn't have your element
$('div:contains("test"):not(:has(> div))')

less code to write (but with a little limitation):
let selector = $('div:contains("test")');
selector.not(selector.has('div:contains("test")'))
Just use the jQuery function (.has) because the css :has is experimental:
https://developer.mozilla.org/en-US/docs/Web/CSS/:has#Browser_compatibility
Limitation:
When you have a structure like this:
<div>
<div>test</div>
test
</div>
Then only the inner div - Element will be found by this solution. This is because there is still an Element - Child of the div that :contains the string "test".

Related

Jquery remove the innertext but preserve the html

I have something like this.
<div id="firstDiv">
This is some text
<span id="firstSpan">First span text</span>
<span id="secondSpan">Second span text</span>
</div>
I want to remove 'This is some text' and need the html elements intact.
I tried using something like
$("#firstDiv")
.clone() //clone the element
.children() //select all the children
.remove() //remove all the children
.end() //again go back to selected element
.text("");
But it didn't work.
Is there a way to get (and possibly remove, via something like .text("")) just the free text within a tag, and not the text within its child tags?
Thanks very much.
Filter out text nodes and remove them:
$('#firstDiv').contents().filter(function() {
return this.nodeType===3;
}).remove();
FIDDLE
To also filter on the text itself, you can do:
$('#firstDiv').contents().filter(function() {
return this.nodeType === 3 && this.nodeValue.trim() === 'This is some text';
}).remove();
and to get the text :
var txt = [];
$('#firstDiv').contents().filter(function() {
if ( this.nodeType === 3 ) txt.push(this.nodeValue);
return this.nodeType === 3;
}).remove();
Check out this fiddle
Suppose you have this html
<parent>
<child>i want to keep the child</child>
Some text I want to remove
<child>i want to keep the child</child>
<child>i want to keep the child</child>
</parent>
Then you can remove the parent's inner text like this:
var child = $('parent').children('child');
$('parent').html(child);
Check this fiddle for a solution to your html
var child = $('#firstDiv').children('span');
$('#firstDiv').html(child);
PS: Be aware that any event handlers bounded on that div will be lost as you delete and then recreate the elements
Why try to force jQuery to do it when it's simpler with vanilla JS:
var div = document.getElementById('firstDiv'),
i,
el;
for (i = 0; i< div.childNodes.length; i++) {
el = div.childNodes[i];
if (el.nodeType === 3) {
div.removeChild(el);
}
}
Fiddle here: http://jsfiddle.net/YPKGQ/
Check this out, not sure if it does what you want exactly... Note: i only tested it in chrome
http://jsfiddle.net/LgyJ8/
cleartext($('#firstDiv'));
function cleartext(node) {
var children = $(node).children();
if(children.length > 0) {
var newhtml = "";
children.each(function() {
cleartext($(this));
newhtml += $('<div/>').append(this).html();
});
$(node).html(newhtml);
}
}

How to change innerHTML of childNodes in case some childnodes without tags?

That is my example of problem
<div onclick="this.childNodes(0).innerHTML='0';">
1<b>2</b>3<b>4</b>5
</div>
as you see, two childnodes ("2" and "4") are tagged, others are simple text.
The question is how to change innerHTML of tagged and untagged nodes (texts) in sertain div container without touching other nodes/texts?
Essentially, you'll use the data(text) property for text nodes (nodeType 3) and innerHTML otherwise (fiddle):
<div onclick="this.childNodes[0][this.childNodes[0].nodeType === 3 ? 'data' : 'innerHTML'] = '0'">
1<b>2</b>3<b>4</b>5
</div>​
[edit] I'm getting really tired of everyone offering libraries as solutions when all that's required is a simple explanation of a basic concept, e.g.: text-nodes and element nodes have differing content properties, i.e.: data and innerHTML.
I wrote a lib called Linguigi. It would be as easy as
new Linguigi(element).eachText(function(text) {
if(this.parentNode.tagName === 'B') {
return "BACON";
}
});
which turns the text of all text nodes inside b-tags to "BACON". You get the original content as "text" parameter and could transform that.
http://jsfiddle.net/Kz2jX/
BTW: You should get rid of the inline event handling (onclick attribute)
You can cycle through each of the nodes recursively, checking their nodeType property in turn and updating the nodeValue property with '0' if the node is a text node (indicated by nodeType == 3).
Assuming you have this HTML:
<div onclick="doReplace(this)">
1<b>2</b>3<b>4</b>5
</div>
You can then write a simple replace function that calls itself recursively, like so:
window.doReplace = function (rootNode) {
var children = rootNode.childNodes;
for(var i = 0; i < children.length; i++) {
var aChild = children[i];
if(aChild.nodeType == 3) {
aChild.nodeValue = '0';
}
else {
doReplace(aChild);
}
}
}
A working fiddle can be found here: http://jsfiddle.net/p9YCn/1/

jQuery .text('') on multiple nested elements

I wanted to remove all text from html and print only tags. I Ended up writing this:
var html = $('html');
var elements = html.find('*');
elements.text('');
alert(html.html());
It only out prints <head></head><body></body>. Was not that suppose to print all tags. I've nearly 2000 tags in the html.
var elements = html.find('*');
elements.text('');
That says "find all elements below html, then empty them". That includes body and head. When they are emptied, there are no other elements on the page, so they are the only ones that appear in html's content.
If you really wnat to remove all text from the page and leave the elements, you'll have to do it with DOM methods:
html.find('*').each(function() { // loop over all elements
$(this).contents().each(function() { // loop through each element's child nodes
if (this.nodeType === 3) { // if the node is a text node
this.parentNode.removeChild(this); // remove it from the document
}
});
})
You just deleted everything from your dom:
$('html').find('*').text('');
This will set the text of all nodes inside the <html> to the empty string, deleting descendant elements - the only two nodes that are left are the two children of the root node, <head></head> and <body></body> with their empty text node children - exactly the result you got.
If you want to remove all text nodes, you should use this:
var html = document.documentElement;
(function recurse(el) {
for (var i=0; i<el.childNodes.length; i++) {
var child = el.childNodes[i];
if (child.nodeType == 3)
el.removeChild(child);
else
recurse(child);
}
})(html);
alert(html.outerHTML);
Try this instead
$(function(){
var elements = $(document).find("*");
elements.each(function(index, data){
console.log(data);
});
});
This will return all the html elements of page.
lonesomeday seems to have the right path, but you could also do some string rebuilding like this:
var htmlString=$('html').html();
var emptyHtmlString="";
var isTag=false;
for (i=0;i<htmlString.length;i++)
{
if(htmlString[i]=='<')
isTag=true;
if(isTag)
{
emptyHtmlString+=htmlString[i];
}
if(htmlString[i]=='>')
isTag=false;
}
alert(emptyHtmlString);

Using Javascript, what is the method to get an element, based on the text between the opening and closing tags?

I'm a beginner, and couldn't find the answer after searching.
In my example, I'm looking for an
some text here
I'd want to find this particular element, so I can do stuff with it.
Edit: The only thing I know that's unique about the element for sure, is the text "some text here", that's why I want to find it based on that.
Put id on the element:
<a href="bla" onclick="dah" id='myEl'>some text here</a>
From javascript:
var myEl = document.getElementById('myEl') // gives the element
You can also use psuedo selector :contains, with the jQuery library.
Example 2
$('div:contains("test")').css('background-color', 'red');​
http://jsfiddle.net/9z5du/
Example 2
<script>
$("div:contains('John')").css("text-decoration", "underline");
</script>
If you know that the element is a link, you can first call getElementsByTagName [docs] to narrow down your search:
var elements = document.getElementsByTagName('a');
Then you have to iterate over the elements and test which one contains the next you are looking for:
var element = null;
for(var i = 0, l = elements.length; i < l; i++) {
if(elements[i].innerHTML === 'some text here') {
// found the element
element = elements[i];
break;
}
}
if(element) {
// found the element, lets do something awesome with it
}
There are multiple ways to get the content of an element, using Node#innerText (IE) or Node#textContent (W3C) is another option. You might have to trim the text first before you compare it.
If the HTML is as shown in your post,
if(elements[i].firstChild || elements[i].firstChild.nodeValue)
is even more elegant.
The MDN documentation about the DOM might be helpful.
If you can modify the HTML then adding an ID and using getElementById would be the better option.
Try this
function getit()
{
var elems = document.getElementsByTagName('a');
for(var i=0; i<elems.length; i++)
{
var text = elems[i].childNodes[0] != null ? elems[i].childNodes[0].nodeValue : '';
if(text == "some text here")
doSomethingWith(elems[i]);
}
}

Javascript replace an element with a string

How can you change this
<div id='myDiv'><p>This div has <span>other elements</span> in it.</p></div>
into this
<div id='myDiv'>This div has other elements in it.</div>
hopefully using something like this
var ele = document.getElementById('myDiv');
while(ele.firstChild) {
replaceFunction(ele.firstChild, ele.firstChild.innerHTML);
}
function replaceFunction(element, text) {
// CODE TO REPLACE ELEMENT WITH TEXT
}
You can use innerText and textContent if you want to remove all descendant nodes, but leave the text:
// Microsoft
ele.innerText = ele.innerText;
// Others
ele.textContent = ele.textContent;
If you only want to flatten certain ones, you can define:
function foldIntoParent(element) {
while(ele.firstChild) {
ele.parentNode.insertBefore(ele.firstChild, ele);
}
ele.parentNode.removeChild(ele);
}
should pull all the children out of ele into its parent, and remove ele.
So if ele is the span above, it will do what you want. To find and fold all the element children of #myDiv do this:
function foldAllElementChildren(ele) {
for (var child = ele.firstChild, next; child; child = next) {
next = child.nextSibling;
if (child.nodeType === 1 /* ELEMENT */) {
foldIntoParent(child);
}
}
}
foldAllElementChildren(document.getElementById('myDiv'));
If you're not opposed to using jQuery, stripping the HTML from an element and leaving only the text is as simple as:
$(element).html($(element).text());
You can just take the innerText
console.log(ele.innerText)

Categories

Resources