collapse fieldset when legend element clicked
I have a jQuery function which toggles the visibility of the contents of a fieldset when its legend is clicked, leaving just the fieldset border (if there is one) and the legend showing:
$('legend.togvis').click(function() {
$(this).siblings().toggle();
return false;
});
It works great unless the fieldset contains text nodes.
<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>
In an effort to toggle the text nodes too I tried this:
$('legend.togvis').click(function() {
$(this).parent().contents().not('legend').toggle();
return false;
});
which works the same as the first function. And this:
$('legend.togvis').click(function() {
$(this).parent().contents(":not(legend)").toggle();
return false;
});
which throws the error
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
Any thoughts on how to get the entire contents of a fieldset (minus the legend) to toggle when the legend is clicked?
ETA Solution, with man开发者_如何学运维y thanks to 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();
});
You simply cannot make text disappear in HTML, JavaScript or CSS. It needs to be contained in a HTML element that will have display:none;
applied or something similar.
The following code will wrap everything into a div, and move your legend to the same level as the div, and make the div show/hide when you click on the legend.
$("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();
}
});
- Code: http://jsbin.com/eleva3/edit
- Preview: http://jsbin.com/eleva3
Since text nodes are not elements, there is no way to toggle them.
As a last resort, if you cannot wrap them with an element, you can remove everything except the legend and add the elements and the text nodes back later:
$(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;
});
});
You can test that solution here.
Glorious Pure HTML/CSS Solution
This can be done purely with HTML and CSS, now in the year 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 Version
While the Pure HTML/CSS Solution is clearly glorious, there's nothing wrong with using Javascript to program interactivity on a page. But I've noticed all other answers use JQuery, which is totally unnecessary in the year 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>
Use a div tag to separate the contents, something like:
<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>
Then you only need to toggle the div.
$('legend.togvis').click(function() {
$(this).closest("contents").toggle();
return false;
});
Update only in case you cannot edit the HTML you can do the following but it will only work if all texts are within at least one tag:
$('legend.togvis').click(function() {
$(this).parents("fieldset").find("*:not('legend')").toggle();
return false;
});
Online demo here: http://jsfiddle.net/yv6zB/1/
May be you simply enclose your text into the sibilig tag:
<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>
I liked the accepted answer, but did not find it robust enough for my purposes. Just checking the length of the number of child elements does not support many scenarios. As such, I would consider using an implementation similar to:
$("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();
}
});
精彩评论