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 includenewNode
.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?
- Select your lines
Code | Surround With...
For Windows/Linux: Ctrl + Alt + T
For MacOS: Cmd + Alt + T- Choose "Emmet"
- Type
p*
-- this will surround each line with<p>
tag
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
Check If an Array Is Empty or Exists
How to Add an Object to an Array
How to Load Local Script Files as Fallback in Cases Where Cdn Are Blocked/Unavailable
Where to Write to Localstorage in a Redux App
How to Add Conditional Attribute in Angular 2
Convert String in Dot Notation to Get the Object Reference
Using Http Rest APIs with Angular 2
When Does Js Interpret {} as an Empty Block Instead of an Empty Object
How to Map More Than One Property from an Array of Objects
How to Calculate Md5 Hash of a File Using JavaScript
Angularjs Changes Urls to "Unsafe:" in Extension Page
How to Check If an Element Is Really Visible with JavaScript
How to Get the Day of Week and the Month of the Year
How to Break a String Across More Than One Line of Code in JavaScript
How to Toggle an Element's Class in Pure JavaScript