I want to get all of the elements in a user highlighted area. The problem is that I don't know how to traverse to different parts of the DOM, when the elements are on a different part of the tree. My code so far can only get the elements off the startContainer of the selection range. Do I need some sort of recursion? Here is my code:
getSelectedElementTags:function()
{
var range, sel, container;
sel = content.window.getSelection();
if (sel.getRangeAt)
{
if (sel.rangeCount > 0)
{
range = sel.getRangeAt(0);
}
}
else
{
range = content.window.createRange();
range.setStart(sel.anchorNode, sel.anchorOffset);
range.setEnd(sel.focusNode, sel.focusOffset);
alert("range created");
}
if (range)
{
container = range["startContainer"];
var elms = container.parentNode.getElementsByTagName("*");
elmlist = "parent: "+container.parentNode.tagName + " (" + elms.length + ")\n";
for (i in elms)
{
if (elms[i].tagName != null)
{
elmlist += elms[i].tagName+"\n";
}
}
alert(elmlist);
}
},
You could use a TreeWalker using document.createTreeWalker. An example is below. It lists all elements that are partially or fully selected. You can easily change the behaviour by modifying the parameters passed to document.createTreeWalker.
Note that in Firefox you don't need to check for the existence of the getRangeAt method of a selection. This check is only required for older versions of WebKit. Also, IE < 9 does not support TreeWalker or Range, so the following won't work in those browsers.
Edit Fixed as per comments below.
function rangeIntersectsNode(range, node) {
var nodeRange;
if (range.intersectsNode) {
return range.intersectsNode(node);
} else {
nodeRange = node.ownerDocument.createRange();
try {
nodeRange.selectNode(node);
} catch (e) {
nodeRange.selectNodeContents(node);
}
return range.compareBoundaryPoints(Range.END_TO_START, nodeRange) == -1 &&
range.compareBoundaryPoints(Range.START_TO_END, nodeRange) == 1;
}
}
function getSelectedElementTags(win) {
var range, sel, elmlist, treeWalker, containerElement;
sel = win.getSelection();
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0);
}
if (range) {
containerElement = range.commonAncestorContainer;
if (containerElement.nodeType != 1) {
containerElement = containerElement.parentNode;
}
treeWalker = win.document.createTreeWalker(
containerElement,
NodeFilter.SHOW_ELEMENT,
function(node) { return rangeIntersectsNode(range, node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; },
false
);
elmlist = [treeWalker.currentNode];
while (treeWalker.nextNode()) {
elmlist.push(treeWalker.currentNode);
}
console.log(elmlist);
}
}
<input type="button" onclick="getSelectedElementTags(window)" value="Get selected elements">
Related
I want to store user selection in a database in order to highlight them the next time user visits the page.
Here is the code I am using to add a span around the selected text.
window.rangeIntersectsNode = function(range, node) {
var nodeRange;
if (range.intersectsNode) {
return range.intersectsNode(node);
} else {
nodeRange = node.ownerDocument.createRange();
try {
nodeRange.selectNode(node);
} catch (e) {
nodeRange.selectNodeContents(node);
}
return range.compareBoundaryPoints(Range.END_TO_START, nodeRange) == -1 && range.compareBoundaryPoints(Range.START_TO_END, nodeRange) == 1;
}
}
window.getSelectedElementTags = function(win) {
var range, sel, elmlist, treeWalker, containerElement;
sel = win.getSelection();
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0);
}
if (range) {
containerElement = range.commonAncestorContainer;
if (containerElement.nodeType != 1) {
containerElement = containerElement.parentNode;
}
treeWalker = win.document.createTreeWalker(
containerElement, NodeFilter.SHOW_TEXT, function(node) {
return rangeIntersectsNode(range, node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
}, false);
elmlist = [treeWalker.currentNode];
while (treeWalker.nextNode()) {
elmlist.push(treeWalker.currentNode);
}
return (elmlist);
}
}
window.wrapInSpan = function(range) {
var tempElement;
var selectedArr = getSelectedElementTags(window);
for (var i = 2; i < (selectedArr.length - 1); i++) {
tempElement = document.createElement("span");
tempElement.innerHTML = selectedArr[i].nodeValue;
selectedArr[i].parentElement.replaceChild(tempElement, selectedArr[i])
}
var sC = range.startContainer;
if (selectedArr.length == 2) {
if (sC.nodeType == 3) {
tempElement = document.createElement("span");
tempElement.innerHTML = sC.nodeValue.substring(range.startOffset, range.endOffset);
var anc1 = sC.splitText(range.startOffset);
var anc2 = (anc1.splitText(range.endOffset)).previousSibling;
anc2.parentElement.replaceChild(tempElement, anc2);
}
} else {
if (sC.nodeType == 3) {
tempElement = document.createElement("span");
tempElement.innerHTML = sC.nodeValue.substring(range.startOffset);
sC.parentElement.insertBefore(tempElement, sC.nextSibling);
sC.nodeValue = sC.nodeValue.replace(sC.nodeValue.substring(range.startOffset), "");
}
var eC = range.endContainer;
if (eC.nodeType == 3) {
tempElement = document.createElement("span");
tempElement.innerHTML = eC.nodeValue.substring(0, range.endOffset);
eC.parentElement.insertBefore(tempElement, eC);
eC.nodeValue = eC.nodeValue.replace(eC.nodeValue.substring(0, range.endOffset), "");
}
}
}
i am using the following code to get the details of the selection, but it in the console I get always get the variables as undefined.
var startNode=range.anchorNode;
var endNode=range.focusNode;
var startoffset=range.anchorOffset;
var endoffset=range.focusOffset;
var location=[startNode,endNode,startoffset,endoffset];
console.log(startNode);
return location;
I would greatly appreciate if some one can help me figure out how to store the range and to reload it. I am not looking to use rangy's library unless it is essentials. Also i am not sure if it will work in a chrome extension
When I try to pass text which spreads throughout a few block elements the window.find method dosent work:
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
</head>
<body>
<p>search me</p><b> I could be the answer</b>
</body>
</html>
JavaScript:
window.find("meI could be");
Or:
str = "me";
str+= "\n";
str+="I could be t";
window.find(str);
This happens when the <p> element is present between the search term.
The end result should be a GUI selection on the text in the page, I do not want to search if it exists.
I would like to know how to achieve this in Firefox(at least) and internet explorer.
Note: I can't change the dom (e.g. change to display inline).
Edit:
Here is something I tried after #Alexey Lebedev's comment, but it also finds the script (tag [...] text):
Can I make it more simple? (better)?
function nativeTreeWalker(startNode) {
var walker = document.createTreeWalker(
startNode,
NodeFilter.SHOW_TEXT,
null,
false
);
var node;
var textNodesV = [];
var textNodes = [];
node = walker.nextNode();
while(node ) {
if(node.nodeValue.trim()){
textNodes.push(node);
textNodesV.push(node.nodeValue);
//console.log(node.nodeValue);
}
node = walker.nextNode();
}
return [textNodes,textNodesV];
}
var result = nativeTreeWalker(document.body);
var textNodes = result[0];
var textNodesV = result[1];
var param = " Praragraph.Test 3 Praragr";
paramArr = param.split(/(?=[\S])(?!\s)(?=[\W])(?=[\S])/g);
//Fix split PARAM
for(i=0;i<paramArr.length-1;i++){
paramArr[i]= paramArr[i]+paramArr[i+1].charAt(0);
paramArr[i+1] = paramArr[i+1].substring(1,paramArr[i+1].length);
}
//Fix last element PARAM
if(paramArr[paramArr.length-1] === ""){
paramArr.splice(paramArr.length-1,1);
}
//console.log(paramArr);
var startNode,startOffset,sFound=false,
endNode,endOffset;
for(i=0;i<paramArr.length;i++){
for(j=0;j<textNodesV.length;j++){
//Fully Equal
var pos = textNodesV[j].indexOf(paramArr[i]);
if(pos != -1){
if(!sFound){
startNode = textNodes[j];
startOffset = pos;
sFound=true;
}else{
endNode = textNodes[j];
endOffset = pos+paramArr[i].length;
break;
}
}
}
}
console.log(startNode);
console.log(startOffset);
console.log(endNode);
console.log(endOffset);
var range = document.createRange();
range.setStart(startNode,startOffset);
range.setEnd(endNode,endOffset);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
Note: No jQuery (only Raw JS).
JS Bin demo: http://jsbin.com/aqiciv/1/
If you want this to work in IE < 9 you'll need to add MS-specific selection code (nightmare), or use Rangy.js (pretty heavy).
function visibleTextNodes() {
var walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_ALL,
function(node) {
if (node.nodeType == 3) {
return NodeFilter.FILTER_ACCEPT;
} else if (node.offsetWidth && node.offsetHeight && node.style.visibility != 'hidden') {
return NodeFilter.FILTER_SKIP;
} else {
return NodeFilter.FILTER_REJECT;
}
},
false
);
for (var nodes = []; walker.nextNode();) {
nodes.push(walker.currentNode);
}
return nodes;
}
// Find the first match, select and scroll to it.
// Case- and whitespace- insensitive.
// For better scrolling to selection see https://gist.github.com/3744577
function highlight(needle) {
needle = needle.replace(/\s/g, '').toLowerCase();
var textNodes = visibleTextNodes();
for (var i = 0, texts = []; i < textNodes.length; i++) {
texts.push(textNodes[i].nodeValue.replace(/\s/g, '').toLowerCase());
}
var matchStart = texts.join('').indexOf(needle);
if (matchStart < 0) {
return false;
}
var nodeAndOffsetAtPosition = function(position) {
for (var i = 0, consumed = 0; consumed + texts[i].length < position; i++) {
consumed += texts[i].length;
}
var whitespacePrefix = textNodes[i].nodeValue.match(/^\s*/)[0];
return [textNodes[i], position - consumed + whitespacePrefix.length];
};
var range = document.createRange();
range.setStart.apply(range, nodeAndOffsetAtPosition(matchStart));
range.setEnd.apply( range, nodeAndOffsetAtPosition(matchStart + needle.length));
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
range.startContainer.parentNode.scrollIntoView();
}
highlight('hello world');
Is there any way to get the image element after we insert the image using execCommand? for example
e.execCommand('insertimage',0,'ronaldo.png')
Don't use insertimage, use plain old insertHTML and give the element you are inserting an ID so that you can reference it later.
i.e.,
function insertHTML(img) {
var id = "rand" + Math.random();
var doc = document.getElementById("editor");
doc = doc.document ? doc.document : doc.contentWindow.document;
img = "<img src='" + img + "' id=" + id + ">";
if(document.all) {
var range = doc.selection.createRange();
range.pasteHTML(img);
range.collapse(false);
range.select();
} else {
doc.execCommand("insertHTML", false, img);
}
return doc.getElementById(id);
};
You can use the fact that browsers place the caret immediately after the inserted image and work back from there. The following requires DOM Range and selection support, which rules out IE <= 8, but if that's important then you can use a library such as my own Rangy to fill that gap.
Demo: http://jsfiddle.net/xJkuj/
Code:
function previousNode(node) {
var previous = node.previousSibling;
if (previous) {
node = previous;
while (node.hasChildNodes()) {
node = node.lastChild;
}
return node;
}
var parent = node.parentNode;
if (parent && parent.nodeType.hasChildNodes()) {
return parent;
}
return null;
}
document.execCommand("InsertImage", false, "http://placekitten.com/200/300");
// Get the current selection
var sel = window.getSelection();
if (sel.rangeCount > 0) {
var range = sel.getRangeAt(0);
var node = range.startContainer;
if (node.hasChildNodes() && range.startOffset > 0) {
node = node.childNodes[range.startOffset - 1];
}
// Walk backwards through the DOM until we find an image
while (node) {
if (node.nodeType == 1 && node.tagName.toLowerCase() == "img") {
alert("Found inserted image with src " + node.src);
break;
}
node = previousNode(node);
}
}
This is my way:
e.execCommand('insertimage', 0, URI) // image's URI
image=$('img[src="'+URI+'"]').not('.handled').addClass('.handled');
//.not('.handled').addClass('.handled') is needed if there are many images with the same URI
I'm writing a client-side application with Javascript, I'm using the following functions :
function creation() {
var userSelection;
if (window.getSelection) {
userSelection = window.getSelection();
}
else if (document.selection) { // should come last; Opera!
userSelection = document.selection.createRange();
}
var rangeObject = getRangeObject(userSelection);
var startOffset = rangeObject.startOffset;
var endOffset = rangeObject.endOffset;
var startCon = rangeObject.startContainer;
var endCon = rangeObject.endContainer;
var myRange = document.createRange();
myRange.setStart(startCon,rangeObject.startOffset);
myRange.setEnd(endCon, rangeObject.endOffset);
$('#result').text(myRange.toString());
}
function getRangeObject(selectionObject) {
if (selectionObject.getRangeAt) {
var ret = selectionObject.getRangeAt(0);
return ret;
}
else { // Safari!
var range = document.createRange();
range.setStart(selectionObject.anchorNode, selectionObject.anchorOffset);
range.setEnd(selectionObject.focusNode, selectionObject.focusOffset);
return range;
}
}
I need a way to know the character offset related to body element. I found a function with counts the character in an element :
function getCharacterOffsetWithin(range, node) {
var treeWalker = document.createTreeWalker(
node,
NodeFilter.SHOW_TEXT,
function (node) {
var nodeRange = document.createRange();
nodeRange.selectNode(node);
return nodeRange.compareBoundaryPoints(Range.END_TO_END, range) < 1 ?
NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
},
false
);
var charCount = 0;
while (treeWalker.nextNode()) {
charCount += treeWalker.currentNode.length;
}
if (range.startContainer.nodeType == 3) {
charCount += range.startOffset;
}
return charCount;
}
That looks like a function from one of my answers. I overcomplicated it a little; see this answer for a simpler function that works in IE < 9 as well: https://stackoverflow.com/a/4812022/96100. You can just pass in document.body as the node parameter. Also, please read the part in the linked answer about the shortcomings of this approach.
Here's a live demo: http://jsfiddle.net/PzQjA/
Is there a way to remove the id attribute of every node in a range or fragment?
Update: I finally found out that the bug I'm struggling with is based on a <[script]> being included in a range, and therefore unexpectedly cloned, when a chrome user does a ctrl+a. My goal would be to remove any instance of <[script]> from the range (or doc fragment), such that it is not replicated when cloned.
You may be able to use a TreeWalker, which works in pretty much all the browers that Range works in.
function actOnElementsInRange(range, func) {
function isContainedInRange(el, range) {
var elRange = range.cloneRange();
elRange.selectNode(el);
return range.compareBoundaryPoints(Range.START_TO_START, elRange) <= 0
&& range.compareBoundaryPoints(Range.END_TO_END, elRange) >= 0;
}
var rangeStartElement = range.startContainer;
if (rangeStartElement.nodeType == 3) {
rangeStartElement = rangeStartElement.parentNode;
}
var rangeEndElement = range.endContainer;
if (rangeEndElement.nodeType == 3) {
rangeEndElement = rangeEndElement.parentNode;
}
var isInRange = function(el) {
return (el === rangeStartElement || el === rangeEndElement ||
isContainedInRange(el, range))
? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
};
var container = range.commonAncestorContainer;
if (container.nodeType != 1) {
container = container.parentNode;
}
var walker = document.createTreeWalker(document,
NodeFilter.SHOW_ELEMENT, isInRange, false);
while (walker.nextNode()) {
func(walker.currentNode);
}
}
actOnElementsInRange(range, function(el) {
el.removeAttribute("id");
});
yes: http://api.jquery.com/removeAttr/