jQuery: как выбрать отсюда до следующего H2?

Я создаю очень простую страницу часто задаваемых вопросов с помощью jQuery. Вот так:

<h2>What happens when you click on this question?</h2>
<p>This answer will appear!</p>

Все это находится внутри очень определенного элемента div, поэтому я выберу заголовок с помощью $('#faq h2'). Просто, верно? Нажмите на H2 и используйте this.next(), чтобы появился следующий абзац.

(Оговорка с этой страницей заключается в том, что ее будет поддерживать непрограммист, поэтому я не использую классы: нет никакой гарантии, что любые новые записи будут содержать правильные классы.)

Так! Проблема:

<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>

Так как же, не добавляя div, классов и прочего, я могу сделать так, чтобы моя подпрограмма this.next() выбирала все между вопросом, на который был нажат, и следующим вопросом (заголовок H2)?


person Eileen    schedule 14.05.2009    source источник
comment
так что вы хотите выбрать все p под вторым h2 без селектора класса?   -  person TStamper    schedule 14.05.2009
comment
+1 за интересный вопрос и за то, что не поддались искушению везде добавлять классы и элементы div.   -  person Dominic Rodger    schedule 14.05.2009
comment
В идеале я бы хотел выбрать все между H2 #1 и H2 #2. Насколько я знаю, он будет вставлять UL и все такое.   -  person Eileen    schedule 14.05.2009
comment
Одна вещь, которая пришла мне в голову, заключается в том, что вам нужно быть осторожным с последним h2, у которого нет следующего h2.   -  person Dominic Rodger    schedule 14.05.2009
comment
проверьте мой ответ здесь stackoverflow.com/a/43836549/4251431   -  person Basheer AL-MOMANI    schedule 07.05.2017


Ответы (9)


Интересная проблема. Сначала позвольте мне сказать, что я думаю, что лучшая стратегия - заключить весь ответ в div, и тогда проблема становится тривиальной для решения:

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

с участием:

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

Но, как говорится, это решаемо и без этого.

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

Не в высшей степени элегантно, но это сработает.

Примечание. Предполагается, что вопросы являются родственными. Если их нет, все становится намного сложнее.

person cletus    schedule 14.05.2009
comment
Я не согласен с вами в части DIV. Не более ли элегантно обернуть каждую пару вопрос/ответ в DIV, а не только ответы? При этом, это верный ответ на вопрос Эйлин! - person Mathias Bynens; 14.05.2009
comment
Обертывание всего вопроса/ответа в div является разумным вариантом, но (имхо), когда вы показываете/скрываете или выделяете ответ, это то, что вы должны обернуть. Переключение div более эффективно, чем переключение всех элементов в нем, за исключением вопроса. - person cletus; 14.05.2009
comment
По моему скромному мнению, вы не должны учитывать поведение при написании HTML. В этом весь смысл ненавязчивого JavaScript: просто напишите HTML, который вам нужен, а затем напишите JavaScript, который делает с ним то, что вы хотите. Вот почему я думаю, что семантически более желательно обернуть каждую пару вопрос/ответ. - person Mathias Bynens; 14.05.2009
comment
Это справедливое замечание, но также произвольное и субъективное. Для меня ответ является самостоятельным семантически значимым понятием, а не понятиями Вопрос/Ответ, Вопрос и Вопрос/Ответ минус Вопрос. - person cletus; 14.05.2009
comment
+1 Туда я и направлялся, но как обычно немного красивее и намного быстрее. - person cgp; 14.05.2009
comment
ИДЕАЛЬНО! Это работает именно так, как я надеялся; Спасибо! - person Eileen; 14.05.2009
comment
Мой ответ теперь включает функцию jQuery, которая получает следующие элементы до тех пор, пока не найдет совпадение, а затем может использоваться для цепочки. Не уверен, что реализация функции jQuery настолько чиста, насколько это возможно.... - person cgp; 14.05.2009
comment
Возможно, вы захотите обновить свой ответ, так как jQuery теперь поддерживает это напрямую с помощью nextUntil с версии 1.4. stackoverflow.com/questions/863356/ - person gradbot; 28.05.2010
comment
Не могли бы вы использовать цикл $(this).nextUntil("h2") вместо цикла .each()? - person nnnnnn; 27.10.2013

Я понимаю, что это старый вопрос, но в jQuery 1.4 теперь есть nextUntil. Итак, что-то вроде этого теперь должно работать:

$('h2').click(function(){
    $(this).nextUntil('h2').show();
})
person John McCollum    schedule 06.02.2010
comment
Еще лучше, если вы измените .show() на .toggle(), таким образом вы получите как скрытие, так и отображение функций. :) - person Timothy Aaron; 22.02.2012

не лучше ли использовать список DL в стиле css?

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

А затем легкий выбор, используя:

$('+ dd', this); 

это текущий выбор dt.

Или просто оберните каждый ответ в div, так как это имеет смысл и семантически. Однако я думаю, что список DL имеет гораздо больше смысла семантически.

person Dmitri Farkov    schedule 14.05.2009
comment
Я абсолютно согласен. На мой взгляд, это одно из лучших применений списка определений. Вопрос — термин, ответ — определение. +1! (Я бы дал +2, если бы мог :P) - person Erik van Brakel; 14.05.2009
comment
Блестящее использование тега, но я сомневаюсь, что редактор, которым люди используют его контент, сделает это. +1 независимо, даже если это не очень помогает ОП - person Samantha Branham; 14.05.2009
comment
О, это хорошая идея, и если бы я делал это с нуля (вместо того, чтобы пытаться обойти устаревший код), это было бы отличным решением. FWIW FCKEditor довольно легко настроить, а добавление кнопок с пользовательским кодом (например, DL или даже с пользовательскими классами span/divs/и т. д.) довольно просто. - person Eileen; 14.05.2009

Конечно! Просто сделайте цикл while.

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

Это предполагает, что у вас есть только теги p после вашего h2. Вы можете добавить больше исключений в цикл while, если хотите добавить что-то вроде img.

Или, если вам все равно, что находится между тегами H2, вы можете проверить, не равно ли H2.

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

Это скроет все после H2, по которому щелкнули, пока либо не будет найден следующий H2, либо вы не подниметесь на уровень в доме.

person gradbot    schedule 14.05.2009

Возможно, это на самом деле не отвечает на ваш вопрос, но вы можете обернуть каждый элемент часто задаваемых вопросов (т. е. каждую пару вопрос/ответ) в элемент DIV. Это имело бы смысл семантически, и не-программист, обслуживающий страницу, просто должен был бы скопировать полный DIV (классы не нужны).

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();
});
person Mathias Bynens    schedule 14.05.2009
comment
Проблема в том, что сопровождающий будет использовать WYSIWYG-редактор (FCKEditor), поэтому я не могу полагаться на то, что он сделает ЧТО-НИБУДЬ с кодом, кроме как сделать вопросы H2, а редактор сделает ответы p. - person Eileen; 14.05.2009
comment
FCKEditor — это онлайн-редактор WYSIWYG, поэтому я предполагаю, что вы создаете какую-то систему управления контентом. Почему бы просто не запрограммировать элементы DIV и H2? Затем сопровождающему нужно будет ввести только заголовок (который будет заключен в H2) и ответ (который будет заключен в абзацы или что-то еще). Я не могу себе представить ситуацию, когда вам нужен весь HTML. страница для редактирования через FCKEditor... - person Mathias Bynens; 14.05.2009
comment
Что ж, это одна из таких ситуаций — длинная история, но в основном существует очень длинная страница, которую никто не хочет повторно вводить как отдельные записи. - person Eileen; 14.05.2009
comment
@mathias: просто к вашему сведению, недавно я написал что-то похожее на это. В моем случае я использовал TinyMCE, но это была не CMS, которую я делал. - person cletus; 14.05.2009

На всякий случай есть еще люди, которым нужен ответ на это... Это также включает в себя строку h2 в группе.

Здесь, если образец html, для которого работает мое решение:

<h2> text1 </h2>
<p>asd</p>
<p>asd</p>
<p>asd</p>
<p>asd</p>
<h3>kjdf</h3>
<h2>asdk</h2>
<h3>kjdf</h3>
<p>asd</p>
<p>asd</p>
<p>asd</p>
<p>asd</p>
<h2>aqdsa</h2>

решение:

jQuery( function ( $ ) {
//defining groups for wrapping them in custom divs

    $('h2').each( function () {
        var grpForDiv= [];          
        var next = $(this).next();  
        grpForDiv.push(this);
        while ( next.length!==0 && next!== undefined && next[0].nodeName !== 'H2')
            {
            grpForDiv.push(next[0]);
            next = next.next();
            }       
        jQuery(grpForDiv).wrapAll('<div class="Group1" />');

    } );
} );
person Arshdeep    schedule 27.10.2013

Обратите внимание, что выбранный ответ является хорошим, но я собираюсь сделать что-то подобное, поэтому я работал над ним сверхурочно :)

Я привел это в пример.

Учитывая 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> 

Сделайте следующее, и вы получите ответ. Я думаю, что сейчас это самое элегантное из всех предложенных решений, но я бы очень ценил мнение других.

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(); 
  }) 
});
person cgp    schedule 14.05.2009
comment
Ваш метод примерно на 32,1% медленнее, чем метод Клетуса. Учитывая предоставленный вами HTML-код, я запустил как решения Клетуса, так и ваши решения, и решение Клетуса заняло около 2,28 мс за прогон из 10 попыток, а ваше - 3,36 мс за прогон из 10 попыток. Конечно... мы говорим здесь о разнице в 1 мс... но, если бы в каком-то странном случае были миллионы элементов, которые нужно было проанализировать, кажется, что решение Клетуса закончилось бы быстрее (опять же, ваше может быть более бегун на длинные дистанции, где он скорее спринтер). Доблестное усилие однако. - person KyleFarris; 14.05.2009
comment
Интересный. Мне, вероятно, нужно использовать внутреннюю функцию jQuery для фильтрации вместо использования n.filter.... - person cgp; 15.05.2009

Вы также можете сделать это без редактирования HTML. Это плагин, который я только что написал для этой конкретной цели:

(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);

Вы можете использовать его следующим образом:

 $('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
 });
person Pim Jager    schedule 14.05.2009
comment
Это интересно. Для чего нужна переменная rec? - person ; 28.05.2009
comment
ой, забудьте удалить это, это ничего не делает. - person Pim Jager; 28.05.2009

Я не думаю, что ваша стратегия решения проблемы верна, у вас было 6 ответов, которые частично решают вашу проблему.....

Но ваша самая большая проблема заключается в том, что вы говорите, что не можете доверять сопровождающим использовать div/spans/classes и т. д.

Я думаю, вам следует упростить для них этот процесс и научить их использовать ваш метод, иначе он НЕ БУДЕТ работать.

добавьте свою собственную простую разметку и интерпретируйте для них.

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

(jQuery не исправит ошибку пользователя)

person Mesh    schedule 14.05.2009
comment
Если вы читаете другой комментарий, его контент люди используют WYSIWYG. - person Samantha Branham; 14.05.2009
comment
Я упростил им процесс! Теперь они просто используют H2 и P, как и на любой другой странице всего сайта. И ВУАЛЯ! Вы правы в том, что мы никогда не сможем решить ошибку «пользователя», но я не думаю, что правильное решение — научить их какому-то придуманному коду, который работает только на этом сайте. - person Eileen; 14.05.2009
comment
Но ВАШЕ поведение будет работать ТОЛЬКО на одном сайте. Где вы внедрили js для onclick. Фактически один конкретный div на одном конкретном веб-сайте. Вы не учите их HTML... Таким образом, из вашего комментария правильное решение должно состоять в том, чтобы иметь div с классами. Но вы исключили эти ответы в своем вопросе.... а затем приняли ответ, который включал добавление div! - person Mesh; 14.05.2009
comment
Я не понимаю, почему использование WYSIWYG, FCKeditor в этом случае гарантирует, что ваши пользователи будут использовать только теги H2 и P ....... - person Mesh; 14.05.2009