свернуть набор полей при нажатии на элемент легенды

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

$('legend.togvis').click(function() {
    $(this).siblings().toggle();
    return false;
});

Он отлично работает, если набор полей не содержит текстовых узлов.

<fieldset><legend class='togvis'>Click Me</legend>
  <p>I toggle when the legend is clicked.</p>
  But I'm a recalcitrant text node and refuse to toggle.
</fieldset>

Пытаясь переключить текстовые узлы, я попробовал это:

$('legend.togvis').click(function() {
    $(this).parent().contents().not('legend').toggle();
    return false;
});

которая работает так же, как и первая функция. И это:

$('legend.togvis').click(function() {
    $(this).parent().contents(":not(legend)").toggle();
    return false;
});

который выдает ошибку

Message: 'style.display' is null or not an object
Line: 5557
Char: 3
Code: 0
URI: http://code.jquery.com/jquery-1.4.4.js

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

ETA Solution, большое спасибо Eibx

$('legend.togvis').click(function() { 
    $(this).parent().contents().filter(
        function() {  return this.nodeType == 3; }).wrap('<span></span>');//wrap any stray text nodes
    $(this).siblings().toggle(); 
}); 

person dnagirl    schedule 11.01.2011    source источник
comment
похоже, проблема в том, что текстовые узлы не имеют/не могут иметь стиль. Поскольку .toggle() работает, влияя на css узла, он не может влиять на текстовые узлы. Возможно, мне нужно сохранить содержимое набора полей в .data(), а затем удалить дочерние элементы?   -  person dnagirl    schedule 11.01.2011


Ответы (6)


Вы просто не можете заставить текст исчезнуть в HTML, JavaScript или CSS. Он должен содержаться в элементе HTML, к которому будет применено display:none; или что-то подобное.

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

$("fieldset legend").click(function() {
  if ($(this).parent().children().length == 2)
    $(this).parent().find("div").toggle();
  else
  {
    $(this).parent().wrapInner("<div>");
    $(this).appendTo($(this).parent().parent());
    $(this).parent().find("div").toggle();
  }
});
person はると    schedule 11.01.2011
comment
это действительно хорошее решение, потому что разметка добавляется той функцией, которая в ней нуждается. - person dnagirl; 11.01.2011
comment
Спасибо, но я бы порекомендовал вам переместить часть кода в $(document).ready(), так как это будет проверять элемент при каждом щелчке. Было бы разумно убедиться, что у всех легенд есть правильные потомки. Что-то вроде: jsbin.com/eleva3/2/edit - person はると; 11.01.2011
comment
К сожалению, это вызывает некоторые проблемы с макетом. Вы посылаете меня в действительно хорошем направлении, хотя. - person dnagirl; 11.01.2011
comment
К сожалению, это самое близкое, что я могу сделать, не возясь с вашим дизайном: jsbin.com/eleva3/3/ edit - Но там падает куча даунов, не рекомендую использовать. - person はると; 11.01.2011
comment
Я понял это (решение добавлено к вопросу сейчас). Отдаю вам должное за решение, так как без вас я бы его не получил. - person dnagirl; 11.01.2011
comment
Просто в качестве предупреждения для всех, кто использует эту функцию. В IE8 (и, возможно, ранее) рендеринг элемента DIV над элементом LEGEND приведет к тому, что он фактически появится над элементом легенды. Вам нужно будет настроить код для вашего использования, чтобы элемент div отображался под элементом легенды (невозможно вызывать внутреннюю обертку для всего содержимого) - person Sean Anderson; 26.10.2012
comment
Это довольно старо, но я подумал, что стоит упомянуть, что я нашел полезным использовать дочерние элементы вместо find в дополнение к использованию prependTo вместо appendTo. - person drs9222; 24.09.2014

Поскольку текстовые узлы не являются элементами, их невозможно переключить.

В крайнем случае, если вы не можете обернуть их элементом, вы можете удалить все, кроме легенды, и добавить элементы и текстовые узлы обратно позже:

$(document).ready(function() {
    $('legend.togvis').click(function() {
        var $this = $(this);
        var parent = $this.parent();
        var contents = parent.contents().not(this);
        if (contents.length > 0) {
            $this.data("contents", contents.remove());
        } else {
            $this.data("contents").appendTo(parent);
        }
        return false;
    });
});

Вы можете протестировать это решение здесь.

person Frédéric Hamidi    schedule 11.01.2011
comment
Это должно сработать для меня. Я хотел иметь возможность переключать набор полей с помощью элементов управления фильтрацией отчетов, поэтому у меня было несколько раскрывающихся списков, пара текстовых полей и сопровождающие их метки. - person Nick Harrison; 22.12.2011

Используйте тег div для разделения содержимого, например:

<fieldset>
  <legend class='togvis'>Click Me</legend>
  <div class="contents">
  <p>I toggle when the legend is clicked.</p>
  But I'm a recalcitrant text node and refuse to toggle.
  </div>
</fieldset>

Тогда вам нужно только переключить div.

$('legend.togvis').click(function() {
    $(this).closest("contents").toggle();
    return false;
});

Обновлять только в том случае, если вы не можете редактировать HTML. Вы можете сделать следующее, но это сработает только в том случае, если все тексты находятся хотя бы в одном теге:

$('legend.togvis').click(function() {
    $(this).parents("fieldset").find("*:not('legend')").toggle();
    return false;
});

Онлайн-демонстрация здесь: http://jsfiddle.net/yv6zB/1/

person amosrivera    schedule 11.01.2011
comment
он может не уметь это делать - person hunter; 11.01.2011
comment
ваш обновленный код не влияет на текстовые узлы. Он просто делает то, что делает моя исходная функция. Я рассматривал внутренний div как переключатель, но добавление несемантической разметки только для того, чтобы получить функциональность javascript, оскорбляет мои чувства;). - person dnagirl; 11.01.2011
comment
jejeje, понял, но тогда как семантически правильно, что текстовый узел плавает вокруг? не должно этого Но я непокорный текстовый узел и отказываюсь переключаться. идти внутри тегов абзаца? - person amosrivera; 11.01.2011

Великолепное решение на чистом HTML/CSS

Это можно сделать исключительно с помощью HTML и CSS, теперь в 2019 году.

#toggle + #content {
    display: none;
}

#toggle:checked + #content {
    display:block;
}

legend {
    width: 85%;
    border: 1px solid black;
    padding: 3px 6px;
    cursor: pointer;
}

legend label {
    width: 100%;
    display: inline-block;
    cursor: inherit;
}

legend label::after {
    content: "▼";
    float: right;
}
<body>
<form>
<fieldset>
    <legend><label for="toggle">Test</label></legend>
    <!-- This input has no name, so it's not submitted with the form -->
    <input type="checkbox" id="toggle" checked="" hidden=""/>
    <div id="content">
        <label>Some text field<input type="text" name="some-text-field"/></label><br/>
        <label>Some color input<input type="color" name="some-color-field"/></label>
    </div>
</fieldset>
</form>
</body>

Версия Javascript

Хотя решение Pure HTML/CSS явно великолепно, нет ничего плохого в использовании Javascript для программирования интерактивности на странице. Но я заметил, что во всех других ответах используется JQuery, который совершенно не нужен в 2019 году.

window.addEventListener('load', () => {
    const collapser = document.getElementById('collapser');
    const collapsable = document.getElementById('collapsable');

    function collapse(event) {
        if (event) {
            event.stopPropagation();
        }

        collapsable.hidden = !collapsable.hidden;
    }

    collapser.addEventListener('click', collapse);
});
legend {
    width: 85%;
    border: 1px solid black;
    padding: 3px 6px;
    cursor: pointer;
    display: inline-block;
}

legend::after {
    content: "▼";
    float: right;
}
<body>
<form>
<fieldset>
    <legend id="collapser">Test</legend>
    <div id="collapsable">
        <label>Some text field<input type="text" name="some-text-field"/></label><br/>
        <label>Some color input<input type="color" name="some-color-field"/></label>
    </div>
</fieldset>
</form>
</body>

person ocket8888    schedule 31.10.2019
comment
Мне не хотелось анимировать переход по стрелке, но это тоже вполне возможно как с Javascript, так и без него (хотя в данном случае без него сложнее). Вы также можете использовать <details>/<summary> для аналогичного эффекта, если все поддерживаемые вами браузеры совместимы с HTML5. - person ocket8888; 31.10.2019

Может быть, вы просто заключаете свой текст в тег sibilig:

<fieldset><legend class='togvis'>Click Me</legend>
  <div><p>I toggle when the legend is clicked.</p>
  But I'm a recalcitrant text node and refuse to toggle.</div>
</fieldset>
person Ivan Buttinoni    schedule 11.01.2011
comment
он может не уметь это делать - person hunter; 11.01.2011

Мне понравился принятый ответ, но он не нашел его достаточно надежным для моих целей. Просто проверка длины количества дочерних элементов не поддерживает многие сценарии. Таким образом, я бы рассмотрел возможность использования реализации, подобной:

$("fieldset legend").click(function () {
    var fieldset = $(this).parent();
    var isWrappedInDiv = $(fieldset.children()[0]).is('div');

    if (isWrappedInDiv) {
        fieldset.find("div").slideToggle();
    } else {
        fieldset.wrapInner("<div>");
        $(this).appendTo($(this).parent().parent());
        fieldset.find("div").slideToggle();
    }
});
person Sean Anderson    schedule 24.09.2012