Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery wrapping text and elements between <hr> tags

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.

like image 254
user191688 Avatar asked Jul 18 '10 15:07

user191688


2 Answers

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.

like image 101
user113716 Avatar answered Oct 07 '22 05:10

user113716


​$('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>.

like image 37
James Avatar answered Oct 07 '22 03:10

James