NOTE: The first requirement for this is that it not use jQuery.
I also do not want to use .innerHTML if I can avoid it (even if that requires a bit more code)
I have divs within a page (multiple locations) that will be something like this:
<div class="p-user-content">John Smith current working on ticket LQ-1954</div>
... again, multiple locations ...
<div class="p-user-content">Sally Jones assigned GM-3398 yesterday</div>
There will be simple text, and tags like <a> mixed in with the text as shown.
The following script successfully identifies all the "text nodes":
var el = document.getElementsByClassName('p-user-content');
for (var i in el) {
if (typeof el[i].childNodes === 'undefined') continue
for (var k in el[i].childNodes) {
if (typeof el[i].childNodes[k] === 'function') continue
if (el[i].childNodes[k].nodeType !== Node.TEXT_NODE) continue
/**
This successfully gets the nodeValue
*/
console.log(el[i].childNodes[k].nodeValue)
}
}
What I want to do is split the text node word-by-word, and then convert values like LQ-1954 and GM-3398 to anchor elements, and then replace the modified text and anchor links back into the existing div. I'm able to split the text up and do the matching part, but how would I
build the nodes back with the new links and
replace it back into the same div?
You can use .replace
document.body.outerHTML = document.body.outerHTML.replace(/(LQ-1954)/g , '<a> $1 </a>')
If you must avoid manipulating html by changing the value of innerHTML,
create a new element and replace the old element with it:
function addLinks(){
var el = document.getElementsByClassName('p-user-content');
for (var i in el) {
if (typeof el[i].childNodes === 'undefined') continue
let value = el[i].innerHTML.replace(/[A-Z]{2}-[\d]{4}/g, (match)=>'' + match + '');
let newDiv = document.createElement('div')
newDiv.insertAdjacentHTML("beforeend", value);
el[i].replaceChildren(newDiv)
}
}
<div class="p-user-content">John Smith current working on ticket LQ-1954</div>
... again, multiple locations ...
<div class="p-user-content">Sally Jones assigned GM-3398 yesterday</div>
<button onclick="addLinks()">Add Links</button>
Simple way using innerHTML:
function addLinks(){
var el = document.getElementsByClassName('p-user-content');
for (var i in el) {
if (typeof el[i].childNodes === 'undefined') continue
el[i].innerHTML = el[i].innerHTML.replace(/[A-Z]{2}-[\d]{4}/g, (match)=>'' + match + '');
}
}
<div class="p-user-content">John Smith current working on ticket LQ-1954</div>
... again, multiple locations ...
<div class="p-user-content">Sally Jones assigned GM-3398 yesterday</div>
<button onclick="addLinks()">Add Links</button>
Related
I know how to find specific text, but I'm really struggling to find elements with specific text
for example: <span class="foo">Special</span>
Is there example like this script below to find elemements with text?
var txt = window.document.body.innerHTML;
if (txt.match(/Special/)) {
// do something if true
alert("Found");
} else {
// do something if false
alert("Sorry");
};
You could e.g. catch every element inside your document and check if any of them contains given text.
var elems = document.querySelectorAll("*"),
res = Array.from(elems).find(v => v.textContent == 'Special');
console.log(res ? 'found!' : 'not found');
<span class="foo">Special</span>
<span class="foo">Else</span>
With pure Javascript just grab element's parent like this:
var x = this.parentElement.nodeName; //x=="span"
or
var x = this.parentElement; //x == <span> object
With jQuery just grab element's parent like this:
$(this).parents("span");
So if you add above line inside your if you can get span element as object or as text.
I know this question has been asked but almost all of the answers use the wrap function of jQuery. I'm trying to figure out a plain javascript way of doing this:
<div id="content">
"hey"
<br>
<br>
"foo"
"bar"
</div>
<script>
function mainFocusOut(e) {
//here or any other focus outs...create a function to look between <br> tags and put everything into divs
if (e == 'undefined') e = window.event;
var target = e.target || e.srcElement;
for (var i = 0; i < target.childNodes.length; i++) {
//I'm not sure if I need a closure function here inside the loop so it works on each individual textNode
(function(i) {
var curNode = target.childNodes[i];
if (curNode.nodeName === "#text") {
var element = document.createElement("div");
element.appendChild(curNode);
target.insertBefore(element, curNode);
//NotFoundError: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
}
})(i);
}
}
</script>
Of course the desired output is all the text nodes to be between div tags, same position. What am I doing wrong? And is it as simple as what I have here, just a simple loop?
It seems to me, that all you need to do is switch around these two lines:
element.appendChild(curNode);
target.insertBefore(element, curNode);
so it becomes:
target.insertBefore(element, curNode);
element.appendChild(curNode);
Because right now, you append the curNode to the new div, before placing the div in the correct position. This results in:
<div>"hey"</div>
That's all great, but then in order to put the div on the right place, you can not place it before "hey" anymore, since you just put that inside the div.
By swapping the two lines, you first place the div in front of the "hey":
<div></div>"hey"
And then you position "hey" correctly inside the div:
<div>"hey"</div>
I hope this helps!
The following is a solution that depends on the string object methods split and indexOf
<script>
function enclose(id, cutString){
out = '';
target = document.getElementById(id);
text = target.innerHTML;
if (text.indexOf(cutString) == -1){
return true;
}
parts = text.split(cutString);
for (i = 0; i < parts.length; i++){
out += '<div class="new-div">'+parts[i]+'</div>';
}
target.innerHTML = out;
}
</script>
Checkout this DEMO
Update
The follwing demo is an update to neglect the empty parts using match(/\w/gi) conditional check through the for loop. Checkout it here.
I’m trying to wrap multiple instances of a string found in html around a tag (span or abbr) using pure JS. I have found a way to do it by using the code:
function wrapString() {
document.body.innerHTML = document.body.innerHTML.replace(/string/g, ‘<tag>string</tag>');
};
but using this code messes with a link’s href or an input’s value so I want to exclude certain tags (A, INPUT, TEXTAREA etc.).
I have tried this:
function wrapString() {
var allElements = document.getElementsByTagName('*');
for (var i=0;i<allElements.length;i++){
if (allElements[i].tagName != "SCRIPT" && allElements[i].tagName != "A" && allElements[i].tagName != "INPUT" && allElements[i].tagName != "TEXTAREA") {
allElements[i].innerHTML = allElements[i].innerHTML.replace(/string/g, ‘<span>string</span>');
}
}
}
but it didn’t work as it gets ALL the elements containing my string (HTML, BODY, parent DIV etc.), plus it kept crushing my browser. I even tried with JQuery's ":containing" Selector but I face the same problem as I do not know what the string's container is beforehand to add it to the selector.
I want to use pure JavaScript to do that as I was planning on using it as a bookmark for quick access to any site but I welcome all answers regarding JQuery and other frameworks as well.
P.S. If something like that has already been answered I couldn't find it...
This is a quite complicated problem actually (you can read this detailed blog post about it).
You need to:
recurse on the dom tree
find all text nodes
do your replace on its data
make the modified data into dom nodes
insert the dom nodes to the tree, before the original text node
remove the original text node
Here is a demo fiddle.
And if you still need tagName based exclusions, look at this fiddle
The code:
function wrapInElement(element, replaceFrom, replaceTo) {
var index, textData, wrapData, tempDiv;
// recursion for the child nodes
if (element.childNodes.length > 0) {
for (index = 0; index < element.childNodes.length; index++) {
wrapInElement(element.childNodes[index], replaceFrom, replaceTo);
}
}
// non empty text node?
if (element.nodeType == Node.TEXT_NODE && /\S/.test(element.data)) {
// replace
textData = element.data;
wrapData = textData.replace(replaceFrom, replaceTo);
if (wrapData !== textData) {
// create a div
tempDiv = document.createElement('div');
tempDiv.innerHTML = wrapData;
// insert
while (tempDiv.firstChild) {
element.parentNode.insertBefore(tempDiv.firstChild, element);
}
// remove text node
element.parentNode.removeChild(element);
}
}
}
function wrapthis() {
var body = document.getElementsByTagName('body')[0];
wrapInElement(body, "this", "<span class='wrap'>this</span>");
}
I am using the highlighter module available in Rangy, and it work great in creating a highlight for the text that is selected.
In terms of changes to the html, the selected text is replaced by a span tag like the following for example:
the selected text is <span class="highlight">replaced by a span tag</span> like the
What I want to do is get a reference to the span element once it has been created so I can do some other stuff with it. How can this be done?
Please note there may be other spans with or without the highlight tag elsewhere, so these cannot be used to find it.
The important part of the code I have to create the highlight for the selected text is:
var highlighter = null;
var cssApplier = null;
rangy.init();
cssApplier = rangy.createCssClassApplier("highlight", { normalize: true });
highlighter = rangy.createHighlighter(document, "TextRange");
highlighter.addClassApplier(cssApplier);
var selection = rangy.getSelection();
highlighter.highlightSelection("highlight", selection);
I was waiting for #TimDown to update his answer with working code. But as he hasn't done that then I will post some myself (which is based on his answer).
The following function will return an array of highlight elements that have been creating, assuming the selection is still valid:
function GetAllCreatedElements(selection) {
var nodes = selection.getRangeAt(0).getNodes(false, function (el) {
return el.parentNode && el.parentNode.className == "highlight";
});
var spans = [];
for (var i = 0; i < nodes.length; i++) {
spans.push(nodes[i].parentNode);
}
return spans;
}
There is no guarantee that only one <span> element will be created: if the selection crosses element boundaries, several spans could be created.
Anyway, since the selection is preserved, you could use the getNodes() method of the selection range to get the spans:
var spans = selection.getRangeAt(0).getNodes([1], function(el) {
return el.tagName == "SPAN" && el.className == "highlight";
});
I have an HTML-document:
<html>
<body>
<p>
A funny joke:
<ul>
<li>do you know why six is afraid of seven?
<li>because seven ate nine.
</ul>
Oh, so funny!
</p>
</body>
</html>
Now I want to identify the first occurence of "seven" and tag it with
<span id="link1" class="link">
How can this be accomplished?
Do you have to parse the DOM-tree or is it possible to get the whole code within the body-section and then search for the word?
In both cases, after I found the word somewhere, how do you identify it and change it's DOM-parent to span (I guess that's what has to be done) and then add the mentioned attributes?
It's not so much a code I would expect, but what methods or concepts will do the job.
And I am not so much intersted in a framework-solution but in a pure javascript way.
You need to find a DOM node with type TEXT_NODE (3) and containig your expected word. When you need to split a that node into three ones.
First is a TEXT_NODE which contains a text before the word you search, second one is a SPAN node containing the word you search, and third one is another TEXT_NODE containing an original node's tail (all after searched word).
Here is a source code...
<html>
<head>
<style type="text/css">
.link {
color: red;
}
</style>
</head>
<body>
<p>
A funny joke:
<ul>
<li>do you know why six is afraid of seven?
<li>because seven ate nine.
</ul>
Oh, so funny!
</p>
<script type="text/javascript">
function search(where, what) {
var children = where.childNodes;
for(var i = 0, l = children.length; i < l; i++) {
var child = children[i], pos;
if(child.nodeType == 3 && (pos = child.nodeValue.indexOf(what)) != -1) { // a TEXT_NODE
var value = child.nodeValue;
var parent = child.parentNode;
var start = value.substring(0, pos);
var end = value.substring(pos + what.length);
var span = document.createElement('span');
child.nodeValue = start;
span.className = 'link';
span.id = 'link1';
span.innerHTML = what;
parent.appendChild(span);
parent.appendChild(document.createTextNode(end));
return true;
} else
if(search(child, what))
break;
}
return false;
}
search(document.getElementsByTagName('body')[0], 'seven');
</script>
</body>
</html>
This is a function I’ve written a few years ago that searches for specific text, and highlights them (puts the hits in a span with a specific class name).
It walks the DOM tree, examining the text content. Whenever it finds a text node containing the looked-for text, it will replace that text node by three new nodes:
one text node with the text preceding the match,
one (newly created) span element containing the matching text,
and one text node with the text following the match.
This is the function as I have it. It’s part of a larger script file, but it should run independently as well. (I’ve commented out a call to ensureElementVisible which made the element visible, since the script also had folding and expanding capabilities).
It does one (other) thing that you probably won’t need: it turns the search text into a regular expression matching any of the multiple words.
function findText(a_text, a_top) {
// Go through *all* elements below a_top (if a_top is undefined, then use the body)
// and check the textContent or innerText (only if it has textual content!)
var rexPhrase = new RegExp(a_text.replace(/([\\\/\*\?\+\.\[\]\{\}\(\)\|\^\$])/g, '\\$1').replace(/\W+/g, '\\W*')
, 'gi');
var terms = [];
var rexSearchTokens = /[\w]+/g;
var match;
while(match = rexSearchTokens.exec(a_text)) {
terms.push(match[0]);
}
var rexTerm = new RegExp('\\b(' + terms.join('|') + ')', 'gi');
var hits = [];
walkDOMTree(a_top || document.body,
function search(a_element) {
if (a_element.nodeName === '#text') {
if(rexPhrase.test(a_element.nodeValue)) {
// ensureElementVisible(a_element, true);
hits.push(a_element);
}
}
});
// highlight the search terms in the found elements
for(var i = 0; i < hits.length; i++) {
var hit = hits[i];
var parent = hit.parentNode;
if (parent.childNodes.length === 1) {
// Remove the element from the hit list
hits.splice(i, 1);
var text = hit.nodeValue;
parent.removeChild(hit);
var match, prevLastIndex = 0;
while(match = rexTerm.exec(text)) {
parent.appendChild(document.createTextNode(text.substr(prevLastIndex, match.index - prevLastIndex)));
var highlightedTerm = parent.appendChild(document.createElement('SPAN'));
highlightedTerm.className = 'search-hit';
highlightedTerm.appendChild(document.createTextNode(match[0]));
prevLastIndex = match.index + match[0].length;
// Insert the newly added element into the hit list
hits.splice(i, 0, highlightedTerm);
i++;
}
parent.appendChild(document.createTextNode(text.substr(prevLastIndex)));
// Account for the removal of the original hit node
i--;
}
}
return hits;
}
I found the following so question:
Find text string using jQuery?
This appears to be close to what you're trying to do. Now are you attempting to wrap just the text "seven" or are you attempting to wrap the entire content of the <li>?