javascriptdomselectionrangetextrange

How to know if there is a link element within the selection


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?


Solution

  • 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.