Skip to main content

jQuery: How to select "from here until the next H2”?



I'm setting up a very straightforward FAQ page with jQuery. Like so:







<h2>What happens when you click on this question?</h2>

<p>This answer will appear!</p>







This is all inside a very specific div, so I'll be selecting the header with $('#faq h2') . Simple, right? Click on the H2, and use this.next() to make the next paragraph show up.





(The caveat with this page is that a non-programmer will be maintaining it, which is why I'm not using classes: there's no guarantee that any new entries would have the right classes in them.)





So! The problem:







<h2>What happens when you click on the next question?</h2>

<p>That is an interesting conundrum.</p>

<p>Because the maintainer is kind of long-winded</p>

<p>and many answers will span a few paragraphs.</p>







So how, without adding in div s and classes and whatnot, can I have my this.next() routine select everything between the question-that-was-clicked-on and the next question (H2 header)?



Source: Tips4all

Comments

  1. Interesting problem. First let me say that I think the best strategy is to enclose the whole answer in a div and then the problem becomes trivial to solve:

    <h2>Question here</h2>
    <div>
    <p>Answer here</p>
    </div>
    </h2>Next Question</h2>
    ...


    with:

    $(function() {
    $("h2").click(function() {
    $(this).next().toggleClass("highlighted");
    });
    });


    But that being said, it is solvable without that.

    $(function() {
    $("h2").click(function() {
    $(this).nextAll().each(function() {
    if (this.tagName == 'H2') {
    return false; // stop execution
    }
    $(this).toggleClass("highlighted");
    });
    });
    });


    Not supremely elegant but it'll work.

    Note: This assumes the questions are siblings. If they are not it gets much more complicated.

    ReplyDelete
  2. I realise that this is an old question, but jQuery 1.4 now has nextUntil. So something like this should now work:

    $('h2').click(function(){
    $(this).nextUntil('h2').show();
    })

    ReplyDelete
  3. wouldn't it make more sense to use a css styled DL list?

    <dl class="faq">
    <dt>Question?</dt>
    <dd>
    <p>Answer</p>
    <p>Answer</p>
    <p>Answer</p>
    </dd>
    </dl>


    And then easy selection using:

    $('+ dd', this);


    this being the current dt selection.

    Or just wrap each answer in a div, since it makes sense semantically too. However I think a DL list makes a lot more sense semantically.

    ReplyDelete
  4. Sure! Just make a while loop.

    $('h2').click(function() {
    var next = $(this).next();
    while (next.length != 0 && next[0].nodeName == 'P')
    {
    next.toggle();
    next = next.next();
    }
    });


    This assumes that you only have p tags after your h2. You can add more exceptions to the while loop if you want to add something like img.

    Or if you don't care what's between the H2 tags you can check for not equal to H2.

    $('h2').click(function() {
    var next = $(this).next();
    while (next.length != 0 && next[0].nodeName != 'H2')
    {
    next.toggle();
    next = next.next();
    }
    });


    This will hide everything after the H2 that is clicked on until either the next H2 is found or you go up a level in the dom.

    ReplyDelete
  5. Maybe this isn't really answering your question, but you could wrap every FAQ item (i.e. every question/answer pair) in a DIV element. This would make sense semantically, and the non-programmer maintaining the page would simply have to copy a full DIV (no need for classes).

    HTML:

    <div id="faq">
    <!-- start FAQ item -->
    <div>
    <h2>Question goes here</h2>
    <p>Answer goes here.</p>
    <p>And here.</p>
    <ul>
    <li>Really, use any HTML element you want here.</li>
    <li>It will work.</li>
    </ul>
    </div>
    <!-- end FAQ item -->
    <!-- start FAQ item -->
    <div>
    <h2>Second question goes here</h2>
    <p>Answer to question two.</p>
    </div>
    <!-- end FAQ item -->
    </div>


    JavaScript (jQuery):

    $('#faq div h2').click(function() {
    $(this).parent().find(':not(h2)').show();
    });

    ReplyDelete
  6. Note that the answer selected is a good one, but I'm looking into doing something similar so I worked overtime on it :)

    I've put this into an example.

    Given HTML:

    <h2>Question 1</h2>
    <p>Answer 1</p>
    <p>Answer 1</p>
    <p>Answer 1</p>
    <h2>Question 2</h2>
    <p>Answer 2</p>
    <p>Answer 2</p>
    <p>Answer 2</p>
    <h2>Question 3</h2>
    <p>Answer 3</p>
    <p>Answer 3</p>
    <p>Answer 3</p>


    Do the following and you'll get your answer. I think it's now the most elegant of all the solutions given, but I would value others input on this quite a bit.

    jQuery.fn.nextUntilMatch = function(selector) {
    var result = [];
    for(var n=this.next();(n.length && (n.filter(selector).length == 0));n=n.next())
    result.push(n[0]);
    return $(result);
    }

    $(function() {
    $('h2~p').hide();
    $('h2').click(function() {
    $(this).nextUntilMatch(':not(p)').toggle();
    })
    });

    ReplyDelete
  7. You can also do this without editing you HTML. This is a plugin I just wrote for this particular purpose:

    (function($){
    $.fn.nextUntill = function(expr){
    var helpFunction = function($obj, expr){
    var $siblings = $obj.nextAll();
    var end = $siblings.index( $siblings.filter(expr) );
    if(end===-1) return $([]);
    return $siblings.slice(0, end);
    }
    var retObject = new Array();
    this.each(function(){
    $.merge(retObject, helpFunction($(this), expr));
    });
    return $(retObject);
    }
    })(jQuery);


    You can use it like this:

    $('h2').click(function(){
    $(this).nextUntill('h2').show(); //this will show all P tags between the clicked h2 and the next h2 assuiming the p and h2 tags are siblings
    });

    ReplyDelete
  8. I dont think your strategy for solving the problem is correct you've had 6 answers that partly solve your problem.....

    But your biggest problem is that you say you cannot trust the maintainers to use divs/spans/classes etc.

    What I think you should do is simplify the process for them, and train them to use your method or it WON'T work.

    add some simple mark up of your own and interpret for them.

    [Q] What happens when you click this link?[/Q]
    [A] This marvellous answer appears [/A]


    (jQuery will not fix the 'user' bug)

    ReplyDelete

Post a Comment

Popular posts from this blog

[韓日関係] 首相含む大幅な内閣改造の可能性…早ければ来月10日ごろ=韓国

div not scrolling properly with slimScroll plugin

I am using the slimScroll plugin for jQuery by Piotr Rochala Which is a great plugin for nice scrollbars on most browsers but I am stuck because I am using it for a chat box and whenever the user appends new text to the boxit does scroll using the .scrollTop() method however the plugin's scrollbar doesnt scroll with it and when the user wants to look though the chat history it will start scrolling from near the top. I have made a quick demo of my situation http://jsfiddle.net/DY9CT/2/ Does anyone know how to solve this problem?

Why does this javascript based printing cause Safari to refresh the page?

The page I am working on has a javascript function executed to print parts of the page. For some reason, printing in Safari, causes the window to somehow update. I say somehow, because it does not really refresh as in reload the page, but rather it starts the "rendering" of the page from start, i.e. scroll to top, flash animations start from 0, and so forth. The effect is reproduced by this fiddle: http://jsfiddle.net/fYmnB/ Clicking the print button and finishing or cancelling a print in Safari causes the screen to "go white" for a sec, which in my real website manifests itself as something "like" a reload. While running print button with, let's say, Firefox, just opens and closes the print dialogue without affecting the fiddle page in any way. Is there something with my way of calling the browsers print method that causes this, or how can it be explained - and preferably, avoided? P.S.: On my real site the same occurs with Chrome. In the ex