How can I get all text node in document fragment? - javascript

I get the user selected text:
var selection = window.getSelection();
var selectRange = selection.getRangeAt(0);
var content = selectRange.cloneContents(); // DocumentFragment
How can I get the textNode in the DocumentFragment content?

use textContent
var selection = window.getSelection();
var selectRange = selection.getRangeAt(0);
var content = selectRange.cloneContents(); // DocumentFragment
var text = content.textContent;

Filter fragment.childNodes to get the text nodes:
const selection = window.getSelection();
const selectRange = selection.getRangeAt(0);
const fragment = selectRange.cloneContents(); // DocumentFragment
// Get the child nodes and filter them to only include text nodes
const textNodes = Array.from(fragment.childNodes).filter(child => child.nodeName === "#text");

Combining some tricks it is easy to extract the text nodes out of any container node (in this case a fragment). The fragment part of the question is irrelevant to the extraction part.
Getting all the children of the container, converting them to a "real" Array using the spread operator ... so filter could be used to. Can also skip this part because HTMLCollection does support forEach so it's possible to fill an empty Array within that.
Note that Node.TEXT_NODE is a DOM constant for 3
// create a demo fragment with some HTML mix of text nodes & elements
var frag = document.createRange().createContextualFragment("<a>1</a> 2 <b>3</b> 4.");
// now the work begins: get only the text nodes from the fragment
var textNodes = [...frag.childNodes].filter(node => node.nodeType == Node.TEXT_NODE)
// print the text nodes as an array
console.log( textNodes.map(node => node.textContent) )

Related

JavaScript add spaces between sentences while parsing XML string

I have this XML string which I am displaying as a text in a document:
<p>The new strain of <s alias="coronavirus">COVID</s>seems to be having a greater spread rate.</p>
The following function returns the text form of the XML:
function stripHtml(html) {
// Create a new div element
var temporalDivElement = document.createElement("div");
// Set the HTML content with the providen
temporalDivElement.innerHTML = html;
// Retrieve the text property of the element (cross-browser support)
return temporalDivElement.textContent || temporalDivElement.innerText || "";
}
The problem is, this function returns the following string:
The new strain of COVIDseems to be having a greater spread rate.
which is nearly what I want, but there is no space between the word COVID and seems. Is it possible that I can add a space between contents of two tags if it doesn't exist?
One option is to iterate over text nodes and insert spaces at the beginning if they don't exist, something like:
const getTextNodes = (parent) => {
var walker = document.createTreeWalker(
parent,
NodeFilter.SHOW_TEXT,
null,
false
);
var node;
var textNodes = [];
while(node = walker.nextNode()) {
textNodes.push(node);
}
return textNodes;
}
function stripHtml(html) {
// Create a new div element
var temporalDivElement = document.createElement("div");
// Set the HTML content with the providen
temporalDivElement.innerHTML = html;
// Retrieve the text property of the element (cross-browser support)
for (const node of getTextNodes(temporalDivElement)) {
node.nodeValue = node.nodeValue.replace(/^(?!\s)/, ' ');
}
return temporalDivElement.textContent.replace(/ +/g, ' ').trim();
}
console.log(stripHtml(`<p>The new strain of <s alias="coronavirus">COVID</s>seems to be having a greater spread rate.</p>`));

How can I get the raw value of a text node?

Let's say I have the following:
var div = document.createElement('div');
div.innerHTML = "<";
var nodes = div.childNodes;
console.log(nodes)
then in all fields of nodes[0] (i.e. nodes[0].data, nodes[0].nodeValue, nodes[0].textContent, nodes[0].wholeText) I get <. Can I retrieve the "raw" value of a text node somehow? In this case <. Or is the only option to first retrieve it parsed, and then to escape the html somewhat like this:
function escapeHtml(html) {
var text = document.createTextNode(html);
var div = document.createElement('div');
div.appendChild(text);
return div.innerHTML;
}
Note: I consciously chose childNodes in order to get text-nodes as well as non-text-nodes.

Inserting an HTML element after text selection

I am trying to insert some HTML after the user has selected some text on the page. I have it working but not optimally and there is one problem in particular:
It relies on the element being on the page, but I don't want it to be. I want to do it all in the code, I am sure it's something relatively simple but need some help :)
I also welcome any other tips/suggestions/feedback!
The code canbe seen working here: http://jsfiddle.net/shE58/ (The selectionchange library is to make this work in FF).
document.addEventListener('selectionchange', function (event) {
var sel = this.getSelection();
// If text is being selected by drag, to wait for them to finish selecting.
jQuery("body").mouseup(function() {
// Ensure there is some text selected and that it is more than one character.
if (sel.toString().length > 1) {
// We only want one #element on the page at a time.
$("#element").remove();
// #TODO: Remove dependency on this:
var el = document.getElementById('selection');
el.innerHTML = '<div id="element"></div>';
var frag = document.createDocumentFragment(), node;
frag.appendChild(el.firstChild);
var range = sel.getRangeAt(0)
var startNode = range.startContainer, startOffset = range.startOffset;
var boundaryRange = range.cloneRange();
boundaryRange.collapse(false);
boundaryRange.insertNode(frag);
boundaryRange.setStart(startNode, startOffset);
boundaryRange.collapse(true);
// Clean up
sel = '';
}
});
});
Solution 1
Remove frag variable and directly add value in insertNode function like this :
boundaryRange.insertNode($('<div id="element"></div>')[0]);
http://jsfiddle.net/N6zz8/
Fix issue when to select right to left :
With a condition check if endOffset is smaller than startOffset.
var startNode = range.startContainer, startOffset = range.startOffset;
if (range.endOffset < range.startOffset) {
startNode = range.endContainer;
startOffset = range.endOffset;
}
http://jsfiddle.net/2c4qw/
Solution 2
Remove unnecessary code, remove 'el' variable like this :
// #TODO: Remove dependency on this:
var frag = document.createDocumentFragment(), node;
$(frag).append($('<div id="element"></div>'));
http://jsfiddle.net/zYSH4/
Solution 3
Declare your variable like this var el = $("<div></div>")[0];. To get this :
// #TODO: Remove dependency on this:
var el = $("<div></div>")[0];
el.innerHTML = '<div id="element"></div>';
var frag = document.createDocumentFragment(), node;
frag.appendChild(el.firstChild);
http://jsfiddle.net/7L3Kf/

Determine HTML Node name given a javascript selection object

How can I determine the node (what tag this innerHTML is associated with) that is associated with a given javascript selection object? I am using window.getSelection() and would like to know in which div/span class the selection is in.
Thank you!
If you want the deepest element that contains the whole of the selection, the easiest way is to obtain a Range object from the selection and use its commonAncestorContainer property.
jsFiddle: http://jsfiddle.net/5LvEG/1/
Code:
function getSelectionContainerElementId() {
var sel = window.getSelection();
if (sel.rangeCount > 0) {
var range = sel.getRangeAt(0);
var container = range.commonAncestorContainer;
// container could be a text node, so use its parent if so
if (container.nodeType == 3) {
container = container.parentNode;
}
return container.id;
}
return null;
}
alert(getSelectionContainerElementId());

Inserting Custom Tags on User Selection

I want to insert my own custom tags and scripts around the selected text. Something like this
var range = window.getSelection().getRangeAt(0);
var sel = window.getSelection();
range.setStart( sel.anchorNode, sel.anchorOffset );
range.setEnd(sel.focusNode,sel.focusOffset);
highlightSpan = document.createElement("abbr");
highlightSpan.setAttribute("style","background-color: yellow;");
highlightSpan.setAttribute("onmouseout","javascript:HideContentFade(\"deleteHighlight\");");
highlightSpan.setAttribute("onmouseover","javascript:ShowHighlighter(\"deleteHighlight\",\""+id_val+"\");");
highlightSpan.appendChild(range.extractContents());
range.insertNode(highlightSpan);
This works in normal scenarios but if I select some text in different paragraphs the extractContents API will validate the HTML returned and put additional tags to make it valid HTML. I want the exact HTML that was selected without the additional validating that javascript did.
Is there any way this can be done?
I have tried it the way mentioned in How can I highlight the text of the DOM Range object? but the thing is I want user specific highlights so if A has added some highlight B should not be able to see it. For this I have my backend code ready.
If you wrap with tags the selected text that belongs to different paragraphs, you create invalid HTML code.
This is an example of invalid HTML code that you would generate.
<p>notselected <span>selected</p><p>selected</span> notselected</p>
In order to accomplish your task, you need to wrap with tags each text in each paragraph of the selection resulting in a code like this.
<p>notselected <span>selected</span></p><p><span>selected</span> notselected</p>
To accomplish this you have to iterate over all nodes selected and wrap the selected text like this:
function wrapSelection() {
var range, start, end, nodes, children;
range = window.getSelection().getRangeAt(0);
start = range.startContainer;
end = range.endContainer;
children = function (parent) {
var child, nodes;
nodes = [];
child = parent.firstChild;
while (child) {
nodes.push(child);
nodes = nodes.concat(children(child));
child = child.nextSibling;
}
return nodes;
}
nodes = children(range.commonAncestorContainer);
nodes = nodes.filter(function (node) {
return node.nodeType === Node.TEXT_NODE;
});
nodes = nodes.slice(nodes.indexOf(start) + 1, nodes.indexOf(end));
nodes.forEach(function (node) {
wrap = window.document.createElement("span");
node.parentNode.insertBefore(wrap, node);
wrap.appendChild(node);
});
start = new Range();
start.setStart(range.startContainer, range.startOffset);
start.setEnd(range.startContainer, range.startContainer.length);
start.surroundContents(window.document.createElement("span"));
end = new Range();
end.setStart(range.endContainer, 0);
end.setEnd(range.endContainer, range.endOffset);
end.surroundContents(window.document.createElement("span"));
}

Categories

Resources