How to remove all wrappers before new range.surroundContents(span) - javascript

On my page I use searching for a text and highlighting it like this:
document.querySelector('#search').addEventListener('keyup', function() {
var inputValue = this.value;
var tableTDs = document.querySelectorAll('table td');
for(var i = 0; i < tableTDs.length; i++) {
tableTDs[i].scrollIntoView();
if(document.createRange) {
var range = document.createRange();
var childs = tableTDs[i].childNodes;
for(var n = 0; n < childs.length; n++) {
var childNode = childs[n];
if(childNode.nodeValue) { // if child is a text node
var childValue = childNode.nodeValue;
} else { // if child is a nested element e.g. a link
var childValue = childNode.firstChild.nodeValue;
var childNode = childNode.firstChild;
}
if(childValue && childValue.indexOf(inputValue) != -1) {
range.setStart(childNode, childValue.indexOf(inputValue));
range.setEnd(childNode, childValue.indexOf(inputValue) + inputValue.length);
var span = document.createElement('span');
span.style.backgroundColor = 'yellow';
range.surroundContents(span);
return;
}
}
}
}
});
<input type="text" id="search">
<table>
<tr><td>First cell</td><td>Second cell and link anchor</td></tr>
</table>
If I want to find for example the word "anchor", I type "a", next "n" and next "c" and I see two highlightings, but not one highlighting of "anc".
So as I can understand, I need to remove all the wrappers before new range.surroundContents()
How can I resolve the issue?
UPDATED
Ok, the first possible solution is adding before tableTDs[i].scrollIntoView() the following code
tableTDs[i].innerHTML = tableTDs[i].innerHTML.replace(/<span style=\"background-color: yellow;\">/g,'');
tableTDs[i].innerHTML = tableTDs[i].innerHTML.replace(/<\/span>/g,'');
But is there something better?

Related

javascript doing multiple createrange() while encountering unexpected anchorOffset

My goal:
Let users highlight different substring in a single long string.
However, once I've highlighted one substring with range.surroundContents(newNode) (newNode is a span with yellow background), the innerHTML of the whole long string changed-- it started to contain the span element; consequently, if the user wants to highlight a substring after the previous highlighted substring in the same long string, the anchorOffset will return the index starting after the previous span.
For example, in this long string:
"Mr. and Mrs. Dursley, of number four, Privet Drive, were proud to say that they were perfectly normal, thank you very much."
this long sentence is wrapped by a p whose class name is noting. If the range.surroundContents() method the substring "Privet Drive", then, when I want to get the window.getSelection().anchorOffset of the substring "thank", the answer wrongly is 53 while the correct answer should be 102.
How should I do? Thank you!!
P.S. I don't want to use substring method to find the position, thank you!
$(".noting").mouseup(function(e){
$("#noteContent").val("");/*flushing*/
curSentNum = $(this).attr("id").split("-")[1];
$('#curSentNum').val(curSentNum);
highlightLangName = $(this).attr("id").split("-")[2];
$('#highlightLangName').val(highlightLangName);
//console.log(".noting $(this).html()"+$(this).html()+" "+$(this).attr("id"));//id, for example: p-2-French
if (window.getSelection) {
highlightedText = window.getSelection().toString();
curAnchorOffset = window.getSelection().anchorOffset;
$('#anchorAt').val(curAnchorOffset);
$('#highlightLen').val(highlightedText.length);
}
else if (document.selection && document.selection.type != "Control") {
highlightedText = document.selection.createRange().text;
}
});
And then I'll save the anchorAt information to db; after the db operation, I'll immediately call this function using the previous variables remained:
function highlightNoteJustSaved(){
var curI = noteCounter;
var anchorAt = parseInt($("#anchorAt").val());
var highlightLen = parseInt($("#highlightLen").val());
/*p to find, for example: p-2-French*/
var curP = document.getElementById('p-'+curSentNum.toString()+"-"+$("#highlightLangName").val());
var range = document.createRange();
root_node = curP;
range.setStart(root_node.childNodes[0], anchorAt);
range.setEnd(root_node.childNodes[0], anchorAt+highlightLen);
var newNode = document.createElement("span");
newNode.style.cssText="background-color:#ceff99";//yellow
newNode.className = alreadyNoteStr;
newNode.setAttribute('id','already-note-'+curI.toString());
range.surroundContents(newNode);
}
for HTML tree node structure, please take a look at the comment below( I didn't figure out how to copy-paste the code at this asking area).
I replaced your method to highlight text with 2 methods. highlightTextNodes finds the word in the content of the node. Searching each child. Also I implemented a highlight remover to show how it works. I replaced the span with a mark tag.
let alreadyNoteStr = 'already';
let noteCounter = 0;
let elementId;
$('p.noting').mouseup(function(e) {
elementId = $(this).attr('id');
$('#noteContent').val(''); /*flushing*/
curSentNum = elementId.split('-')[1];
$('#curSentNum').val(curSentNum);
highlightLangName = elementId.split('-')[2];
$('#highlightLangName').val(highlightLangName);
//console.log(".noting $(this).html()"+$(this).html()+" "+$(this).attr("id"));//id, for example: p-2-French
if (window.getSelection) {
highlightedText = window.getSelection().toString();
curAnchorOffset = window.getSelection().anchorOffset;
$("#noteContent").val(highlightedText);
$('#anchorAt').val(curAnchorOffset);
$('#highlightLen').val(highlightedText.length);
highlight(elementId, highlightedText);
} else if (document.selection && document.selection.type != "Control") {
highlightedText = document.selection.createRange().text;
}
});
function highlightNoteJustSaved() {
let curI = noteCounter;
let anchorAt = parseInt($("#anchorAt").val());
let highlightLen = parseInt($("#highlightLen").val());
/*p to find, for example: p-2-French*/
let curP = document.getElementById('p-' + curSentNum.toString() + "-" + $("#highlightLangName").val());
let range = document.createRange();
rootNode = curP;
let childNode = rootNode.childNodes[0];
range.setStart(rootNode.childNodes[0], anchorAt);
range.setEnd(rootNode.childNodes[0], anchorAt + highlightLen);
var newNode = document.createElement("span");
newNode.style.cssText = "background-color:#ceff99"; //yellow
newNode.className = alreadyNoteStr;
newNode.setAttribute('id', 'already-note-' + curI.toString());
range.surroundContents(newNode);
}
/*
* Takes in an array of consecutive TextNodes and returns a document fragment with `word` highlighted
*/
function highlightTextNodes(nodes, word) {
if (!nodes.length) {
return;
}
let text = '';
// Concatenate the consecutive nodes to get the actual text
for (var i = 0; i < nodes.length; i++) {
text += nodes[i].textContent;
}
let fragment = document.createDocumentFragment();
while (true) {
// Tweak this if you want to change the highlighting behavior
var index = text.toLowerCase().indexOf(word.toLowerCase());
if (index === -1) {
break;
}
// Split the text into [before, match, after]
var before = text.slice(0, index);
var match = text.slice(index, index + word.length);
text = text.slice(index + word.length);
// Create the <mark>
let mark = document.createElement('mark');
mark.className = 'found';
mark.appendChild(document.createTextNode(match));
// Append it to the fragment
fragment.appendChild(document.createTextNode(before));
fragment.appendChild(mark);
}
// If we have leftover text, just append it to the end
if (text.length) {
fragment.appendChild(document.createTextNode(text));
}
// Replace the nodes with the fragment
nodes[0].parentNode.insertBefore(fragment, nodes[0]);
for (var i = 0; i < nodes.length; i++) {
let node = nodes[nodes.length - i - 1];
node.parentNode.removeChild(node);
}
}
/*
* Highlights all instances of `word` in `$node` and its children
*/
function highlight(id, word) {
let node = document.getElementById(id);
let children = node.childNodes;
let currentRun = [];
for (var i = 0; i < children.length; i++) {
let child = children[i];
if (child.nodeType === Node.TEXT_NODE) {
// Keep track of consecutive text nodes
currentRun.push(child);
} else {
// If we hit a regular element, highlight what we have and start over
highlightTextNodes(currentRun, word);
currentRun = [];
// Ignore text inside of our <mark>s
if (child.nodeType === Node.ELEMENT_NODE && child.className !== 'found') {
highlight(child, word);
}
}
}
// Just in case we have only text nodes as children
if (currentRun.length) {
highlightTextNodes(currentRun, word);
}
}
/*
* Removes all highlighted <mark>s from the given node
*/
function unhighlight(id) {
let node = document.getElementById(id);
let marks = [].slice.call(node.querySelectorAll('mark.found'));
for (var i = 0; i < marks.length; i++) {
let mark = marks[i];
// Replace each <mark> with just a text node of its contents
mark.parentNode.replaceChild(document.createTextNode(mark.childNodes[0].textContent), mark);
}
}
label {
display: block;
position: relative;
padding-left: 100px;
}
button {
margin-top: 20px;
margin-bottom: 20px;
padding: 10px;
}
label>span {
position: absolute;
left: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button type="button" onclick="unhighlight(elementId);">Unhighlight</button>
<div id="div-0" class="only-left-border">
<p class="lan-English noting" id="p-1-English">Mr. and Mrs. Dursley, of number four, Privet Drive, were proud to say that they were perfectly normal, thank you very much.</p>
</div>
<label><span>Content:</span><input type="text" id="noteContent"></input></label>
<label><span>Numer:</span><input type="text" id="curSentNum"></input></label>
<label><span>Language:</span><input type="text" id="highlightLangName"></input></label>
<label><span>Anchor:</span><input type="text" id="anchorAt"></input></label>
<label><span>Length:</span><input type="text" id="highlightLen"></input></label>

How to add wrapper on the word with space

In JS i need to add span as wrapper on the entire document text words.using below code i can able to add wrapper
function walk(root)
{
if (root.nodeType == 3) // text node
{
doReplace(root);
return;
}
var children = root.childNodes;
for (var i = 0;i<children.length ;i++)
{
walk(children[i]);
}
}
function doReplace(text)
{
var start = counter;
counter = counter+text.nodeValue.length;
var div = document.createElement("div");
var string = text.nodeValue;
var len = string.trim();
if(len.length == 0){
return;
}
var nespan = string.replace(/\b(\w+)\b/g,function myFunction(match, contents, offset, s){
var end = start + contents.length;
var id = start+"_"+end;
start = end;
return "<span class='isparent' id='"+id+"'>"+contents+"</span>";
});
div.innerHTML = nespan;
var parent = text.parentNode;
var children = div.childNodes;
for (var i = children.length - 1 ; i >= 0 ; i--)
{
parent.insertBefore(children[i], text.nextSibling);
}
parent.removeChild(text);
}
using above code ill get below result
input :
Stackoverflow is good
out put:
<span>Stackoverflow</span> <span>is</span> <span>good</span>
expecting result:
<span>Stackoverflow </span><span>is </span><span>good</span>
Add optional space character to your regex after the word but before the second word boundary: \b(\w+\s*)\b

how to add checkboxes in cells of first column of HTML table?

I am working on an app development which will read through my mailbox and list all the unread e-mails in a HTML table on my web-app upon click of a button. Below is the code which I have made while researching through google which solves for the purpose.
<!DOCTYPE html>
<html>
<body>
<button onclick="groupFunction()">Click me</button>
<table id="tblContents">
<tr onclick="tableClickTest()">
<th>Sender</th>
<th>Sent_Date</th>
<th>Received_By</th>
<th>Received_Date</th>
<th>Subject</th>
</tr>
</table>
<script>
function RowSelection()
{
var table = document.getElementById("tblContents");
if (table != null) {
for (var i = 0; i < table.rows.length; i++) {
for (var j = 0; j < table.rows[i].cells.length; j++)
table.rows[i].cells[j].onclick = function () {
tableText(this);
};
}
}
}
function tableText(tableCell) {
alert(tableCell.innerHTML);
}
function PopulateTable()
{
var objOutlook = new ActiveXObject("Outlook.Application");
var session = objOutlook.Session;
//alert(session.Folders.Count)
for(var folderCount = 1;folderCount <= session.Folders.Count; folderCount++)
{
var folder = session.Folders.Item(folderCount);
//alert(folder.Name)
if(folder.Name.indexOf("Premanshu.Basak#genpact.com")>=0)
{
for(var subFolCount = 1; subFolCount <= folder.Folders.Count; subFolCount++)
{
var sampleFolder = folder.Folders.Item(subFolCount);
//alert(sampleFolder.Name)
if(sampleFolder.Name.indexOf("test1")>=0)
{
for(var itmCount = 1; itmCount <= sampleFolder.Items.Count; itmCount++)
{
var itm = sampleFolder.Items.Item(itmCount);
if(!itm.UnRead)
continue;
var sentBy = itm.SenderName;
var sentDate = itm.SentOn;
var receivedBy = itm.ReceivedByName;
var receivedDate = itm.ReceivedTime;
var subject = itm.ConversationTopic;
// var contents = itm.Body;
var tbl = document.getElementById("tblContents");
if(tbl)
{
var tr = tbl.insertRow(tbl.rows.length);
// tr.onclick(tableClickTest())
if(tbl.rows.length%2 != 0)
tr.className = "alt";
var tdsentBy = tr.insertCell(0);
var tdsentDate = tr.insertCell(1);
var tdreceivedBy = tr.insertCell(2);
var tdreceivedDate = tr.insertCell(3);
var tdsubject = tr.insertCell(4);
// var tdcontents = tr.insertCell(5);
tdsentBy.innerHTML = sentBy;
tdsentDate.innerHTML = sentDate;
tdreceivedBy.innerHTML = receivedBy;
tdreceivedDate.innerHTML = receivedDate;
tdsubject.innerHTML = subject;
// tdcontents.innerHTML = contents;
}
//itm.UnRead = false;
}
break;
}
}
break;
}
}
return;
}
function groupFunction()
{
PopulateTable()
RowSelection()
}
</script>
</body>
</html>
The thing that I am now looking for and is unable to do is how do I add a checkbox in the first column in each row. Also upon checking this checkbox the entire row should get highlighted so that I can perform specific task on all the selected items.
As far as I have understood your code, your first column's data is being set as:
tdsentBy.innerHTML = sentBy;
So in the same line, you can add textbox as a string as:
var cbox = "<div class='select-box'>
<input type='checkbox' name='selectBox' class='select-row'>
</div?>"
tdsentBy.innerHTML = cbox + sentBy;
In this way, a checkbox will always be available in first column of every row.
Now in RowSelection function, to bind event you can do something like:
var checkBox = table.rows[i].cells[j].querySelector(".select-row");
checkBox.addEventListener("click",function(evt){
});

Get own text content using pure javascript

I wan to collect all text from a list of elements obtains using
var elements =document.body.getElementsByTagName("*");
What I've done so far:
var text = '';
for (var i = 0; i < elements.length; i++) {
text = text + ' ' + elements[i].innerText
}
This will return duplicated text because it get the own text of each element plus its children's. I want to know if there is a way to get element's owntext using pure javasript?
I think the issue is that nested matching elements of a particular tag are being counted twice. The solution is to check if we've already visited a parent element and to skip the child if that's the case.
var text = '';
var visited = [];
for (var i = 0; i < elements.length; i++) {
var found = false;
for (var e = elements[i]; e != null; e = e.parentNode) {
if (visited.indexOf(e) > -1) {
found = true;
break;
}
}
if (!found) {
text = text + ' ' + elements[i].innerText;
visited.push(elements[i]);
}
}
http://jsfiddle.net/h8k0xx82/

Extracting a DOM element into an array of lines

I'm working on a page that includes a div with contenteditable="true" and I need to extract the text typed by the user as plain text to be later processed by some other javascript code. I then wrote this function:
function extractLines(elem) {
var nodes = elem.childNodes;
var lines = [];
for (i = 0; i < nodes.length; ++i) {
var node = nodes[i];
if (node.nodeType == 3) {
if (node.nodeValue.length > 0) {
lines.push(node.nodeValue);
}
}
if (node.nodeType == 1) {
if (node.nodeName == "BR") {
lines.push("");
}
else {
lines = lines.concat(extractLines(node));
}
}
}
return lines;
}
This takes an element and should return an array of lines. I don't expect it to work for any HTML, but it should be able to process what the browser generates on the div. Currently I'm testing on Chrome only (later I'll expand the idea to other browsers as they format of generated html is different on contenteditable divs).
Given this HTML:
<div id="target">aaa<div><br></div></div>
It correctly produces:
["aaa", ""]
But my problem is when the user insert two consecutive line breaks (EnterEnter). Chrome produces this:
<div id="target">aaa<div><br></div><div><br></div></div>
And my code gets stuck into an infinite loop. Why?
You can try with this:
console.log(extractLines(target));
Note: you might need to force-kill the tab (use Shift+Esc)
Live demo here (click).
var myElem = document.getElementById('myElem');
var myBtn = document.getElementById('myBtn');
myBtn.addEventListener('click', function() {
var results = [];
var children = myElem.childNodes;
for (var i=0; i<children.length; ++i) {
var child = children[i];
if (child.nodeName === '#text') {
results.push(child.textContent);
}
else {
var subChildren = child.childNodes;
for (var j=0; j<subChildren.length; ++j) {
var subChild = subChildren[j];
results.push(subChild.textContent);
}
}
}
console.log(results);
});
Old Answer
How about this? Live demo here (click).
var myElem = document.getElementById('myElem');
var myBtn = document.getElementById('myBtn');
myBtn.addEventListener('click', function() {
var results = [];
var children = myElem.childNodes;
for (var i=0; i<children.length; ++i) {
var text = children[i].textContent;
if (text) { //remove empty lines
results.push(text);
}
}
console.log(results);
});
You can remove that if (text) statement if you want to keep the empty lines.

Categories