hi there the idea is to copy the selected nodes and there text then remove the selection but i dont know why it's not working:
var sel = window.getSelection();
var range = sel.getRangeAt(0);
var ancestor = sel.getRangeAt(0).commonAncestorContainer;
var child = ancestor.childNodes;
var len = child.length, div=[], text = [];
for (var i =0; i<len; i++){
div.push(child[i].localName);
text.push(child[i].textContent);
}
sel.removeAllRanges();
Related
I am working on a chrome extension, which replaces part of the text. It does not work as expected on Gmail though.
Scanario:
Gmail composer has following text -
i am now to this.
i can do this.
I want to replace it to -
i am new to this.
i can do this.
However, whenever I execute the code, it does not replace the text on the correct location.
It sees where my cursor was and appends the text there instead of replacing the intended text.
This snippet works on other websites, which have contenteditable editors.
My current implementation looks like the following:
const range = document.createRange();
const ele = <div tag element for 'i am now to this.' sentence>
// rangeStart and rangeEnd are the index which wraps word 'now'
range.setStart(ele.childNodes[0], rangeStart);
range.setEnd(ele.childNodes[0], rangeEnd);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
setTimeout(()=>{
document.execCommand(
"insertText",
false,
"new"
);
},0)
There are a couple of things that might go wrong about it.
One is that I noticed the div initially containing a single textNode with some text switched to a node containing multiple textNodes with exactly the same over all text and that can create a number of problems considering that you are operating only with children[0].
Another thing that didn't work for me was the document executing the isertText
How so ever something that worked for me:
var range = document.createRange();
var rangeStart = el.innerText.indexOf(s);
var rangeEnd = rangeStart + s.length;
var selection = window.getSelection();
range.setStart(el.childNodes[0], rangeStart);
range.setEnd(el.childNodes[0], rangeEnd);
selection.removeAllRanges();
selection.addRange(range);
range.deleteContents();
range.insertNode(document.createTextNode(ss))
My entire testing scenario:
(()=>{
var all = document.getElementsByTagName('div');
var n = all.length;
var find = (s)=>{
var result = [];
for (var i = 0; i < n; i++) {
var el = all[i];
var text = el && el.childNodes && el.childNodes[0] && el.childNodes[0].wholeText;
if (text && text.match(s)) {
result.push(el);
}
}
return result.length ? result : false;
}
;
var replacer = (s,ss)=>(el)=>{
try {
var range = document.createRange();
var rangeStart = el.innerText.indexOf(s);
var rangeEnd = rangeStart + s.length;
range.setStart(el.childNodes[0], rangeStart);
range.setEnd(el.childNodes[0], rangeEnd);
var selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
range.deleteContents();
range.insertNode(document.createTextNode(ss))
}catch(ex){
}
}
;
var elements = find('now');
if (elements) {
elements.map(replacer('now', 'new'))
}
}
)(window);
Good luck :)
I am trying to make a web app which allows the user to select some text on a page, and then with the click of another button highlight it. When the user goes back to the page, I want the highlight to show up in the same spot as well.
So I've gotten as far as:
var selectedRange = document.getSelection().getRangeAt(0);
highlightRange(selectedRange);
Where highlightRange is a function that highlights the range. This works so far.
The problem is, I need a way to save the selectedRange into a database so that it can be fetched again later. After that, I need to re-create the range from this data and highlight it again. I've found this method:
document.createRange();
From this page here: https://developer.mozilla.org/en-US/docs/Web/API/range
But I'm not really sure how I can make this work
UPDATE:
I know I'll need to re-create the range from scratch afterwards. And to do that I will use something like this:
var range = document.createRange();
range.setStart(startNode,startOffset);
range.setEnd(endNode,endOffset);
I can easily store startOffset and endOffset because those are just numbers. But startNode and endNode are node objects. I don't know how to store this in a database?
More specifically, I need to store the reference to the node in a database.
I solved this problem by saving 5 pieces of information from the range into the database:
var saveNode = range.startContainer;
var startOffset = range.startOffset; // where the range starts
var endOffset = range.endOffset; // where the range ends
var nodeData = saveNode.data; // the actual selected text
var nodeHTML = saveNode.parentElement.innerHTML; // parent element innerHTML
var nodeTagName = saveNode.parentElement.tagName; // parent element tag name
And then to build the range from the database, I have this function:
function buildRange(startOffset, endOffset, nodeData, nodeHTML, nodeTagName){
var cDoc = document.getElementById('content-frame').contentDocument;
var tagList = cDoc.getElementsByTagName(nodeTagName);
// find the parent element with the same innerHTML
for (var i = 0; i < tagList.length; i++) {
if (tagList[i].innerHTML == nodeHTML) {
var foundEle = tagList[i];
}
}
// find the node within the element by comparing node data
var nodeList = foundEle.childNodes;
for (var i = 0; i < nodeList.length; i++) {
if (nodeList[i].data == nodeData) {
var foundNode = nodeList[i];
}
}
// create the range
var range = cDoc.createRange();
range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
return range;
}
From there, I can just use my highlightRange function again to highlight the text.
Update 2022-01
Didn't know this was actually still being used. Thought I might as well give two cents about the two for loops and how we can improve them with modern syntax:
const foundEle = tagList.find(x => x.innerHTML === nodeHTML);
const foundNode = nodeList.find(x => x.data === nodeData);
run the following script when user select texts
let sel = window.getSelection();
let range = sel.getRangeAt(0);
let startNode = range.startContainer;
let endNode = range.endContainer;
if (startNode.nodeType == 3) {
var startIsText = true;
var startFlag = startNode.parentNode;
startNode = startNode.nodeValue;
} else {
var startIsText = false;
var startFlag = startNode;
}
if (endNode.nodeType == 3) {
var endIsText = true;
var endFlag = endNode.parentNode;
endNode = endNode.nodeValue;
} else {
var endIsText = false;
var endFlag = endNode;
}
let startOffset = range.startOffset;
let endOffset = range.endOffset;
let startTagName = startFlag.nodeName;
let startHTML = startFlag.innerHTML;
let endTagName = endFlag.nodeName;
let endHTML = endFlag.innerHTML;
//you can store this in database and use it
let rInfo = {
startNode: startNode,
startOffset: startOffset,
startIsText: startIsText,
startTagName: startTagName,
startHTML: startHTML,
endNode: endNode,
endOffset: endOffset,
endIsText: endIsText,
endTagName: endTagName,
endHTML: endHTML
};
window.localStorage.setItem("r", JSON.stringify(rInfo));
then use the following scripts when user go back to the page
function findEle(tagName, innerHTML) {
let list = document.getElementsByTagName(tagName);
for (let i = 0; i < list.length; i++) {
if (list[i].innerHTML == innerHTML) {
return list[i];
}
}
}
function show(startNode,startIsText,startOffset,
endNode,endIsText,endOffset,sP,eP) {
var s, e;
if (startIsText) {
let childs = sP.childNodes;
console.log(childs);
for (let i = 0; i < childs.length; i++) {
console.log(childs[i].nodeValue);
console.log(startNode);
if (childs[i].nodeType == 3 && childs[i].nodeValue == startNode)
s = childs[i];
console.log(s);
}
} else {
s = startNode;
}
if (endIsText) {
let childs = eP.childNodes;
console.log(childs);
for (let i = 0; i < childs.length; i++) {
if (childs[i].nodeType == 3 && childs[i].nodeValue == endNode)
e = childs[i];
console.log(e);
}
} else {
e = endNode;
}
let range = document.createRange();
range.setStart(s, startOffset);
range.setEnd(e, endOffset);
let sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
function use(obj) {
let sP = findEle(obj.startTagName, obj.startHTML);
let eP = findEle(obj.endTagName, obj.endHTML);
show(
obj.startNode,
obj.startIsText,
obj.startOffset,
obj.endNode,
obj.endIsText,
obj.endOffset,
sP,
eP
);
}
let a = window.localStorage.getItem("r");
use(JSON.parse(a));
I am using document.getSelection() to select some text. I would like to know what type of element the selected range contains (I specifically want to see if it is an anchor tag).
var selection = document.getSelection();
var range = selection.getRangeAt(0);
So I can get the range, but how can I know what element is in that range? Happy to use plain js or jQuery.
EDIT:
Here is what I came up with:
var updateLink = function(url) {
var selection = document.getSelection();
var range = selection.getRangeAt(0);
if (range != 0) {
var containerElement = range.commonAncestorContainer;
if (containerElement.nodeType != 1) {
containerElement = containerElement.parentNode;
var e = $(containerElement);
e.attr('href', url);
}
}
}//end
Try this:
var obj = document.getSelection();
var parentNode = $(obj.anchorNode).parent();
Here is jsfiddle
You could use cloneContents() method:
DEMO
$(document).mouseup(function(){
var range = window.getSelection().getRangeAt(0),
selectionContents = range.cloneContents();
alert($(selectionContents.childNodes).filter('a').length);
});
I have a script to wrap html tags around the selection.
function wrap(tagName)
{
var selection;
var elements = [];
var ranges = [];
var rangeCount = 0;
if (window.getSelection)
{
selection = window.getSelection();
if (selection.rangeCount)
{
rangeCount = selection.rangeCount;
for (var i=0; i<rangeCount; i++)
{
ranges[i] = selection.getRangeAt(i).cloneRange();
elements[i] = document.createElement(tagName);
elements[i].appendChild(ranges[i].cloneContents());
ranges[i].deleteContents();
ranges[i].insertNode(elements[i]);
ranges[i].selectNode(elements[i]);
}
selection.removeAllRanges();
for (var i=0; i<ranges.length; i++)
{
selection.addRange(ranges[i]);
}
}
}
}
It works fine, but when I try to select the following text and wrap tags around the following code
<strong>W</strong>elcom<strong>e</strong>
The code changes to
<strong></strong><u><strong>W</strong>elcom<strong>e</strong></u><strong></strong>
instead of
<u><strong>W</strong>elcom<strong>e</strong></u>
I have inspected the code with firebug and it goes wrong at the function deleteContents(). The function deleteContents() leaves two empty nodes <strong></strong> so it doesn't delete the whole content. How does this happen?
I have this function which removes selected (cursor) highlighted elements:
function deleteSelection() {
if (window.getSelection) {
// Mozilla
var selection = window.getSelection();
if (selection.rangeCount > 0) {
window.getSelection().deleteFromDocument();
window.getSelection().removeAllRanges();
}
} else if (document.selection) {
// Internet Explorer
var ranges = document.selection.createRangeCollection();
for (var i = 0; i < ranges.length; i++) {
ranges[i].text = "";
}
}
}
What I actually want to do is remove all the cursor- highlighted elements, except for the input#cursor element.
edit:
so lets say if I highlighted these elements with my cursor:
<span>a</span>
<span>b</span>
<input type='text' id = 'cursor' />
<span>c</span>
<span>d</span>
on a keyup, this function should not remove the <input> ...only the <span>
Phew, that was a little harder than expected!
Here's the code, I've tested it in IE8 and Chrome 16/FF5. The JSFiddle I used to test is available here. AFAIK, this is probably the best performance-wise you'll get.
The docs I used for Moz: Selection, Range, DocumentFragment. IE: TextRange
function deleteSelection() {
// get cursor element
var cursor = document.getElementById('cursor');
// mozilla
if(window.getSelection) {
var selection = window.getSelection();
var containsCursor = selection.containsNode(cursor, true);
if(containsCursor) {
var cursorFound = false;
for(var i=0; i < selection.rangeCount; i++) {
var range = selection.getRangeAt(i);
if(!cursorFound) {
// extracts tree from DOM and gives back a fragment
var contents = range.extractContents();
// check if tree fragment contains our cursor
cursorFound = containsChildById(contents, 'cursor');
if(cursorFound) range.insertNode(cursor); // put back in DOM
}
else {
// deletes everything in range
range.deleteContents();
}
}
}
else {
selection.deleteFromDocument();
}
// removes highlight
selection.removeAllRanges();
}
// ie
else if(document.selection) {
var ranges = document.selection.createRangeCollection();
var cursorFound = false;
for(var i=0; i < ranges.length; i++) {
if(!cursorFound) {
// hacky but it will work
cursorFound = (ranges[i].htmlText.indexOf('id=cursor') != -1);
if(cursorFound)
ranges[i].pasteHTML(cursor.outerHTML); // replaces html with parameter
else
ranges[i].text = '';
}
else {
ranges[i].text = '';
}
}
}
}
// simple BFS to find an id in a tree, not sure if you have a
// library function that does this or not
function containsChildById(source, id) {
q = [];
q.push(source);
while(q.length > 0) {
var current = q.shift();
if(current.id == id)
return true;
for(var i=0; i < current.childNodes.length; i++) {
q.push(current.childNodes[i]);
}
}
return false;
}
I would suggest removing the element you want keep, extracting the contents of the selection using Range's extractContents() method and then reinserting the element you want to keep using the range's insertNode() method.
For IE < 9, which doesn't support DOM Range, the code is trickier, so for simplicity you could use my Rangy library, which also adds convenience methods such as the containsNode() method of Range.
Live demo: http://jsfiddle.net/DFxH8/1/
Code:
function deleteSelectedExcept(node) {
var sel = rangy.getSelection();
if (!sel.isCollapsed) {
for (var i = 0, len = sel.rangeCount, range, nodeInRange; i < len; ++i) {
range = sel.getRangeAt(i);
nodeInRange = range.containsNode(node);
range.extractContents();
if (nodeInRange) {
range.insertNode(node);
}
}
}
}
deleteSelectedExcept(document.getElementById("cursor"));