Javascript create tooltip for the part of the line that is not yet under the tooltip

I have a line like this:

"Size: 40; Color: 30" 

I want to create tooltips for them so that it looks like this:

  <span class='tooltip' data-tooltip='The Size of a Unit is controlled by the Color of the Unit.'>Size</span>: 40; <span class='tooltip' data-tooltip='The Color of a Unit is a standard Setting.'>Color</span>: 30 

Using a naive replacement, I end up with the following:

  <span class='tooltip' data-tooltip='The Size of a Unit is controlled by the <span class='tooltip' data-tooltip='The Color of a Unit is a standard Setting.'>Color</span> of the Unit.'>Size</span>: 40; <span class='tooltip' data-tooltip='The Color of a Unit is a standard Setting.'>Color</span>: 30 

This is not what I want. How to write a regular expression or make a replacement so that it does not replace the text that is already part of the tooltip?

Edit: I did not make it clear that the replacements are not "Size and Color", these are just examples. I add an arbitrary amount, usually more than 20 tips to any line.

Here are some test cases:

 var tooltips = { "Size":"The Size of a Unit is controlled by the Color", "Color": "bar", "Time and Size": "foo" } "Here we have something of <b>Size</b> 20 and Color red. it very important that the Time and Size of the work and kept in sync." 

It should turn out:

 "Here we have something of <b><span class='tooltip' data-tooltip='The Size of a Unit is controlled by the Color'>Size<span></b> 20 and <span class='tooltip' data-tooltip='bar'>Color<span> red. it very important that the <span class='tooltip' data-tooltip='foo'>Time and Size<span> of the work and kept in sync." 

A longer match must take precedence over shorter matches. It should correspond only to whole words, not parts of words.

Edit: Forgot to indicate another requirement.

It should still match strings that are wrapped with tags that are not prompts.

+6
source share
8 answers

I think one str.replace will do the job if it gets the correct regular expression pattern.

 function replaceTooltips(str, tooltips) { //copy from https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions function escapeRegExp(string) { return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); } var keys = []; for ( var k in tooltips) { keys.push(escapeRegExp(k)); } //create a regexp like (Size)|(Color)|(Time and Size) var ptn = new RegExp('(' + keys.join(')|(') + ')', 'g'); str = str.replace(ptn, function(mat) { if (!tooltips[mat]) { return mat; } return '<span class="tooltip" data-tooltip="' + tooltips[mat].replace(/"/g, '&quot;') + '">' + mat + '</span>'; }); return str; } 

http://jsfiddle.net/rooseve/6wRUF/

+2
source

I would use the following:

 //usual regex var re = new RegExp("("+key+")(?!(?:<|.+?'>))", "g"); //regex to be applied on reversed string, same concept as regex above var reRev = new RegExp("("+key.reverse()+")(?!(?:.+'=|>))", "g"); //start of the span tag including the tooltip var repl = "<span class='tooltip' data-tooltip='"+value+"'>"; //end of the span tag var replEnd = "</span>"; //first replacement output = output.replace(re, repl+"$1"+replEnd); //now reverse the whole string and do a second replacement, //this, together with the reversed regex (which uses a lookahead) mimics a lookbehind (which is unfortunately not available in JS) output = output.reverse().replace(reRev, replEnd.reverse()+"$1"+repl.reverse()).reverse(); 

Regex-demo @ regex101
JS-Demo @JSFiddle
See JSFiddle for how to replace the sentences you must first order in an input array.

For each replacement, the regular expression replaces it with the corresponding span-tooltip construct.
Since JS does not have any distortion, we must imitate it to replace every occurrence of keywords, because the first regular expression will not match the keywords before opening the <span> -tag, which could easily be solved with lookbehind:
To simulate lookbehind , we use lookahead, but just reformat all the text in advance. The only drawback is that you must change your regular expression too ... manually! For large expressions, it will be a pain, but in this case it is quite easy. If you cancel the data entry, you do not want to match keywords if it occurs .+'= Or > .

For the most regular expression:
Regular expression visualization
it matches only the keyword if it is not followed by < (which will mark the </span> ), and if it is not followed by .+'> , which means several characters and '> , which mean the end of the data-tooltip attribute.
I made it case sensitive. If you want it to be case insensitive, use the gi flags instead of the g flag.

Thus, you are not limited to individual words, you can replace "I offer" with the tooltip of your choice. The same concept applies to reverse regular expression.

You may need to adjust the replacement according to your data structure. If this comes from user input, an associative array may be right for you:

 var replacements = new Array (); replacements['Size'] = "The Size of a Unit is controlled by the Color of the Unit."; replacements['Color'] = "The Color of a Unit is a standard Setting."; 
+2
source

Many of the other answers are just group functions on the main problem: sometimes a search is found in other string replacements.

For examples, some of the other naive solutions that break easily:

  • reordering (doesn't work if both contain the other)
  • look forward or backward (does not work if you replace template changes, as well as regular expression regular expression HTML, as you know, is difficult)

But if we recall the main problem, there is an obvious solution:

Do not allow replacement replacements.

So do two passes. Steps:

  • Instead of an object / dictionary, use an array of objects. Each item must be { search: "Size", tooltip: "Long description" } .
  • Sorting an array by the length of the search string (you said you want more priority, so here's how)
  • Iterate through the array and replace for all keywords with a unique string with an index in the array. For example, Size and color becomes ### replace0 ### .
  • Iterate again and replace all unique lines with a tooltip, so ### replace0 ### becomes <span class='tooltip' data-tooltip='The size is one thing, the color is another.'>Size and color</span> .

Thus, the whole operation acts as a single search / replacement and does not have the ability to replace matches in other replacements.

+2
source

Just scroll through the words of your line, and if a tooltip is found for a word, replace it with a tooltip:

 var s1 = 'Here we have something of <b>Size</b> 20 and Color red. it\ very important that the Time and Size of the work and kept in sync.', // replace object rep = { size: '<span class="tooltip" data-tooltip="The Size of a Unit is controlled by the Color of the Unit.">Size</span>', color: '<span class="tooltip" data-tooltip="The Color of a Unit is a standard Setting.">Color</span>', 'time and size': '<span class="tooltip" data-tooltip="Time and Size tooltip">Foo</span>' }, // build RegExp out of replace object keys reg = new RegExp('(' + Object.keys(rep).join(')|(') + ')', 'ig'); // replace s2 = s1.replace(reg, function(s) { return rep[s.toLowerCase()] ? rep[s.toLowerCase()] : s; }); console.log(s2); 
+1
source

It could be that simple if there were no verbose replacements.

 var tooltips = { "Size":"The Size of a Unit is controlled by the Color", "Color": "bar", "Time and Size": "foo" } var text = "Here we have something of Size 20 and Color red. it very important that the Time and Size of the work and kept in sync." var replaced = text.split(" ").map(function(token) { return tooltips[token] || token; }).join(" "); 
+1
source

You can use jQuery to find all text nodes inside an element. Subsequently, you can use the DOM functions (instead of the regular expression) to split the text around the specified word, and then wrap the word inside the tooltip. Here is an example:

 function replaceTextWithSpan(node, text, options) { var searchText = text.toLowerCase(), currentNode = node, matchIndex, newTextNode, newSpanNode; while ((matchIndex = currentNode.data.toLowerCase().indexOf(searchText)) >= 0) { newTextNode = currentNode.splitText(matchIndex); currentNode = newTextNode.splitText(searchText.length); newSpanNode = document.createElement("span"); newSpanNode.className = "tooltip"; newSpanNode.setAttribute("data-tooltip", options["data-tooltip"]); currentNode.parentNode.insertBefore(newSpanNode, currentNode); newSpanNode.appendChild(newTextNode); } } 

And the test:

 <div id="test">Size: 40; Color: 30; <b>Bold Size Test:</b> 20; <span>Another Size Test: 10</span></div> 
 $("#test, #test *").contents().filter(function () { return this.nodeType == this.TEXT_NODE; }).each(function () { replaceTextWithSpan(this, "Size", { "data-tooltip": "The Size of a Unit is controlled by the Color of the Unit." }); }); $("#test, #test *").contents().filter(function () { return this.nodeType == this.TEXT_NODE; }).each(function () { replaceTextWithSpan(this, "Color", { "data-tooltip": "The Color of a Unit is a standard Setting." }); }); alert($("#test").html()); 

And the result:

 <span class="tooltip" data-tooltip="The Size of a Unit is controlled by the Color of the Unit.">Size</span>: 40; <span class="tooltip" data-tooltip="The Color of a Unit is a standard Setting.">Color</span>: 30; <b>Bold <span class="tooltip" data-tooltip="The Size of a Unit is controlled by the Color of the Unit.">Size</span> Test:</b> 20; <span>Another <span class="tooltip" data-tooltip="The Size of a Unit is controlled by the Color of the Unit.">Size</span> Test: 10</span> 

Demo here


Original answer: here is a solution that does not use RegEx:

  • Iterate over child text / HTML nodes within an element
  • Skip HTML nodes and text nodes that do not contain a search string
  • Break text node before and after search string (so you get three text nodes)
  • Wrap the middle node inside the span

Here is the code (I don't know what is complicated):

 function replaceTextWithSpan(node, text, options) { var searchText = text.toLowerCase(), currentNode = node.firstChild, matchIndex, newTextNode, newSpanNode; while (currentNode) { matchIndex = currentNode.nodeType === currentNode.TEXT_NODE ? currentNode.data.toLowerCase().indexOf(searchText) : -1; if (matchIndex >= 0) { newTextNode = currentNode.splitText(matchIndex); currentNode = newTextNode.splitText(searchText.length); newSpanNode = document.createElement("span"); // the following line can be replaced with for...in // loop to assign multiple attributes to the span newSpanNode.className = options.className; currentNode.parentNode.insertBefore(newSpanNode, currentNode); newSpanNode.appendChild(newTextNode); } else { currentNode = currentNode.nextSibling; } } } 

Here is the test:

 var node = document.createElement("div"); node.innerHTML = "Size: 40; Color: 30; Span: For testing"; replaceTextWithSpan(node, "Size", { className: "highlight" }); replaceTextWithSpan(node, "Color", { className: "highlight" }); replaceTextWithSpan(node, "Span", { className: "highlight" }); alert(node.innerHTML); 

This produces the following output (fairly printed):

 <span class="highlight">Size</span>: 40; <span class="highlight">Color</span>: 30; <span class="highlight">Span</span>: For testing 

Demo here

+1
source

If they are always divided ; then you have to break the line there, replace each part with the corresponding line and then join it again.

Sort of

 var tooltips = { 'size': 'The Size of a Unit is controlled by the Color of the Unit.', 'color': 'The Color of a Unit is a standard Setting.' ..etc.. }, myString = "Size: 40; Color: 30", stringParts = myString.split(';'); for (var i = 0, len = stringParts.length; i < len; i++){ var pair = stringParts[i].split(':'), key = pair[0].trim().toLowerCase(), tip = tooltips[key]; if (tip){ pair[0] = '<span class="tooltip" data-tooltip="'+ tip +'">' + key + '</span>'; } } stringParts[i] = pair.join(':'); } alert( stringParts.join('; ') ); 

if your browser does not support the .trim() function, then find the implementation in Trim String in JavaScript?

0
source

You can use the following code:

 var tooltips = { "Size":"The Size of a Unit is controlled by the Color", "Color": "bar", "Time and Size": "foo" } var str="Here we have something of <b>Size</b> 20 and Color red. it very important that the Time and Size of the work and kept in sync." var len=0; var res=str; var rep="<span class='tooltip' data-tooltip='"; $.each(tooltips, function(key, value) { var patt1=new RegExp(key); var ar=patt1.exec(str); var repstr=rep+value+"'>"+key+"<span>"; res=res.substr(0,ar.index+len)+repstr+res.substr(ar[0].length+ar.index+len); len+=repstr.length-key.length; }); alert("result:" +res); 
0
source

All Articles