如何在每次点击时获取iframe的下一个文本元素?

时间:2013-01-04 07:45:51

标签: javascript jquery

我有一个iframe,点击它的任何内容,就会在其上应用css类(比如说“selected”)。之后

单击下一个按钮时,它应该在包含文本的下一个元素上应用此类

并返回文字。遍历应该是基于文本节点的。虽然我确实尝试过做一些非常丑陋的事情,但我想必须有一些简单的解决方案。

这是我的代码:

$(function(){
$('#next').click(function(){
    var current_segment =$('#my_iframe').contents().find(".highlight");

    // if current segment has children
    if($(current_segment).children().not('.traversed_already').length > 0){
        $(current_segment).children().not('.traversed_already').first().addClass('highlight');
        $(current_segment).removeClass('highlight');

        // add class to stop repitative traversing
        $(current_segment).addClass('traversed_already');
    //                                                return false;

    // if has siblings and no children
    }else if($(current_segment).siblings().not('.traversed_already').length > 0 
        && $(current_segment).children().not('.traversed_already').length <= 0){
        $(current_segment).siblings().not('.traversed_already').first().addClass('highlight');
        $(current_segment).removeClass('highlight');

        // add class to stop repitative traversing
        $(current_segment).addClass('traversed_already'); 
    //                                                return false;

    // if no siblings and no children
    }else if($(current_segment).siblings().not('.traversed_already').length == 0 && 
        $(current_segment).children().not('.traversed_already').length == 0){

        // check the very first parent if traversed check its siblings
        var parent_segment = $(current_segment).parent().first();

        // if parent is already traversed already
        if($(parent_segment).hasClass('traversed_already')){

            // if parent is traversed but parent has sibling that is untraversed
            if($(parent_segment).siblings().not('.traversed_already').length > 0){
                $(parent_segment).siblings().not('.traversed_already').first().addClass('highlight');
                $(parent_segment).removeClass('highlight');

                // add class to stop repitative traversing
                $(parent_segment).addClass('traversed_already');
            //                                                        return false;
            // if no untraversed sibling then search for parent(s)
            }else{
                // Look for the parent in Dom tree which is not traversed
                $(parent_segment).parents().not('.traversed_already').first().addClass('highlight');
                $(parent_segment).removeClass('highlight');

                // add class to stop repitative traversing
                $(parent_segment).addClass('traversed_already');
            //                                                        return false;
            } // end of if traversed parent has siblings(untraversed).

        // if parent is not traversed
        } else {
            $(parent_segment).addClass('highlight');
            $(current_segment).removeClass('highlight');

            // add class to stop repitative traversing
            $(current_segment).addClass('traversed_already');
        } // end of if parent is already traversed or not

    //                                                return false;
    } // end of else if no siblings and no parents

本准则的问题是:

它只是通过首先看到孩子,然后是兄弟姐妹,然后是父母的兄弟姐妹来遍历下一个元素,但是当父母来到时,它会失败&gt;父母&gt;父母的兄弟姐妹。

注意:我相信这可以通过DOM遍历很容易完成,但我无法找到正确的解决方案。

5 个答案:

答案 0 :(得分:2)

这是一个最小的DOM遍历脚本。我不知道它是最好的,还是最小的,但我很确定它是有效的(只有浏览器测试,但看起来很稳固)。

getNextElement = function(element, goingUp) {
  if (element.firstChild && !goingUp){
    return element.firstChild;
  }
  else if (element.nextSibling){
    return element.nextSibling;
  }
  else if (element.parentNode) {
    return arguments.callee(element.parentNode, true);
  }
};

这实际上会击中文本节点以及其他节点。我想你实际上可以利用它。但是,如果您只想要元素,只需将firstChild更改为firstElementChild,将nextSibling更改为nextElementSibling。虽然IE8或更低版本不支持。 通过运行element = getNextElement(document.getElementsByTagName('body')[0])然后运行

来试用它
if (element.style){element.style.backgroundColor = 'yellow'};
element = getNextElement(element);

反复走路(并突出显示)DOM。

答案 1 :(得分:2)

最简单的解决方案是 DOM TreeWalker ,我很幸运,我花了太长时间才弄明白。

Here是指向它的链接。希望它能帮助遇到同样问题的人,而不是花费数小时或数天来帮助他们。

答案 2 :(得分:1)

我不确定iframe的标记有多复杂,所以我正在使用一些我自己很快创建的。

首先,单击一个元素以记下您的起始位置 - 该元素将突出显示 - 然后单击“下一步”按钮,代码将突出显示它找到的下一个元素。一旦突出显示所有元素,就不会再发生任何事情。取决于元素的嵌套程度取决于代码是否会看到元素并突出显示它。所有已发现的内容都将在控制台窗口中显示,并且会显示“已选择”类。

我知道您要求基于文本节点的解决方案,但我认为这可行。试试看,无论如何。

Iframe.html的:

<html>
<head>
<title>iFrame</title>
</head>
<body>
<ul>
<li><abbr title="Cascading Style Sheets">CSS</abbr> (an abbreviation; <code>abbr</code> markup used)</li>
<li><acronym title="radio detecting and ranging">radar</acronym> (an acronym; <code>acronym</code> markup used)</li>
<li><b>bolded</b> (<code>b</code> markup used - just bolding with unspecified semantics)</li>
<li><big>big thing</big> (<code>big</code> markup used)</li>
<li><cite>Origin of Species</cite> (a book title; <code>cite</code> markup used)</li>
<li><code>a[i] = b[i] + c[i);</code> (computer code; <code>code</code> markup used)</li>
<li>here we have some <del>deleted</del> text (<code>del</code> markup used)</li>
<li>an <dfn>octet</dfn> is an entity consisting of eight bits (<code>dfn</code> markup used for the term being defined)</li>
<li>this is <em>very</em> simple (<code>em</code> markup used for emphasizing a word)</li>
<li><i lang="la">Homo sapiens</i> (should appear in italics; <code>i</code> markup used)</li>
<li>here we have some <ins>inserted</ins> text (<code>ins</code> markup used)</li>
<li><q>Hello!</q> (<code>q</code> markup used for quotation)</li>
<li>He said: <q>She said <q>Hello!</q></q> (a quotation inside a quotation)</li>
<li><small>this is not that important</small> (<code>small</code> markup used)</li>
<li><strike>overstruck</strike> (<code>strike</code> markup used; note: <code>s</code> is a nonstandard synonym for <code>strike</code>)</li>
<li><strong>this is highlighted text</strong> (<code>strong</code> markup used)</li>
<li>In order to test how subscripts and superscripts (<code>sub</code> and <code>sup</code> markup) work inside running text, we need some dummy text around constructs like x<sub>1</sub> and H<sub>2</sub>O (where subscripts occur). So here is some fill so that you will (hopefully) see whether and how badly the subscripts and superscripts mess up vertical spacing between lines. Now superscripts: M<sup>lle</sup>, 1<sup>st</sup>, and then some mathematical notations: e<sup>x</sup>, sin<sup>2</sup><i>x</i>, and some nested superscripts (exponents) too: e<sup>x<sup>2</sup></sup> and f(x)<sup>g(x)<sup>a+b+c</sup></sup> (where 2 and a+b+c should appear as exponents of exponents).</li>
<li><u>underlined</u> text (<code>u</code> markup used)</li>
<li>the command <code>cat</code><var>filename</var> displays the file specified by the <var>filename</var> (<code>var</code> markup used to indicate a word as a variable).</li>
</ul>
</body>
</html>

demo.html:

<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script><script type="text/javascript">
$(document).ready(function(){
    $('#iframe').load(function(){
        $all = $('#iframe').contents().find('body *').filter(function(){
            var $this = $(this);
            return $this.children().length == 0 && $.trim($this.text()).length > 0;
        });         

        $('#next').on('click', highlightTextNode);
        $('#iframe').contents().find('body').on('click', highlightTextNode);
    });

    var highlightTextNode = function(event){
        event.stopPropagation();
        $el = $(event.target).children().length == 0 ? $(event.target) : $(event.target).find(':first-child').first();

        if(!$el.hasClass('selected') && $.trim($el.text()).length > 0 && $('#iframe').contents().find('body').find('.selected').length == 0){
            $el.addClass('selected').css({backgroundColor: 'yellow'});
            console.log($.trim($el.text()));
        } else {
            var found = false;
            var finished = false;

            $all.each(function(){
                finished = true;

                if(found && !$(this).hasClass('selected')){
                    found = false;
                    $(this).addClass('selected').css({backgroundColor: 'yellow'});
                    console.log($.trim($(this).text()));
                    finished = false;
                    return false;
                }

                if($(this).hasClass('selected'))
                    found = true;
            });

            if(finished){ //start at the beginning again
                $all.each(function(){                   
                    if(found && !$(this).hasClass('selected')){
                        found = false;
                        $(this).addClass('selected').css({backgroundColor: 'yellow'});
                        console.log($.trim($(this).text()));
                        return false;
                    }

                    if($(this).hasClass('selected'))
                        found = true;                   
                });
            }
        }
    }       
});
</script>
</head>
<body>
<input type="button" value="NEXT" id="next" />
<iframe id="iframe" src="iframe.html" width="100%" height="100%"></iframe>
</body>
</html>

答案 3 :(得分:0)

为什么不使用像

这样的while函数
$(function(){
$('#next').click(function(){
    var current_segment =$('#my_iframe').contents().find(".highlight");
    var doWhile = true; // variable that will determine that You have to go again trough the loop
    while (doWhile){
       ... // Your code
       // Every time where You put
          // add class to stop repitative traversing
          $(element).addClass('traversed_already');
      // give also
          doWhile = false; // to stop while function
      // of course return will take You out as well
    }

它应该有用。

答案 4 :(得分:0)

以下解决方案应符合您的目标。

注意:

  • 它使用了一些较新的浏览器/ JS功能,因此您可能需要将其移植到jQuery或类似版本以实现跨浏览器兼容性。
  • 它预先计算完整的文本节点列表,因此只有在iframe DOM是静态的情况下它才会起作用。
  • 它遍历所有文本节点,而不是所有包含文本的元素。也就是说,对于HTML <div id="a">1<div id="b">2</div>3</div>,它会选择1然后2然后选择3而不是a然后b。我相信这就是你所要求的,但并非100%明确。
  • 我没有测试过。

代码:

// First grab the iframe's document object.
var iframeDocument = document.querySelector('iframe').contentDocument;

// Then generate the list of all text nodes in the iframe.
var textNodes = [];
function findTextNodes(curElem){
    Array.prototype.slice.apply(curElem.childNodes).forEach(function(childNode) {
        switch (childNode.nodeType) {
            case Node.TEXT_NODE:
                textNodes.push(childNode);
            case Node.ELEMENT_NODE:
                findTextNodes(childNode);
        }
    }, this);
}
findTextNodes(iframeDocument.body);

// Helper function to move the text node selection.
function selectTextNodeAtIndex(index){
    if (index > 0) {
        // Replace the old .selected wrapper with the original text node.
        var oldWrapperElem = iframeDocument.querySelector('.selected');
        oldWrapperElem.parentNode.replaceChild(textNodes[index - 1], oldWrapperElem);
    }

    // Replace the current text node with a .selected wrapper.
    var wrapperElem = iframeDocument.createElement('span');
    wrapperElem.classList.add('selected');
    wrapperElem.textContent = textNodes[index].textContent;
    textNodes[index].parentNode.replaceChild(wrapperElem, textNodes[index]);
}

// Finally, select the first text node and set up the click handler.
var curIndex = 0;
selectTextNodeAtIndex(curIndex);
document.querySelector('#next').addEventListener('click', function () {
    curIndex++;
    selectTextNodeAtIndex(curIndex);
}, true);