In Javascript, I'd like determine whether an element, say an A
element, exists inside a given range/textRange. The aim is to determine if the user's current selection contains a link. I am building a rich text editor control.
The range object has a commonAncestorContainer (W3C) or parentElement() (Microsoft) method which returns the closest common anscestor of all elements in the range. However, looking inside this element for A
elements won't work, since this common ancestor may also contain elements that aren't in the range, since the range can start or end part way through a parent.
How would you achieve this?
How about selection.containsNode?
https://developer.mozilla.org/en/DOM/Selection/containsNode
something like:
var selection = window.getSelection();
var range = selection.getRangeAt(0);
var result = $('a', range.commonAncestorContainer).filter(function() {
return selection.containsNode(this);
});
console.log(result);
I ended up going with a solution like this:
var findinselection = function(tagname, container) {
var
i, len, el,
rng = getrange(),
comprng,
selparent;
if (rng) {
selparent = rng.commonAncestorContainer || rng.parentElement();
// Look for an element *around* the selected range
for (el = selparent; el !== container; el = el.parentNode) {
if (el.tagName && el.tagName.toLowerCase() === tagname) {
return el;
}
}
// Look for an element *within* the selected range
if (!rng.collapsed && (rng.text === undefined || rng.text) &&
selparent.getElementsByTagName) {
el = selparent.getElementsByTagName(tagname);
comprng = document.createRange ?
document.createRange() : document.body.createTextRange();
for (i = 0, len = el.length; i < len; i++) {
// determine if element el[i] is within the range
if (document.createRange) { // w3c
comprng.selectNodeContents(el[i]);
if (rng.compareBoundaryPoints(Range.END_TO_START, comprng) < 0 &&
rng.compareBoundaryPoints(Range.START_TO_END, comprng) > 0) {
return el[i];
}
}
else { // microsoft
comprng.moveToElementText(el[i]);
if (rng.compareEndPoints("StartToEnd", comprng) < 0 &&
rng.compareEndPoints("EndToStart", comprng) > 0) {
return el[i];
}
}
}
}
}
};
Where getrange() is another function of mine to get the current selection as a range object.
To use, call it like
var link = findselection('a', editor);
Where editor is the contenteditable element, or body in a designmode iframe.
This is a bit of a pain in the bum to do cross-browser. You could use my Rangy library, which is probably overkill for just this task but does make it more straightforward and works in all major browsers. The following code assumes only one Range is selected:
var sel = rangy.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0);
var links = range.getNodes([1], function(node) {
return node.tagName.toLowerCase() == "a" && range.containsNode(node);
});
}
I'm using this code that works with IE / Chrome / FF: (I'm using it to select rows <tr> in a table)
// yourLink is the DOM element you want to check
var selection = window.getSelection().getRangeAt(0)
var node = document.createRange()
node.selectNode(yourLink)
var s2s = selection.compareBoundaryPoints(Range.START_TO_END, node)
var s2e = selection.compareBoundaryPoints(Range.START_TO_START, node)
var e2s = selection.compareBoundaryPoints(Range.END_TO_START, node)
var e2e = selection.compareBoundaryPoints(Range.END_TO_END, node)
if ((s2s != s2e) || (e2s != e2e) || (s2s!=e2e))
console.log("your node is inside selection")