问题
I am dynamically adding Collapsable elements to a page. Bootstrap uses the "data-target" attribute to specify which element the collapse toggle applies to.
From the docs:
The data-target attribute accepts a css selector
Is there a way to write a selector which specifies the next sibling of the parent element? All of the examples from the docs seem to use selections by ID.
Specifically the HTML looks like:
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse" data-target="#collapseOne">
Generated Title
</a>
</div>
<div id="collapseOne" class="accordion-body collapse in">
<div class="accordion-inner">
Generated Content... this is big and sometimes needs collapsing
</div>
</div>
</div>
I would like to write something like (pseudo code using jquery syntax illegally):
<a class="accordion-toggle" data-toggle="collapse" data-target="$(this).parent().next()">
But I am starting to suspect this may not be possible with CSS selectors.
Right now as a workaround I am generating a new ID (an incrementing number appended to a string) when I create the element.
Is there a nicer approach using a selector? Should I be using some post-creation javascript to set the data-target attribute? Is generating IDs for dynamic content the standard approach?
回答1:
While it is true that the selector in a data-target
attribute is a jQuery selector, the data-api specification for this plugin provides no means of referencing back to this
in the scope of execution (see lines 147-153 in bootstrap-collapse.js for its use).
However, I would like to offer another alternative approach, which is to extend the data-api with your own custom toggle specifier. Let's call it collapse-next.
JS (see update note)
$('body').on('click.collapse-next.data-api', '[data-toggle=collapse-next]', function (e) {
var $target = $(this).parent().next()
$target.data('collapse') ? $target.collapse('toggle') : $target.collapse()
})
HTML
<a class="accordion-toggle" data-toggle="collapse-next">
JSFiddle (updated)
Downside here is that it's a rather tightly coupled approach, since the JS presumes a specific structure to the markup.
Note about IE issues
As @slhck pointed out in his answer, IE9 and under apparently fail on the first click when using an earlier revision of my answer. The cause is actually not an IE issue at all, but rather a Bootstrap one. If one invokes .collapse('toggle')
on a target whose Carousel
object is uninitialized, the toggle()
method will be called twice - once during initialization and then again explicitly after initialization. This is definitely a Bootstrap bug and hopefully will get fixed. The only reason it doesn't appear as a problem in Chrome, FF, IE10, etc, is because they all support CSS transitions, and hence when the second call is made it short-circuits because the first one is still active. The updated workaround above merely avoids the double-call problem by checking for initialization first and handling it differently.
回答2:
@merv's solution didn't work for me in IE9 and below, since the collapsible state wasn't available unless you clicked at each item once. It did work fine in Firefox and Chrome though. So after two clicks, everything would work.
What I did was set a .collapse-next
class to the triggering elements, then force their ul
siblings to collapse with toggle
set to false
:
$(".collapse-next").closest('li').each(function(){
if ($(this).hasClass('active')) {
// pop up active menu items
$(this).children("ul").collapse('show');
} else {
// just make it collapsible but don't expand
$(this).children("ul").collapse({ toggle: false });
}
});
This is for actually toggling the menu state:
$('.collapse-next').click(function(e){
e.preventDefault();
$(this).parent().next().collapse('toggle');
});
It seems that using data-
attributes is a somewhat more modern and cleaner approach, but for old browsers working with classes and jQuery seems to do the job as well.
回答3:
Yes, you can do it easily! 😎
Just add the following code to your scripts and enjoy: 😃
$('body').on('click','[data-toggle="collapse"][data-mytarget^="$("]',function(){
eval($(this).data('mytarget')).collapse('toggle');
});
Now every where in your code you can set the target of collapse by a fully dynamic jQuery command inside data-mytarget
.
Now use it like it:
<a data-toggle="collapse" data-mytarget="$(this).parent().next()">Link</a>
or
<a data-toggle="collapse" data-mytarget="$('#TopMenuBar').closest('ul')">Link</a>
or
<a data-toggle="collapse" data-mytarget="[ EACH IDEAl JQUERY CODE OF YOU STARTED WITH $( ]">Link</a>
Demo:
$('body').on('click','[data-toggle="collapse"][data-mytarget^="$("]',function(){
eval($(this).data('mytarget')).collapse('toggle');
});
a{
padding:10px;
cursor:pointer;
margin:20px;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle btn btn-info" data-toggle="collapse" data-mytarget="$(this).parent().next()">Collapse It</a>
</div>
<div id="collapseOne" class="accordion-body collapse ">
<div class="accordion-inner">
Generated Content... this is big and sometimes needs collapsing
</div>
</div>
</div>
I used
data-mytarget
instead ofdata-target
. If you usedata-target
it works too but the jQuery library raise an error like this:Uncaught Error: Syntax error, unrecognized expression: $(this).parent().next()
.So I defined my different property with
data-mytarget
name.
回答4:
No javascript solution or it depends on bootstrap's js already in use, just exploiting the DOM structure-
See the data-target=""
...
A hint for easy way to reduce the bulky solutions-
<button
data-toggle="collapse"
data-target=".dropdown-toggle:hover + .more-menu"
type="button"
class="btn btn-primary dropdown-toggle"
>
Toggle Bagal Wala
</button>
<div class="collapse more-menu">I will be toggled</div>
This CSS selection structure will select the desired DOM .dropdown-toggle:hover + .more-menu
and there we can apply our desired CSS.
There are more ways to exploit what we have. :hover
or :active
or so many other ways.
回答5:
I think the best approach would be to do this iterate all accordion-toggle
elements and set their data-target
attribute dynamically via jquery and after that place the code of accordion mentioned in bootstrap.
Example :
$(function(){
$("a.accordion-toggle").each(function(index) {
$(this).attr("data-target", "#" + $(this).parent().next().attr("id"));
});
// accoridon code
});
Hope this will help
回答6:
TYPO3 FCE and Bootstrap Accordion
I am having some trouble with this issue too i am using it in TYPO3 for a customer who wants to be able to add an infinite number of elements to the accordion. So I created a Flexible Content Element and mapped the elements.
The idea with that data-toggle="collapse-next" did not work for me as expected as it did not close the open elements. I created a new javascript-function doing that please find the code here. Hopefully someone finds the stuff useful.
Javascript
$(document).on('click.collapse-next.data-api', '[data-toggle=collapse-next]', function (e) {
var $container = $(this).parents(".accordion");
var $opencontainers = $container.find(".in");
var $target = $(this).parent().next();
$target.data('collapse') ? $target.collapse('toggle') : $target.collapse();
$opencontainers.each(function() {$(this).collapse('toggle')});
})
HTML
<html>
<div class="accordion">
<div class="accordion-section">
<div class="accordion-group">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse-next">
Collapsible Group Item #1
</a>
</div>
<div class="accordion-body collapse">
<div class="accordion-inner">
Anim pariatur cliche...
</div>
</div>
</div>
</div>
</div>
</html>
回答7:
Here is an approach that avoids needing unique IDs and avoids a specific html structure.
This encloses each instance of the collapse trigger and target in a "section" of html. I like to use class selectors, especially for multiple instances. In this case, it avoids having to create artificial unique IDs.
Borrowing from @merv's excellent answer, I named the data-toggle collapse-section
similar to his collapse-next
, and added a data-section attribute.
Instead of parent().next(), this looks up for a closest() section name, then down for the given target name. They can be siblings or any other level.
(I have a naming convention using "id..." as a prefix for class names that are used as jquery selectors, to avoid mixing with styling.)
HTML
<div class="accordion-group idCollapseSection">
<div class="accordion-heading">
<a class="accordion-toggle" data-toggle="collapse-section"
data-section=".idCollapseSection" data-target=".idCollapseTarget">
Generated Title
</a>
</div>
<div>Any other html, at various depths.
<div class="accordion-body collapse in idCollapseTarget">
<div class="accordion-inner">
Generated Content... this is big and sometimes needs collapsing
</div>
</div>
</div>
</div>
JS
//------------------------------
// Bootstrap Collapse.
//------------------------------
// Extend collapse to contain trigger and target within an enclosing section.
$('body').on('click.collapse-section.data-api', '[data-toggle=collapse-section]', function (e) {
var thisTrigger = $(this);
var sectionSelector = thisTrigger.data("section");
var targetSelector = thisTrigger.data("target");
var target = thisTrigger.closest(sectionSelector).find(targetSelector);
target.data('bs.collapse') ? target.collapse('toggle') : target.collapse();
});
来源:https://stackoverflow.com/questions/12805825/can-you-specify-a-data-target-for-bootstrap-which-refers-to-a-sibling-dom-elem