jQuery wrapping text and elements between
tag

2020-07-11 09:59发布

问题:

I need to wrap everything, including free text, that is between two <hr> elements.

Given this source:

<hr class=begin>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  <a href=mauris.html>Mauris</a> id diam turpis, et faucibus nunc.
  <div><img src=foo.png /></div>
<hr class=end>

I need to wrap everything between the hr.begin and hr.end tags, like so:

<hr class=begin>
  <div class=content>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    <a href=mauris.html>Mauris</a> id diam turpis, et faucibus nunc.
    <div><img src=foo.png /></div>
  </div>
<hr class=end>

I cannot use a method like .nextUntil('hr.end') because this will not select the untagged text.

回答1:

Updated

I like this version better than my previous: http://jsfiddle.net/LbmCg/3/

(Partly inspired from the answer J-P gave.)

$('hr.begin').each(function(){
    var $set = $();
    var nxt = this.nextSibling;
    while(nxt) {
        if(!$(nxt).is('hr.end')) {
            $set.push(nxt);
            nxt = nxt.nextSibling;
        } else break;
    } 
   $set.wrapAll('<div class="content" />');
});

Original answer

Try this: http://jsfiddle.net/LbmCg/

If there's more than one set to wrap, there will some adjustment needed.

var foundBegin = false;
var foundEnd = false;

$('hr.begin').parent()
    .contents()
    .filter(function() {
        if($(this).is('hr.begin')) {
            foundBegin = true;
        }
        if($(this).is('hr.end')) {
            foundEnd = true;
        }
        return foundBegin && !foundEnd;
    })

    .wrapAll('<div class="content"/>');​

jQuery's .contents() returns all nodes including text nodes. So here we traverse to the .parent() of the hr.begin, get all of its nodes using .contents() then filter through them, tracking when we've found the beginning and the end, and only returning the elements between them.

Then we use .wrapAll() to wrap them with the div.content.

  • http://api.jquery.com/contents/
  • http://api.jquery.com/filter/
  • http://api.jquery.com/is/

EDIT: If there are multiple sets to wrap, try this: http://jsfiddle.net/LbmCg/1/

EDIT: Cleaned things up a little in both examples.



回答2:

​$('hr.begin ~ hr.end').each(function(){

    var contents = [], node, begin = $(this).prevAll('hr.begin')[0];

    if (node = this.previousSibling) do {
        if (node === begin) break;
        contents.unshift(node);
    } while (node = node.previousSibling);

    $(contents).wrapAll('<div class="content">');

});​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

This can handle multiple sets of the begin - contents - end sequence. It will look for all hr.end elements preceded by hr.begin elements and will, for each hr.end, find the preceding nodes between it and hr.begin, and then it'll wrap them all in a <div class=content>.



回答3:

theElement.contents() will recognize text nodes. Since jQuery.nextUntil() is actually a wrapper around jQuery.dir, which does

dir: function( elem, dir, until ) {

    var matched = [], cur = elem[dir];
    while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
        if ( cur.nodeType === 1 ) {
            matched.push( cur );
        }
        cur = cur[dir];
    }
    return matched;
},

where text nodes that get filtered out have a nodeType of 3, a custom jQuery.dir might help here.

I tried this solution with Patrick’s test data and it works:

jQuery.dirIncludingTextNodes = function( elem, dir, until ) {
    var matched = [], cur = elem[dir];
    while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
        if ( cur.nodeType === 1 ||cur.nodeType === 3 ) {
            matched.push( cur );
        }
        cur = cur[dir];
    }
    return matched;
};

jQuery.nextUntilIncludingTextNodes = function( elem, i, until ) {

    return jQuery.dirIncludingTextNodes( elem, "nextSibling", until );

};

$('hr.begin').nextUntilIncludingTextNodes("hr").wrap($("<div>").addClass("content"));​​​​​​​​​​​​​​​​​


回答4:

Had a similar problem, but I was using a CMS based on FCKEditor that didn't work so hot because of insertion of newline TextNodes from using the nextSibling property. I generally don't like to mix jQuery stuff with native JavaScript if I can help it either. I came up with this snippet that updates Patrick DW's solution that would probably be a bit more friendly for jQuery usage. Also doesn't require classes to determine next block.

$('hr').each(function(){
    var $set = $([]);
    var $nxt = $(this).next();
    while($nxt.length) {
        if(!$($nxt).is('hr')) {
            $set = $set.add($nxt);
            $nxt=$nxt.next();
        } else break;
    } 
    $set.wrapAll('<div class="content" />');
});


回答5:

If you HR and other tags are inside a div e.g.

<div id="mydiv">
  <hr class=begin>
  Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  <a href=mauris.html>Mauris</a> id diam turpis, et faucibus nunc.
  <div><img src=foo.png /></div>
  <hr class=end>
</div>

You can use jQuery.contents() e.g.

 $("#mydiv").contents().each(function(i,el){
  console.log(i,el)
  })

outputs:

0, [object Text]
1, [object HTMLHRElement]
2, [object Text]
3, http://jsbin.com/mauris.html
4, [object Text]
5, [object HTMLDivElement]
6, [object Text]
7, [object HTMLHRElement]
8, [object Text]

So you can easily club them in groups as you need.



回答6:

A extended version of user191688's code. This on is not only looking for the "until" in siblings but also up and down the tree.

(function($){

    $.fn.extend({
        nextUntilExtended: function(until) {
            var $set = $();
            var nxt = this.get(0).nextSibling;
            while(nxt) {
                if(!$(nxt).is(until)) {
                    if(nxt.nodeType != 3 && $(nxt).has(until)){
                        nxt = nxt.firstChild;
                    }else{
                        $set.push(nxt);
                        if(nxt.nextSibling){
                            nxt = nxt.nextSibling;
                        }else{
                            nxt = nxt.parentNode.nextSibling;
                        }
                    }
                } else break;
            }
            return($set);
        }
    });

})(jQuery);