How to Wrap/Surround Highlighted Text with an Element

How To Wrap / Surround Highlighted Text With An Element

If the selection is completely contained within a single text node, you can do this using the surroundContents() method of the range you obtain from the selection. However, this is very brittle: it does not work if the selection cannot logically be surrounded in a single element (generally, if the range crosses node boundaries, although this is not the precise definition). To do this in the general case, you need a more complicated approach.

Also, DOM Range and window.getSelection() are not supported in IE < 9. You'll need another approach again for those browsers. You can use a library such as my own Rangy to normalize browser behaviour and you may find the class applier module useful for this question.

Simple surroundContents() example jsFiddle: http://jsfiddle.net/VRcvn/

Code:

function surroundSelection(element) {
if (window.getSelection) {
var sel = window.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0).cloneRange();
range.surroundContents(element);
sel.removeAllRanges();
sel.addRange(range);
}
}
}

How to wrap selected text with html element without losing line breaks?

Add style='white-space: pre-wrap;' to your <code> element

code function would become:

    var code = function (S) {
var str = "<code style='white-space: pre-wrap;'>" + S + "</code>";
return str;
};

See this answer for a more in depth explanation

How to wrap highlighted text with a span and place a div positioned relative to it in javascript?

The value returned by getSelectedText is a selection not an element in the DOM. That is why your cal to the html function is failing, selection has no such property.

You can do what you want as follows, set the part of the document you want to process as contentEditable for example a <p> or <div>. When you select within that area then you can get the HTML element using document.activeElement. If you select outside a contentEdiatble area then activeElement returns the body element. The activeElement you CAN process :

document.activeElement.innerHTML = 
document.activeElement.innerHTML.replace (selection, spn);

However, if you make parts of your document editable other things happen which you may not want. More seriously, if the selected text occurs multiple times you cannot determine which one has actually been selected so this may not be a solution to your problem.

Updated fiddle here

How to wrap part of a text in a node with JavaScript

Here are two ways to deal with this.

I don't know if the following will exactly match your needs. It's a simple enough solution to the problem, but at least it doesn't use RegEx to manipulate HTML tags. It performs pattern matching against the raw text and then uses the DOM to manipulate the content.


First approach

This approach creates only one <span> tag per match, leveraging some less common browser APIs.

(See the main problem of this approach below the demo, and if not sure, use the second approach).

The Range class represents a text fragment. It has a surroundContents function that lets you wrap a range in an element. Except it has a caveat:

This method is nearly equivalent to newNode.appendChild(range.extractContents()); range.insertNode(newNode). After surrounding, the boundary points of the range include newNode.

An exception will be thrown, however, if the Range splits a non-Text node with only one of its boundary points. That is, unlike the alternative above, if there are partially selected nodes, they will not be cloned and instead the operation will fail.

Well, the workaround is provided in the MDN, so all's good.

So here's an algorithm:

  • Make a list of Text nodes and keep their start indices in the text
  • Concatenate these nodes' values to get the text
  • Find matches over the text, and for each match:

    • Find the start and end nodes of the match, comparing the the nodes' start indices to the match position
    • Create a Range over the match
    • Let the browser do the dirty work using the trick above
    • Rebuild the node list since the last action changed the DOM

Here's my implementation with a demo:

function highlight(element, regex) {    var document = element.ownerDocument;        var getNodes = function() {        var nodes = [],            offset = 0,            node,            nodeIterator = document.createNodeIterator(element, NodeFilter.SHOW_TEXT, null, false);                    while (node = nodeIterator.nextNode()) {            nodes.push({                textNode: node,                start: offset,                length: node.nodeValue.length            });            offset += node.nodeValue.length        }        return nodes;    }        var nodes = getNodes(nodes);    if (!nodes.length)        return;        var text = "";    for (var i = 0; i < nodes.length; ++i)        text += nodes[i].textNode.nodeValue;
var match; while (match = regex.exec(text)) { // Prevent empty matches causing infinite loops if (!match[0].length) { regex.lastIndex++; continue; } // Find the start and end text node var startNode = null, endNode = null; for (i = 0; i < nodes.length; ++i) { var node = nodes[i]; if (node.start + node.length <= match.index) continue; if (!startNode) startNode = node; if (node.start + node.length >= match.index + match[0].length) { endNode = node; break; } } var range = document.createRange(); range.setStart(startNode.textNode, match.index - startNode.start); range.setEnd(endNode.textNode, match.index + match[0].length - endNode.start); var spanNode = document.createElement("span"); spanNode.className = "highlight";
spanNode.appendChild(range.extractContents()); range.insertNode(spanNode); nodes = getNodes(); }}
// Test codevar testDiv = document.getElementById("test-cases");var originalHtml = testDiv.innerHTML;function test() { testDiv.innerHTML = originalHtml; try { var regex = new RegExp(document.getElementById("regex").value, "g"); highlight(testDiv, regex); } catch(e) { testDiv.innerText = e; }}document.getElementById("runBtn").onclick = test;test();
.highlight {  background-color: yellow;  border: 1px solid orange;  border-radius: 5px;}
.section { border: 1px solid gray; padding: 10px; margin: 10px;}
<form class="section">  RegEx: <input id="regex" type="text" value="[A-Z].*?\." /> <button id="runBtn">Highlight</button></form>
<div id="test-cases" class="section"> <div>foo bar baz</div> <p> <b>HTML</b> is a language used to make <b>websites.</b> It was developed by <i>CERN</i> employees in the early 90s. <p> <p> This program is <a href="beta.html">not stable yet. Do not use this in production yet.</a> </p> <div>foo bar baz</div></div>

How to wrap part of all text_node nodeValue in an html element?

I think that you need to recurse all the DOM and each match... have a look here:

function replacer(node, parent) {   var r = /Questions/g;  var result = r.exec(node.nodeValue);  if(!result) { return; }    var newNode = this.createElement('span');    newNode.innerHTML = node    .nodeValue    .replace(r, '<span class="replaced">$&</span>')  ;    parent.replaceChild(newNode, node);}

document.addEventListener('DOMContentLoaded', () => { function textNodesIterator(e, cb) { if (e.childNodes.length) { return Array .prototype .forEach .call(e.childNodes, i => textNodesIterator(i, cb)) ; }
if (e.nodeType == Node.TEXT_NODE && e.nodeValue) { cb.call(document, e, e.parentNode); } }
document .getElementById('highlight') .onclick = () => textNodesIterator( document.body, replacer );});
.replaced {background: yellow; }.replaced .replaced {background: lightseagreen; }.replaced .replaced .replaced {background: lightcoral; }
<button id="highlight">Highlight</button><hr><p>Questions1</p><p>Questions 2</p><p>Questions 3</p><p>Questions 4</p><p>Questions 5 Questions 6</p><div>  <h1>Nesting</h1>  Questions <strong>Questions 4</strong>  <div> Questions <strong>Questions 4</strong></div>      <div>     Questions <strong>Questions 4</strong>      <div> Questions <strong>Questions 4</strong></div>  </div></div>

Can I wrap a text selection in a tag in phpstorm?

  1. Select your lines
  2. Code | Surround With...

    For Windows/Linux: Ctrl + Alt + T

    For MacOS: Cmd + Alt + T
  3. Choose "Emmet"
  4. Type p* -- this will surround each line with <p> tag

Screenshot of PhpStorm Emmet functionality

NOTE: Empty lines will be wrapped as well so it is better remove them in advance.

Similar/related case.


P.S.

In current versions of IDE the dedicated "Surround with Emmet" action is available which allows you to bring that popup window in one key stroke instead of having going trough via intermediate Surround with... popup menu first.

That action has no shortcut defined by default, but you can easily change that and assign any desired shortcut in Settings/Preferences | Keymap -- just look for Other | Surround with Emmet action (hint: use search box to speed up your search).



Related Topics



Leave a reply



Submit