We need to add anchors and highlights for some keywords/sentences in the html page. It turns out the highlighting is really slow in Firefox.
In the following code, all ranges which need to be highlighted are stored in array hiliteRanges:
for (var i = 0; i < hiliteRanges.length; i++){
document.designMode = "on";
var selHilites = window.getSelection();
if (selHilites.rangeCount > 0)
selHilites.removeAllRanges();
selHilites.addRange(hiliteRanges[i]);
var anchorId = 'index'+i;
var insertedHTML = '<span id="' + anchorId + '" style="background-color: #FF8C00;" >'+hiliteRanges[i].toString()+'</span>';
document.execCommand('inserthtml', false, insertedHTML);
document.designMode = "off";
}
Is there any way to speed up the processing? We could have hundreds of ranges in the array hiliteRanges. We once tried moving the designMode setting outside of the loop, but we can see some sections are editable in the html page when the loop is running.
This is my default highlighting snippet and works fine in every browser. Try it out.
Demo: http://jsbin.com/adeneh/1/edit
function highlight(text, words, tag) {
// Default tag if no tag is provided
tag = tag || 'span';
var i, len = words.length, re;
for (i = 0; i < len; i++) {
// Global regex to highlight all matches
re = new RegExp(words[i], 'g');
if (re.test(text)) {
text = text.replace(re, '<'+ tag +' class="highlight">$&</'+ tag +'>');
}
}
return text;
}
// Usage:
var el = document.getElementById('element');
el.innerHTML = highlight(
el.innerHTML,
['word1', 'word2', 'phrase one', 'phrase two', ...]
);
And to unhighlight:
function unhighlight(text, tag) {
// Default tag if no tag is provided
tag = tag || 'span';
var re = new RegExp('(<'+ tag +'.+?>|<\/'+ tag +'>)', 'g');
return text.replace(re, '');
}
There's no need to use document.execCommand() for this. Just use range methods instead, and then there's no need for designMode.
var anchorId, hiliteTextNode, hiliteSpan;
for (var i = 0; i < hiliteRanges.length; i++){
// Create the highlight element
hiliteSpan = document.createElement("span");
hiliteSpan.id = anchorId;
hiliteSpan.style.backgroundColor = "#FF8C00";
hiliteTextNode = document.createTextNode(hiliteRanges[i].toString());
hiliteSpan.appendChild(hiliteTextNode);
// Replace the range content
hiliteRanges[i].deleteContents();
hiliteRanges[i].insertNode(hiliteSpan);
}
Also, since ranges are affected by DOM mutation, I would suggest doing this part at the same time as you collect the ranges with window.find(). Here's an example:
http://jsfiddle.net/YgFjT/
Related
I have written a custom search in javascript for highlighting texts.
the scenario is to get innerHtml and search for the text and highlight them.
the problem: if the user search for i the i in the <div> tag were found and everything messed up.
var textBlock=document.body.innerHTML;
searchIndex = textBlock.toLowerCase().indexOf(what.toLowerCase(), 0);
while(searchIndex >= 0)
{
++counter;
ID = "result" + counter;
replacement = '<span id='+ID+' style="background-color:#f0da1e">'+what+'</span>';
textBlock = textBlock.substring(0, searchIndex) + replacement + textBlock.substring(searchIndex + what.length, textBlock.length);
searchIndex = textBlock.toLowerCase().indexOf(what.toLowerCase(), (searchIndex + replacement.length));
}
document.body.innerHTML=textBlock;
what can i do to skip founded index in tags?
something like this:
if(isTag(searchIndex))
//do nothing
UPDATE:
if i use innerText instead of innerHtml then all of my text format and style will be ruind.
var textBlock=document.body.innerText;
document.body.innerHTML=textBlock;
One possible solution would be to work with nodes:
Get the body child nodes (instead of the innerHTML)
Traverse the nodes:
If it's a text node (leaf): search for the string to replace.
If it's an element node: get and traverse the children.
Here is a sample function that will highlight the text that you specify:
function highlightText(nodeList, what) {
// traverse all the children nodes
for (var x = 0; x < nodeList.length; x++) {
// text node, search directly
if (nodeList[x].nodeType == 3) {
// if it contains the text that you are looking for, proceed with the replacement
if (nodeList[x].textContent.indexOf(what) >= 0) {
// your code (mostly :P)
var ID = "result" + counter;
var replacement = '<span id="'+ID+'" style="background-color:#f0da1e">'+what+'</span>';
var textBlock = nodeList[x].textContent;
var searchIndex = nodeList[x].textContent.indexOf(what);
while(searchIndex >= 0)
{
++counter;
ID = "result" + counter;
replacement = '<span id="'+ID+'" style="background-color:#f0da1e">'+what+'</span>';
textBlock = textBlock.substring(0, searchIndex) + replacement + textBlock.substring(searchIndex + what.length, textBlock.length);
searchIndex = textBlock.toLowerCase().indexOf(what.toLowerCase(), (searchIndex + replacement.length));
}
// create a new element with the replacement text
var replacementNode = document.createElement("span");
replacementNode.innerHTML = textBlock;
// replace the old node with the new one
var parentN = nodeList[x].parentNode;
parentN.replaceChild(replacementNode, parentN.childNodes[x]);
}
} else {
// element node --> search in its children nodes
highlightText(nodeList[x].childNodes, what);
}
}
}
And here is a sample demo (also available on this JSFiddle):
var counter = 0;
function highlightText(nodeList, what) {
// traverse all the children nodes
for (var x = 0; x < nodeList.length; x++) {
// text node, search directly
if (nodeList[x].nodeType == 3) {
// if it contains the text that you are looking for, proceed with the replacement
if (nodeList[x].textContent.indexOf(what) >= 0) {
// your code (mostly :P)
var ID = "result" + counter;
var replacement = '<span id="'+ID+'" style="background-color:#f0da1e">'+what+'</span>';
var textBlock = nodeList[x].textContent;
var searchIndex = nodeList[x].textContent.indexOf(what);
while(searchIndex >= 0)
{
++counter;
ID = "result" + counter;
replacement = '<span id="'+ID+'" style="background-color:#f0da1e">'+what+'</span>';
textBlock = textBlock.substring(0, searchIndex) + replacement + textBlock.substring(searchIndex + what.length, textBlock.length);
searchIndex = textBlock.toLowerCase().indexOf(what.toLowerCase(), (searchIndex + replacement.length));
}
// create a new element with the replacement text
var replacementNode = document.createElement("span");
replacementNode.innerHTML = textBlock;
// replace the old node with the new one
var parentN = nodeList[x].parentNode;
parentN.replaceChild(replacementNode, parentN.childNodes[x]);
}
} else {
// element node --> search in its children nodes
highlightText(nodeList[x].childNodes, what);
}
}
}
var nodes = document.body.childNodes;
console.log(nodes);
highlightText(nodes, "ar");
<p>Men at some time are masters of their fates: The fault, dear Brutus, is not in our stars, but in ourselves, that we are underlings.</p>
<p><b>William Shakespeare</b>, <em>Julius Caesar</em> (Act I, Scene II)</p>
One issue with this solution is that it adds additional span elements wrapping each text node that contained the searched string (although I don't know how big of an inconvenience that may be for you). It is also recursive, you may want to look into an iterative alternative.
UPDATE. I know you didn't ask for this, but I thought it could be interesting: by reordering the parameters list, and adding some initialization on the first call, you can make the function cleaner for the user, and at the same time, add some interesting functionality:
function highlightText(what, node) {
// initialize values if first call
node = node || document.body;
var nodeList = node.childNodes;
// traverse all the children nodes
for (var x = 0; x < nodeList.length; x++) {
// text node, search directly
if (nodeList[x].nodeType == 3) {
// if it contains the text that you are looking for, proceed with the replacement
if (nodeList[x].textContent.indexOf(what) >= 0) {
// your code (mostly :P)
var ID = "result" + counter;
var replacement = '<span id="'+ID+'" style="background-color:#f0da1e">'+what+'</span>';
var textBlock = nodeList[x].textContent;
var searchIndex = nodeList[x].textContent.indexOf(what);
while(searchIndex >= 0)
{
++counter;
ID = "result" + counter;
replacement = '<span id="'+ID+'" style="background-color:#f0da1e">'+what+'</span>';
textBlock = textBlock.substring(0, searchIndex) + replacement + textBlock.substring(searchIndex + what.length, textBlock.length);
searchIndex = textBlock.toLowerCase().indexOf(what.toLowerCase(), (searchIndex + replacement.length));
}
// create a new element with the replacement text
var replacementNode = document.createElement("span");
replacementNode.innerHTML = textBlock;
// replace the old node with the new one
var parentN = nodeList[x].parentNode;
parentN.replaceChild(replacementNode, parentN.childNodes[x]);
}
} else {
// element node --> search in its children nodes
highlightText(what, nodeList[x]);
}
}
}
Now, to search a string within the page, you can simply do:
highlightText("ar");
(No second parameter needed as before)
But if you pass an element as a second parameter to the function, then the search will be performed exclusively within the specified element and not in the whole page:
highlightText("ar", document.getElementById("highlight_only_this"));
You can see a demo working on this JSFiddle: http://jsfiddle.net/tkm5696w/2/
Probably you can use innerText instead of innerHTML.
You can use element.textContent.
Differences between innerText and textContent can be in the below link.
MDN - textContent
Internet Explorer introduced element.innerText. The intention is similar but with the following differences:
While textContent gets the content of all elements, including
and elements, the IE-specific property innerText
does not.
innerText is aware of style and will not return the text of hidden
elements, whereas textContent will.
As innerText is aware of CSS styling, it will trigger a reflow,
whereas textContent will not.
innerHtml will search for both text and elements inside given element. Use innerText or textContent to only search for text (I understand that is what you want)
Suppose I have written 5 lines in ckeditor and I have selected the 2nd and 3rd line. How to get the html source of the selected text.Assuming the code will be selected in continuation always.
function getSelectionHtml()
{
editor=CKEDITOR.instances.editor1;
var sel = editor.getSelection();
var ranges = sel.getRanges();
var el = new CKEDITOR.dom.element("div");
for (var i = 0, len = ranges.length; i < len; ++i) {
el.append(ranges[i].cloneContents());
}
console.log("OrgHtml:\n"+el.getHtml());
return el.getHtml();
}
Use this function to get the selected text's InnerHtml value.I'm using this function also.
You can get your active selection with
var sel = window.getSelection();
if (sel.rangeCount) {
var container = document.createElement("div");
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
text = container.innerHTML;
}
the HTML you selected will be stored in the variable text.
All selected HTML nodes - CKEditor
Note: It all depends on the current structure of data in your ckeditor. If you have selected a text which is inside a tag and text is partially selected then complete tag will get returned.
Note eg:
<p>my ckeditor text</p> enclosed in a p-tag
if you have selected "editor te" then complete p-tag will get return as you said you need HTML not selected text.
Code:
var range = editor.getSelection().getRanges()[0] //editor is instance of your ck-editor
var selectedHTML = [];
console.log(range.startPath().elements[0].$, 'first')
selectedHTML.push(range.startPath().elements[0].$)
var selectedSibling = "";
if(range.startPath().elements[0].$ != range.endPath().elements[0].$) {
selectedSibling = range.startContainer.$.parentNode.nextElementSibling
while(selectedSibling && (selectedSibling != range.endPath().elements[0].$)) {
console.log(selectedSibling, 'next')
selectedHTML.push(selectedSibling)
selectedSibling = selectedSibling.nextElementSibling
}
console.log(range.endPath().elements[0].$, 'last')
selectedHTML.push(range.endPath().elements[0].$)
}
console.log(selectedHTML, 'selected HTML Tags')
I'm confused on how to change text content of div with the DOM. When event is triggered, I see that the new text replace the old but it is in a new div. I want to keep it in "transcriptText" to keep all attributes.`How can I do that?
This is my old div with text inside:
var transcriptText = document.getElementById("transcriptText");
these are my new text SPAN elements
var newTranscript = document.createElement("div");
This is how I handle the event
function EventHandler() {
transcriptText.parentNode.replaceChild(newTranscript, transcriptText);
}
Here is the JSFiddle on how it currently works:
http://jsfiddle.net/b94DG/
What you're doing now is creating a new div, newTranscript, which you create by appending a bunch of spans based on the old text. Then in your event handler you replace the old one with the new one. Instead of that, you could still copy the text from the old one, but then clear it and append the children on the old div, replacing line 36 with:
transcriptText.appendChild(newSpan);
To clear the old element, it might work to just set innerHTML to "", or if necessary you could remove all the children with removeChild as described at https://developer.mozilla.org/en-US/docs/Web/API/Node.removeChild
EDIT:
I modified your fiddle to reflect this:
http://jsfiddle.net/b94DG/1/
You can change the innerHTML of transcriptText instead of creating a new div.
var transcriptText = document.getElementById("transcriptText");
var divideTranscript = document.getElementById("divideTranscript");
divideTranscript.onclick = function() {
var sArr = transcriptText.innerHTML.split(" ");
var newInnerHTML = "";
for (var i = 0; i < sArr.length; i++) {
var item = sArr[i];
var newText = "<span class='highlight' id='word" + i + "'>" + item + " </span>";
newInnerHTML += newText;
}
transcriptText.innerHTML = newInnerHTML;
var mouseOverFunction = function () {
this.style.backgroundColor = 'yellow';
};
var mouseOutFunction = function () {
this.style.backgroundColor = '';
};
var highlight = document.getElementsByClassName("highlight");
for (i = 0; i < highlight.length; i++) {
highlight[i].onmouseover = mouseOverFunction;
highlight[i].onmouseout = mouseOutFunction;
}
};
Here's the fiddle: http://jsfiddle.net/khnGN/
I have the following javascript function that aims to append a BIDI code to text that matches a specific criteria.The problem with the current function is that it works great with text only content but is problematic with content that has HTML Content. To summarize, I am splitting the text into words and checking if any of the characters in each word is matching a certain criteria to append the BIDI code to it.
The current status:
<div>This is the text that I am processing</div> //This works great
<div><span>This is the text</span><p>that I am processing</p></div> // This doesn't work well.
I want the function to work is for the text but also to keep the wrapping HTML tags in their place in order to keep etc....
function flipper(flipselector) {
var pagetitle = $(flipselector);
var text = pagetitle.text().split(' '); //I know that I am using text function here but .html didn't work either
var newtext="";
for( var i = 1, len = text.length; i < len; i=i+1 ) {
//text[i] = '<span>' + text[i] + '</span>';
newstring="";
if (matches = text[i].match(/\d/))
{
var currentstring=text[i];
for (var x = 0, charlen = currentstring.length; x < charlen; x++) {
if (matches = currentstring[x].match(/\d/)) {
varnewchar=currentstring[x];
}else {
varnewchar= "" + currentstring[x];
}
newstring=newstring + varnewchar;
}
} else {
newstring= text[i];
}
newtext=newtext + " " + newstring;
}
pagetitle.html(newtext);
}
I have seen many posts pertaining to highlighting text in a DIV using javascript, but none do quite what I'm looking for.
What I need to do is highlight the text within a specific DIV, character by character as the user enters the search term. Conversely, as the user backspaces or deletes characters, I need to "de-highlight" the text of the same DIV.
I imagine this has already been done somewhere by someone, but I have not yet found a post here or from Google that behaves exactly as I need.
Any feedback is appreciated.
this code executes as user types characters into an input field. The problem with it is that in some instances, it inserts the string " " into the table as I type and I don't know why, so I'm searching for a different solution.
Thanks for your feedback!
function filterTable(Stxt, table) {
dehighlight(document.getElementById(table));
if (Stxt.value.length > 0)
highlight(Stxt.value.toLowerCase(), document.getElementById(table));
}
function dehighlight(container) {
for (var i = 0; i < container.childNodes.length; i++) {
var node = container.childNodes[i];
if (node.attributes && node.attributes['class'] && node.attributes['class'].value == 'highlighted') {
node.parentNode.parentNode.replaceChild(
document.createTextNode(node.parentNode.innerHTML.replace(/<[^>]+>/g, "")),node.parentNode);
return;
} else if (node.nodeType != 3) {
dehighlight(node);
}
}
}
function highlight(Stxt, container) {
for (var i = 0; i < container.childNodes.length; i++) {
var node = container.childNodes[i];
if (node.nodeType == 3) {
var data = node.data;
var data_low = data.toLowerCase();
if (data_low.indexOf(Stxt) >= 0) {
var new_node = document.createElement('span');
node.parentNode.replaceChild(new_node, node);
var result;
while ((result = data_low.indexOf(Stxt)) != -1) {
new_node.appendChild(document.createTextNode(data.substr(0, result)));
new_node.appendChild(create_node(
document.createTextNode(data.substr(result, Stxt.length))));
data = data.substr(result + Stxt.length);
data_low = data_low.substr(result + Stxt.length);
}
new_node.appendChild(document.createTextNode(data));
}
} else {
highlight(Stxt, node);
}
}
}
function create_node(child) {
var node = document.createElement('span');
node.setAttribute('class', 'highlighted');
node.attributes['class'].value = 'highlighted';
node.appendChild(child);
return node;
}
This can be easily done with a regular expression to change the div's content. Here's a simple implementation :
var s = document.getElementById('s'); // your input
var div = document.getElementById('a'); // the div to change
var t = a.textContent || a.innerText;
s.onkeyup = function(){
div.innerHTML = this.value
? t.replace(new RegExp('('+this.value+')','ig'), '<span class=highlight>$1</span>')
: t;
};
Demonstration (click "Run with JS")
EDIT :
This more sophisticated version works even if you have tables and stuff :
var s = document.getElementById('s');
var div = document.getElementById('a');
function changeNode(n, r, f) {
f=n.childNodes; for(c in f) changeNode(f[c], r);
if (n.data) {
f = document.createElement('span');
f.innerHTML = n.data.replace(r, '<span class=found>$1</span>');
n.parentNode.insertBefore(f, n);
n.parentNode.removeChild(n);
}
}
s.onkeyup = function(){
var spans = document.getElementsByClassName('found');
while (spans.length) {
var p = spans[0].parentNode;
p.innerHTML = p.textContent || p.innerText;
}
if (this.value) changeNode(
div, new RegExp('('+this.value+')','gi')
);
};
Demonstration (click "Run with JS")
My Rangy library has support for this, although I admit it's quite a large script for just this one use.
Demo: http://rangy.googlecode.com/svn/trunk/demos/textrange.html
I made a demo that uses regex.
// Input element
var input = document.getElementById("highlighter"),
// Text container element
divText = document.getElementById("text"),
// using textContent property if it exists
textProp = ("textContent" in divText) ? "textContent" : "innerText",
// Getting text to discard html tags (delete line 6 and use divText.innerHTML if you want to keep the HTML tags)
originalText = divText[textProp];
function handler(){
// if Input.value is empty clear the highlights
if(!this.value){
divText.innerHTML = originalText;
return true;
}
// Regex to group the matches, with tags 'global' and 'case insensitive'
var regex = new RegExp("("+this.value+")", "ig");
// replace text with the new one ($1 refers to first group matched by regex)
divText.innerHTML = originalText.replace(regex, "<span class='highlight'>$1</span>");
};
// adding listener to input.. IE uses attachEvent method
input.addEventListener("keyup", handler, false);
JSFiddle DEMO
let keywords = $("#highlight").html(); //get replace text
let textBody = $("#textBody"); //replace body
//create regular expression //text without case change
let custfilter = new RegExp("(" + keywords + ")", "ig");
//replace with highlight
textBody.html(textBody.html().replace(custfilter, "<b>$1</b>"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.2.3/jquery.min.js"></script>
<p id="textBody">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</p>
<span id="highlight">IPSUM</span>