Using jQuery to find matching text in any child text node? - javascript

I'm a bit rusty on my jQuery (been a few years!) I have to assume this has been asked 100 times but I'm not searching for the right terms it seems.
I'm trying to grab all of the descendant text nodes of an object, and then, if there's a match to a text string, do something with that object. Here's why I have now:
$(".my-item").each(function(){
var content = $(this).find('*').text();
if (content.indexOf('mySearchString'>0)){
//do something
}
})
I can't seem to get this to find all text, however. Just the direct descendents. Sample HTML:
<div class="my-item">
this text is part of the var 'content'
<div>
this text is not
</div>
</div>

You have to access the textContent of each node, and you could do directly in the filter, and return the matches, or use a regular each loop if you want to work with them directly
$(".my-item").each(function(){
var $children = $(this).children();
$children.contents().each(function(i,node){
if (node.nodeType === 3 && node.textContent.indexOf('mySearchString') !== -1) {
//do something
}
});
});
FIDDLE

Related

Replace non-code text on webpage

I searched through a bunch of related questions that help with replacing site innerHTML using JavaScript, but most reply on targetting the ID or Class of the text. However, my can be either inside a span or td tag, possibly elsewhere. I finally was able to gather a few resources to make the following code work:
$("body").children().each(function() {
$(this).html($(this).html().replace(/\$/g,"%"));
});
The problem with the above code is that I randomly see some code artifacts or other issues on the loaded page. I think it has something to do with there being multiple "$" part of the website code and the above script is converting it to %, hence breaking things.using JavaScript or Jquery
Is there any way to modify the code (JavaScript/jQuery) so that it does not affect code elements and only replaces the visible text (i.e. >Here<)?
Thanks!
---Edit---
It looks like the reason I'm getting a conflict with some other code is that of this error "Uncaught TypeError: Cannot read property 'innerText' of undefined". So I'm guessing there are some elements that don't have innerText (even though they don't meet the regex criteria) and it breaks other inline script code.
Is there anything I can add or modify the code with to not try the .replace if it doesn't meet the regex expression or to not replace if it's undefined?
Wholesale regex modifications to the DOM are a little dangerous; it's best to limit your work to only the DOM nodes you're certain you need to check. In this case, you want text nodes only (the visible parts of the document.)
This answer gives a convenient way to select all text nodes contained within a given element. Then you can iterate through that list and replace nodes based on your regex, without having to worry about accidentally modifying the surrounding HTML tags or attributes:
var getTextNodesIn = function(el) {
return $(el)
.find(":not(iframe, script)") // skip <script> and <iframe> tags
.andSelf()
.contents()
.filter(function() {
return this.nodeType == 3; // text nodes only
}
);
};
getTextNodesIn($('#foo')).each(function() {
var txt = $(this).text().trim(); // trimming surrounding whitespace
txt = txt.replace(/^\$\d$/g,"%"); // your regex
$(this).replaceWith(txt);
})
console.log($('#foo').html()); // tags and attributes were not changed
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="foo"> Some sample data, including bits that a naive regex would trip up on:
foo<span data-attr="$1">bar<i>$1</i>$12</span><div>baz</div>
<p>$2</p>
$3
<div>bat</div>$0
<!-- $1 -->
<script>
// embedded script tag:
console.log("<b>$1</b>"); // won't be replaced
</script>
</div>
I did it solved it slightly differently and test each value against regex before attempting to replace it:
var regEx = new RegExp(/^\$\d$/);
var allElements = document.querySelectorAll("*");
for (var i = 0; i < allElements.length; i++){
var allElementsText = allElements[i].innerText;
var regExTest = regEx.test(allElementsText);
if (regExTest=== true) {
console.log(el[i]);
var newText = allElementsText.replace(regEx, '%');
allElements[i].innerText=newText;
}
}
Does anyone see any potential issues with this?
One issue I found is that it does not work if part of the page refreshes after the page has loaded. Is there any way to have it re-run the script when new content is generated on page?

Appending elements to DOM with indentation/spacing

Here is an example. Check the console for the result. The first two divs (not appended; above the <script> in the console) have the proper spacing and indention. However, the second two divs do not show the same formatting or white space as the original even though they are completely the same, but appended.
For example the input
var newElem = document.createElement('div');
document.body.appendChild(newElem);
var another = document.createElement('div');
newElem.appendChild(another);
console.log(document.body.innerHTML);
Gives the output
<div><div></div></div>
When I want it to look like
<div>
<div></div>
</div>
Is there any way to generate the proper white space between appended elements and retain that spacing when obtaining it using innerHTML (or a possible similar means)? I need to be able to visually display the hierarchy and structure of the page I'm working on.
I have tried appending it within an element that is in the actual HTML but it has the same behavior
I'd be okay with doing it using text nodes and line breaks as lincolnk suggested, but it needs to affect dynamic results, meaning I cannot use the same .createTextNode(' </br>') because different elements are in different levels of the hierarchy
No jQuery please
I think you're asking to be able to append elements to the DOM, such that the string returned from document.body.innerHTML will be formatted with indentation etc. as if you'd typed it into a text editor, right?
If so, something like this might work:
function indentedAppend(parent,child) {
var indent = "",
elem = parent;
while (elem && elem !== document.body) {
indent += " ";
elem = elem.parentNode;
}
if (parent.hasChildNodes() && parent.lastChild.nodeType === 3 && /^\s*[\r\n]\s*$/.test(parent.lastChild.textContent)) {
parent.insertBefore(document.createTextNode("\n" + indent), parent.lastChild);
parent.insertBefore(child, parent.lastChild);
} else {
parent.appendChild(document.createTextNode("\n" + indent));
parent.appendChild(child);
parent.appendChild(document.createTextNode("\n" + indent.slice(0,-2)));
}
}
demo: http://jsbin.com/ilAsAki/28/edit
I've not put too much thought into it, so you might need to play with it, but it's a starting point at least.
Also, i've assumed an indentation of 2 spaces as that's what you seemed to be using.
Oh, and you'll obviously need to be careful when using this with a <pre> tag or anywhere the CSS is set to maintain the whitespace of the HTML.
You can use document.createTextNode() to add a string directly.
var ft = document.createElement('div');
document.body.appendChild(ft);
document.body.appendChild(document.createTextNode(' '));
var another = document.createElement('div');
document.body.appendChild(another);
console.log(document.body.innerHTML);

What is the Javascript Regexp for finding the attributes in a <span> tags?

I have a <div contenteditable="true> that I am trying to use to get text from users. I would use a simple <textarea>, but I need line-breaks and eventually lists and tables etc. I am trying to build something that is semi a rich-text editor, but not a full fledged one. I do not want to use a rich text editor.
I am trying to eliminate the attributes on <span> tags from the text that is typed into the <div contenteditable="true> . Is there a way to do that with Regexp? I was having difficulties coming up with a Regexp because I can't figure out how to make it so that the string starts with <span and ends with > and any number of characters can be in between. Is there a way to combine that in one Regexp? I came up with /^<span >$/ but that does not work because there is no division between the two strings. Would something like this work: /^[<span][>]$/g?
Use DOM functions for it. Here's some code using jQuery to make it easier and nicer to read:
$('#your-div span').each(function() {
var elem = this, $elem = $(this);
$.each(elem.attributes, function(i, attr) {
if(attr) {
$elem.removeAttr(attr.name);
}
});
});
Demo: http://jsfiddle.net/ThiefMaster/qfsAb/
However, in your case you might want to remove attributes not only from spans but from all elements unless the attribute is e.g. align or href.
So, here's some JS for that: http://jsfiddle.net/ThiefMaster/qfsAb/1/
$('#your-div').children().each(function() {
var elem = this, $elem = $(this);
$.each(elem.attributes, function(i, attr) {
if(attr && attr.name != 'href' && attr.name != 'align') {
$elem.removeAttr(attr.name);
}
});
});
Parse the HTML and then strip out the attributes afterwards. If you're doing this in a browser, you have a high grade HTML parser right at your disposal (or you have IE), so use it!
I made a working demo at http://jsfiddle.net/b3DSQ/
$('#editor span').each(function(i, el) {
var attrs = el.attributes;
$.each(attrs, function(i, a) {
$(el).removeAttr(a.name);
});
});
[I changed an earlier version which copied the contents into memory, edited, and then replaced - hadn't realised that the stuff typed into the div was automatically valid in the DOM].
Try this:
your_string.replace(/<span[^>]*>/g, '<span>');
This will break however if the user writes something like <span title="Go > there">

How to get text around an element?

If I have
<div id='wrapper'>
<fieldset id='fldset'>
<legend>Title</legend>
Body text.
</fieldset>
</div>
How could I retrieve the "Body text" without retrieving the text inside of legend tag?
For instance, if I wanted to see if the text inside the fieldset contains the word "body" and change that word, while ignoring whatever might be in the legend? innerHTML retrieves all text from all children nodes.
Use of jQuery wouldn't be a problem.
$("#fldset").contents().eq(2).text();
Without a library --
var fldset = document.getElementById("fldset"),
txt = fldset.lastChild.textContent || fldset.lastChild.innerText;
alert(txt);
This will get all the text nodes of fldset leaving out any other element and it's content:
var fldsetContent = $('#fldset').contents();
var text = '';
$(fldsetContent).each( function(index, item) {
if( item.nodeType == 3 ) text += $.trim($(item).text());
});
alert( text );
Live example
$('#fldset').clone().find('legend').remove().end().text()
But you should also search around the SO
Using .text() to retrieve only text not nested in child tags
Clip content with jQuery
I can't think of a way other than
$("#result").html($("#wrapper").text().replace($("legend").text(),""));
but there should be a more elegant way. You can also create a new element as a copy of this one, remove all the children and get text. Hmm... That would be:
var newElement = $("#fldset").clone();
newElement.children().remove();
$("#result").html(newElement.text());
So doesn't matter how many and which type of children node has, this would work. Here: http://www.jsfiddle.net/wFV4c/
To turn all plain text nodes inside the field set red:
jQuery.each($('#fldset').contents(),function(index,value){
if(value.textContent == value.nodeValue){
$(this).wrap('<span style="color:red;" />')
}
});

Finding the textNode which the cursor is currently over

Let's say I have the following...
<div id="text">Some text</div>
When my mouse goes over Some, Some will be returned and the same with text.
Is this possible without putting each node into it's own element?
That is pretty much impossible. Since in Javascript you just know that you are hovering over an element (div and/or a textnode in this case), you can't know which word is hovered just like that.
Maybe with alot of effort and some geeky hacks on offsets and/or event.pageX/Y, but I would go for the solution you mentioned yourself, wrapping each word into its own element.
var $text = $('#text');
$text.html($text.text().replace(/\b(\w+)\b/g, "<span class='word'>$1</span>"));
$('.word').bind('mouseenter', function(e){
alert($(this).text());
});
This isn't possible without either wrapping the two nodes in elements or somehow determining the exact position of the text nodes and then binding to some parent's click event. The former option is simpler. Just wrap them in spans:
<div id="text"><span>Some<span> <span>text</span></div>
Or, if you need to wrap them all automatically:
jQuery.fn.getTextNodes = function(){
return this.not('script').contents().map(function(){
var n = this.nodeType;
return n === 3 ? this : n === 1 ? jQuery(this).getTextNodes().get() : [];
});
};
jQuery('#text').getTextNodes().replaceWith(function(){
return jQuery(this.data.replace(/\S+/g, '<span>$&</span>')).filter('span').click(function(){
alert('You just clicked on the word: ' + jQuery.text([this]));
}).end();
});​ ​
Demo: http://jsfiddle.net/PYKqM/

Categories

Resources