JS: Получить массив всех выбранных узлов в содержимом div

Привет, я работаю с contentEditable некоторое время, и я думаю, что у меня есть довольно хорошая ручка для этого. Одна вещь, которая уклоняется от меня, это как получить массив ссылок на все узлы, которые частично или полностью находятся в пределах выбора пользователя. У кого-нибудь есть идея?

Вот с чего начать:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
function getSelectedNodes(){
    var sel = window.getSelection();
    try{var frag=sel.getRangeAt(0).cloneContents()}catch(e){return(false);}
    var tempspan = document.createElement("span");
    tempspan.appendChild(frag);

    var selnodes = Array() //<<- how do I fill this array??
    var output = ''
    for(i in selnodes){
        output += "A "+selnodes[i].tagName+" was found\n"
        //do something cool with each element here...
    }
    return(output)
}
</script>
</head>

<body contentEditable="true" onkeypress="return(keypress(event))">
<div>This <strong>div</strong> is <em>content</em> <span class='red'>editable</span> and has a couple of <em><strong>child nodes</strong></em> within it</div>
<br />
<br />
<a href="#" onmouseover="alert(getSelectedNodes())">hover here</a>
</body>
</html>

4 ответа

Решение

Вот версия, которая дает вам фактически выбранные и частично выбранные узлы, а не клоны. В качестве альтернативы вы можете использовать мою библиотеку Rangy, которая имеет getNodes() метод его Range объектов и работает в IE < 9.

function nextNode(node) {
    if (node.hasChildNodes()) {
        return node.firstChild;
    } else {
        while (node && !node.nextSibling) {
            node = node.parentNode;
        }
        if (!node) {
            return null;
        }
        return node.nextSibling;
    }
}

function getRangeSelectedNodes(range) {
    var node = range.startContainer;
    var endNode = range.endContainer;

    // Special case for a range that is contained within a single node
    if (node == endNode) {
        return [node];
    }

    // Iterate nodes until we hit the end container
    var rangeNodes = [];
    while (node && node != endNode) {
        rangeNodes.push( node = nextNode(node) );
    }

    // Add partially selected nodes at the start of the range
    node = range.startContainer;
    while (node && node != range.commonAncestorContainer) {
        rangeNodes.unshift(node);
        node = node.parentNode;
    }

    return rangeNodes;
}

function getSelectedNodes() {
    if (window.getSelection) {
        var sel = window.getSelection();
        if (!sel.isCollapsed) {
            return getRangeSelectedNodes(sel.getRangeAt(0));
        }
    }
    return [];
}

Ты так близко! Когда вы добавляете Document Fragment к временному span элемент, вы превратили их в управляемую группу, доступную через доверенный childNodes массив.

    var selnodes = tempspan.childNodes;

Кроме того, вы настраиваете себя на некоторые проблемы с этим for(i in selnodes) цикл, который будет возвращать элементы в массиве, плюс length собственность, а __proto__ свойство и любые другие свойства, которые может иметь объект.

Вы должны действительно использовать только те виды for зацикливается на свойства объекта в, а затем всегда с if (obj.hasOwnProperty[i]) отфильтровать свойства, унаследованные от прототипа.

При циклическом просмотре массивов используйте:

    for(var i=0,u=selnodes.length;i<u;i++)

Наконец, после загрузки этого массива вам нужно будет проверить каждый элемент, чтобы увидеть, является ли он узлом DOM или узлом Text, прежде чем вы сможете его обработать. Мы можем сделать это, проверив, поддерживает ли он tagName имущество.

    if (typeof selnodes[i].tagName !== 'undefined')

Вот и все:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
function getSelectedNodes(){
    var sel = window.getSelection();
    try{var frag=sel.getRangeAt(0).cloneContents()}catch(e){return(false);}
    var tempspan = document.createElement("span");
    tempspan.appendChild(frag);
    console.log(tempspan);
    window.selnodes = tempspan.childNodes;
    var output = ''
    for(var i=0, u=selnodes.length;i<u;i++){
        if (typeof selnodes[i].tagName !== 'undefined'){
          output += "A "+selnodes[i].tagName+" was found\n"
        }
        else output += "Some text was found: '"+selnodes[i].textContent+"'\n";
        //do something cool with each element here...
    }
    return(output)
}
</script>
</head>

<body contentEditable="true" onkeypress="return(keypress(event))">
<div>This <strong>div</strong> is <em>content</em> <span class='red'>editable</span> and has a couple of <em><strong>child nodes</strong></em> within it</div>
<br />
<br />
<a href="#" onmouseover="alert(getSelectedNodes())">hover here</a>
</body>
</html>

Ответ Тима Дауна близок, но он игнорирует startOffset и endOffset, что в некоторых случаях может привести к странному поведению. Его библиотека Rangy обрабатывает это правильно, но для тех, кто не хочет полной зависимости, вот только извлеченный соответствующий код.

      function isCharacterDataNode(node) {
    var t = node.nodeType;
    return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
}

function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
    var p, n = selfIsAncestor ? node : node.parentNode;
    while (n) {
        p = n.parentNode;
        if (p === ancestor) {
            return n;
        }
        n = p;
    }
    return null;
}

 function RangeIterator(range, clonePartiallySelectedTextNodes) {
        this.range = range;
        this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;


        if (!range.collapsed) {
            this.sc = range.startContainer;
            this.so = range.startOffset;
            this.ec = range.endContainer;
            this.eo = range.endOffset;
            var root = range.commonAncestorContainer;

            if (this.sc === this.ec && isCharacterDataNode(this.sc)) {
                this.isSingleCharacterDataNode = true;
                this._first = this._last = this._next = this.sc;
            } else {
                this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ?
                    this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true);
                this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ?
                    this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true);
            }
            console.log("RangeIterator first and last", this._first, this._last);
        }
    }

RangeIterator.prototype = {
    _current: null,
    _next: null,
    _first: null,
    _last: null,
    isSingleCharacterDataNode: false,

    reset: function() {
        this._current = null;
        this._next = this._first;
    },

    hasNext: function() {
        return !!this._next;
    },

    next: function() {
        // Move to next node
        var current = this._current = this._next;
        if (current) {
            this._next = (current !== this._last) ? current.nextSibling : null;
        }

        return current;
    },
};

Другой способ решить эту проблему — использоватьrange.intersectsNodeфункцию и просто пройдитесь по контейнеру LCA, найдя все, что пересекается, как упоминал Тим в другом ответе

              var selcRange = window.getSelection().getRangeAt(0)

        var containerElement = selcRange.commonAncestorContainer;
        if (containerElement.nodeType != 1) {
            containerElement = containerElement.parentNode;
        }

        var walk = document.createTreeWalker(containerElement, NodeFilter.SHOW_ALL, 
            { acceptNode: function(node) {

                  // Logic to determine whether to accept, reject or skip node
                  // In this case, only accept nodes that have content
                  // other than whitespace
                  return selcRange.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
                }
            }, false);
        var n = walk.nextNode();
        while (n) {
            s.push(n);
            n = walk.nextNode();
        }
        console.log(s)

Ниже код является примером для решения вашей проблемы, ниже код возвращает все выбранные узлы, которые находятся в диапазоне

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>payam jabbari</title>
<script src="http://code.jquery.com/jquery-2.0.2.min.js" type="text/javascript"></script>
<script type="text/javascript">

$(document).ready(function(){
    var startNode = $('p.first').contents().get(0);
var endNode = $('span.second').contents().get(0);
var range = document.createRange();
range.setStart(startNode, 0);
range.setEnd(endNode, 5);
var selection = document.getSelection();
selection.addRange(range);
// below code return all nodes in selection range. this code work in all browser
var nodes = range.cloneContents().querySelectorAll("*");
for(var i=0;i<nodes.length;i++)
{
   alert(nodes[i].innerHTML);
}
});
</script>
</head>

<body>
<div>

<p class="first">Even a week ago, the idea of a Russian military intervention in Ukraine seemed far-fetched if not totally alarmist. But the arrival of Russian troops in Crimea over the weekend has shown that he is not averse to reckless adventures, even ones that offer little gain. In the coming days and weeks</p>

<ol>
    <li>China says military will respond to provocations.</li>
    <li >This Man Has Served 20 <span class="second"> Years—and May Die—in </span> Prison for Marijuana.</li>
    <li>At White House, Israel's Netanyahu pushes back against Obama diplomacy.</li>
</ol>
</div>
</body>
</html>
Другие вопросы по тегам