How to wrap(in span) words in a text editor - javascript

I have a javascript variable like this:
var text = "A <mark id='1'>businessman</mark> should be able to <mark id='1'>manage</mark> his business matters";
I want to wrap each word in a span element with a different id but leave the words which are already wrapped in the <mark> tags. Like this:
text = "<span id='1'>A </span><mark id='1'>businessman</mark><span id='2'>should</span><span id='3'>be </span><span id='4'>able </span><span id='5'>to </span><mark id='2'>manage</mark><span id='6'>his </span><span id='7'>business </span><span id='8'>matters</span>";
I want all this in javascript or jquery but couldn't get it. It would be nice if you people can help me.
Thank you.

Try this,
function removeEmptyStrings(k) {
return k !== '';
}
function getWordsArray(div) {
var rWordBoundary = /[\s\n\t]+/; // split by tab, newline, spaces
var output = [];
for (var i = 0; i < div.childNodes.length; ++i) { // Iterate through all nodes
var node = div.childNodes[i];
if (node.nodeType === Node.TEXT_NODE) { // The child is a text node
var words = node.nodeValue.split(rWordBoundary).filter(removeEmptyStrings); // check for emptyness
for (var j = 0, l = words.length; j < l; j++) {
// adding class txtSpan so that it will not affect your spans already available in your input.
output.push('<span class="txtSpan">' + words[j] + '</span>');
}
} else { // add directly if not a text node
output.push(node.outerHTML);
}
}
return output;
}
var counter = 1,
html = getWordsArray($('#editor')[0]).join('');
$('#output').html(html).find('span.txtSpan').each(function() {
this.id = 'span-' + counter++;
});
span.txtSpan {
padding: 2px;
display: inline-block;
text-decoration: underline;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="editor">
A <mark id='1'>businessman</mark> should be able to <mark id='1'>manage</mark> his business matters
</div>
<div id="output">
</div>

The following quick and dirty method removes the spaces from the <mark id='1'> elements so that the whole string can then be split on spaces, allowing individual words to be wrapped as needed, then it's joined back together, and finally the <mark> elements are restored and renumbered:
var text = "A <mark id='1'>businessman</mark> should be able to <mark id='1'>manage</mark> his business matters";
var spanIndex = 1;
var markIndex = 1;
var result = text.replace(/<mark id='1'>/g,'<mark>')
.split(' ')
.map(function(v){
return v.slice(0,6)==='<mark>' ? v : "<span id='" + spanIndex++ + "'>" + v + '</span>'
})
.join(' ')
.replace(/<mark>/g, function() { return "<mark id='" + markIndex++ + "'>"});
console.log(result);
Note: you said you want to "leave the words which are already wrapped in the <mark> tags", but then your sample output had renumbered the mark element ids (input had both as id='1', but output had 1 and 2). So I've shown code that renumbers, but obviously if you didn't mean to do that you can just make the final .replace() a bit simpler.

I took a much simpler approach that uses the browser's native DOM to parse and transform.
The idea here is to:
create an empty element and don't add it to the document.
take your text you want to transform and wrap and set it as the innerHTML of the temp element.
create an array from the childNodes of the temp element (created when you set the innerHTML)
if the childNode has a nodeValue then it was a text node NOT wrapped in a mark tag. In this case, create a new span element
in the case where the node is already wrapped in an element, just return the element
while you do all of this, remap the IDs to the index they were in from the array. this way they are unique.
Since you're settingthe innerHTML of an element, the DOM will actually fix any mistakes and you get perfect HTML out :)
var text = "A <mark id='1'>businessman</mark> should be able to <mark id='1'>manage</mark> his business matters";
const tempElement = document.createElement('div');
tempElement.innerHTML = text;
let final = Array.from(tempElement.childNodes).map((node, index) => {
if (node.nodeValue) { // if the node is a text node
let newSpanElement = document.createElement('span');
newSpanElement.id = index; // assign an ID
newSpanElement.innerText = node.nodeValue;
return newSpanElement;
}
// else it's inside another element
node.id = index; // reassign the id
return node;
}).map(node => node.outerHTML).join('');
console.log(final);

Related

JQuery - Change Color of String in a Text, ignoring <br>'s between

I need to change the color of a specific sub-string in a text.
The text looks like this:
SOME TE<br>
XT IS HER<br>
E.
I tired the .replace() function from jquery, the problem with it is, that, as you can see above, the text is splited with those <br>'s. How can I "ignore" them ?
For example I want to replace the String TEXT with <span class="color-red">TEXT</span>
Does anyone has an idea on how to solve this problem ?
No jQuery required. Although I did not test this at all so it might need tweaking.
var customReplace = function(str, subStr, color) {
var words = str.split(' ');
for (var i = words.length - 1; i >= 0; i--) {
var word = words[i].replace(/<br>/g, '');
if (word === subStr) {
words[i] = '<span class="color-' + color + '">' + words[i] + '</span>';
}
}
return words.join('');
}
I think a good Idea could be something like following:
fine all text nodes and wrap them inside a span§
re-insert all tags
var ALL_TAGS_REGEX = /(.+)(<{1}\/{0,1}.+>{1})/g;
document.addEventListener("DOMContentLoaded", function() {
document.body.innerHTML = document
.body
.innerHTML
.replace(ALL_TAGS_REGEX, '<span class="highlight">$1</span>$2')
;
});
.highlight { background: cyan; }
I am a string,<br>
<div> something else but no text node</div>
Other text nodes at libitum
< br > tag is break in line, color won't be applicable on < br > text, this would not accounted as normal text while final html render

JavaScript get textContent excluding children

First, I'm creating a library for JavaScript and I can not use jQuery. I'm trying to get the text content of an HTML element without the text contents of its children.
Both attributes innerText and textContent don't give me what needed, please help.
You can solve using DOM API as childNodes and nodeType.
var elChildNode = document.querySelector("#hello").childNodes;
var sChildTextSum = "";
elChildNode.forEach(function(value){
if(value.nodeType === Node.TEXT_NODE) {
console.log("Current textNode value is : ", value.nodeValue.trim());
sChildTextSum += value.nodeValue;
}
});
console.log("All text value of firstChild : ", sChildTextSum);
I created a sample code as above.
https://jsfiddle.net/nigayo/p7t9bdc3/
To get Author's Name from the following element, excluding <span>...:
<div class="details__instructor">
Author's Name<span ng-show="job_title">, Entrepreneur</span>
</div>
use childNodes[0]. For example:
document.querySelector('div.details__instructor').childNodes[0].textContent
Using only JavaScript (you specified you cannot use jQuery), and given that you have provided and know the id for the parent element:
document.getElementById('parent_element_id').childNodes[0].nodeValue;
You can also use .trim() to remove any trailing space characters left behind from the removal of any child element text:
document.getElementById('parent_element_id').childNodes[0].nodeValue.trim();
var mydiv = getElementByID("id");
function Get_text(element) {
var selected = element.cloneNode(true);
var text;
while (selected.firstChild) {
if (selected.firstChild.nodeType == 3) text = selected.firstChild.nodeValue;
selected.removeChild(selected.firstChild);
}
return text;
}
Get_text(mydiv);
I know many good solutions here exist, but none of them actually achieved what I needed (get the textContent of a single node, none of its children), so sharing this for future searchers.
var html = document.getElementsByTagName("*");
for (var i = 0; i < html.length; i++) {
var el = html[i];
for (var j = 0; j < el.children.length; j++) {
var child = el.children[j],
childTextContent = child.innerHTML;
// Remove all children tags, leaving only the actual text of the node.
childTextContent = childTextContent.replace(/\<.*\>.*\<\/.*\>/gmi, "");
// Also remove <img /> type tags.
childTextContent = childTextContent.replace(/\<.*\ \/\>/gmi, "");
console.log(childTextContent);
// Now you can do any type of text matching (regex) on the result.
}
});

Highlighting and formatting code snippets using JavaScript

I'm looking for a way to highlight and format code snippets passed as string for a live style guide. I'm playing around with highlighjs and prettify. They are really helpful and easy for highlighting, but I can't seem to figure out a way to format or whether they can actually do that or not.
By formatting, I mean tabs and newlines to make code legible. I need to pass code as a string to automate the output of dust template I'm using for the style guide.
That is, I want to pass:
"<table><tr><td class="title">Name</td><td class="title">Category</td><td class="title">Results</td></tr></table>"
And get something like:
<table>
<tr>
<td class="title">Name</td>
<td class="title">Category</td>
<td class="title">Results</td>
</tr>
</table>
Any ideas on how to accomplish this?
Thanks!
You could parse this as HTML into a DOM and than traverse every element writing it out and indenting it with every iteration.
This code will do the job. Feel free to use it and surely to improve it. It's version 0.0.0.1.
var htmlString = '<table><tr><td class="title">Name</td><td class="title">Category</td><td class="title">Results</td></tr></table>';
//create a containing element to parse the DOM.
var documentDOM = document.createElement("div");
//append the html to the DOM element.
documentDOM.insertAdjacentHTML('afterbegin', htmlString);
//create a special HTML element, this shows html as normal text.
var documentDOMConsole = document.createElement("xmp");
documentDOMConsole.style.display = "block";
//append the code display block.
document.body.appendChild(documentDOMConsole);
function indentor(multiplier)
{
//indentor handles the indenting. The multiplier adds \t (tab) to the string per multiplication.
var indentor = "";
for (var i = 0; i < multiplier; ++i)
{
indentor += "\t";
}
return indentor;
}
function recursiveWalker(element, indent)
{
//recursiveWalker walks through the called DOM recursively.
var elementLength = element.children.length; //get the length of the children in the parent element.
//iterate over all children.
for (var i = 0; i < elementLength; ++i)
{
var indenting = indentor(indent); //set indenting for this iteration. Starts with 1.
var elements = element.children[i].outerHTML.match(/<[^>]*>/g); //retrieve the various tags in the outerHTML.
var elementTag = elements[0]; //this will be opening tag of this element including all attributes.
var elementEndTag = elements[elements.length-1]; //get the last tag.
//write the opening tag with proper indenting to the console. end with new line \n
documentDOMConsole.innerHTML += indenting + elementTag + "\n";
//get the innerText of the top element, not the childs using the function getElementText
var elementText = getElementText(element.children[i]);
//if the texts length is greater than 0 put the text on the page, else skip.
if (elementText.length > 0)
{
//indent the text one more tab, end with new line.
documentDOMConsole.innerHTML += (indenting + indentor(1) ) + elementText+ "\n";
}
if (element.children[i].children.length > 0)
{
//when the element has children call function recursiveWalker.
recursiveWalker(element.children[i], (indent+1));
}
//if the start tag matches the end tag, write the end tag to the console.
if ("<"+element.children[i].nodeName.toLowerCase()+">" == elementEndTag.replace(/\//, ""))
{
documentDOMConsole.innerHTML += indenting + elementEndTag + "\n";
}
}
}
function getElementText(el)
{
child = el.firstChild,
texts = [];
while (child) {
if (child.nodeType == 3) {
texts.push(child.data);
}
child = child.nextSibling;
}
return texts.join("");
}
recursiveWalker(documentDOM, 1);
http://jsfiddle.net/f2L82m8h/

Add spans to characters in a string (in an HTML element)

I want to loop through the characters of a text in an element and add spans to the characters. This is pretty easy using jQuery.map():
$elem = $('h1');
var chars = jQuery.map($elem.text().split(''), function(c) {
return '<span>' + c + '</span>';
});
$elem.html(chars.join(''));
The above works great with a simple string, but now I want to change the function so that it also will handle more 'complex' contents like: <h1>T<em>e</em><b>st</b></h1>. Which should be translated to: <h1><span>T</span><em><span>e</span></em><b><span>s</span><span>t</span></b></h1>.
This means I cannot simply loop through all the characters in the element anymore. Is there something I can use to loop through the contents (characters) of an element as well as all children? Or is there another way of achieveing what I want?
Overall idea:
You can recursively iterate over the child nodes. If you encounter an element node, you iterate over its children etc. If you encounter a text node, you are replacing it with a series of span elements.
jQuery
function wrapCharacters(element) {
$(element).contents().each(function() {
if(this.nodeType === 1) {
wrapCharacters(this);
}
else if(this.nodeType === 3) {
$(this).replaceWith($.map(this.nodeValue.split(''), function(c) {
return '<span>' + c + '</span>';
}).join(''));
}
});
}
wrapCharacters($('h1')[0]);
DEMO
JavaScript (without jQuery)
The idea stays the same, and even without jQuery, wrapping each character is not very difficult:
var d_ = document.createDocumentFragment();
for(var i = 0, len = this.nodeValue.length; i < len; i++) {
var span = document.createElement('span');
span.innerHTML = this.nodeValue.charAt(i);
d_.appendChild(span);
}
// document fragments are awesome :)
this.parentNode.replaceChild(d_, this);
Only iterating over the child nodes has to be done carefully because text nodes are getting removed during iteration.
Plain JavaScript example
Try something like (untested):
function recursivelyWrapTextNodes($node) {
$node.contents().each(function() {
var $this = $(this);
if (this.nodeType === 3) { //Node.TEXT_NODE (IE...)
var spans = $.each($this.text().split(""), function(index, element) {
var $span = $("<span></span>");
$span.text(element);
$span.insertBefore($this);
});
$this.remove();
}
else if (this.nodeType === 1) //Node.ELEMENT_NODE
recursivelyWrapTextNodes($this);
}
Example: http://jsfiddle.net/Ymcha/
This is a pure javascript solution
/**
* Enclose every character of a string into a span
* #param text Text whose characters will be spanned
* #returns {string} The "spanned" string
*/
function spanText(text) {
return "<span class='char'>" +
text.split("").join("<\/span><span class='char'>") + "<\/span>";
}
var text = "Every character will be in a span";
document.getElementById("testContent").innerHTML = spanText(text);
document.getElementById("showSpans").textContent = spanText(text);
.char{background-color: grey;}
<! --- Demo for spanning all characters -->
<h3> Spanned text is highlighted grey </h3>
<p id="testContent"> Spanned material here</p>
<h3> This is how the above Highlighted text looks </h3>
<p id="showSpans"></p>

how to dynamically address a word/string with javascript in an html-document and then tag it?

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>?

Categories