So I am trying to create a collapse (accordion) in Bootstrap 4 using Angular. I would love to be able to create almost the entire thing using an ngFor loop.
So far I have this:
<div class="row">
<div class="col-lg-6">
<div id="accordion" role="tablist" aria-multiselectable="true">
<div class="card" *ngFor="let environment of environments; let i = index;">
<div class="card-header" role="tab" id="headingi">
<h5 class="mb-0">
<a data-toggle="collapse" data-parent="#accordion" href="#collapsei" aria-expanded="false" aria-controls="collapsei">
Number: {{i}}
</a>
</h5>
</div>
<div id="collapseOne" class="collapse show" role="tabpanel" aria-labelledby="headingOne">
<div class="card-block">
<div *ngFor="let name of uniqueNames; let j = index" class="card infoBox">
<p>this is accordion {{i}}, section: {{j}}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
What I am trying to do is use "let i = index" to set the id and class names needed to make the accordion know what to do when certain links are clicked. So ideally the first accordion section would have class and id of collapse0/heading0 and the next would have id and class of collapse1/heading1 etc.
However, the previous code must not be working because all the accordions and their headers show up and do nothing when clicked on. It does generate the correct number, show all the right headers, and the bodies associated with them.
Any help? thanks!
You can do it this way also
id="collapse{{i}}" class="heading{{i}}" attr.aria-controls="heading{{i}}"
and the fact that aria- attributes are being handled another way in angular you have to prefix them with attr.. In your example should that be attr.aria-controls="heading{{i}}".
You just have to databind id:
<div class="card-header" role="tab" [id]="'heading'+i">
<!-- and -->
<div [id]="'collapse' + i" class="collapse show" role="tabpanel" [aria-labelledby]="'heading' + i">
And then you can access your index via i.
*Here is working code with bootstrap 5 accordion with ngFor Angular 12 (dynamic id):
<div class="accordion" id="accordionPanelsStayOpenExample">
<div class="accordion-item">
<h2 class="accordion-header" id="panelsStayOpen-headingOne">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#panelsStayOpen-collapseOne" aria-expanded="true" aria-controls="panelsStayOpen-collapseOne">
Installed Offer Version Details
</button>
</h2>
<div id="panelsStayOpen-collapseOne" class="accordion-collapse collapse show" aria-labelledby="panelsStayOpen-headingOne">
<div class="accordion-body">
<ng-container *ngFor="let lineItem of allLineItems">
<!-- <line-item-detail [lineItem]="lineItem"></line-item-detail>-->
<div class="accordion-item my-4">
<h2 class="accordion-header" id="heading{{lineItem.id}}">
<button class="accordion-button" type="button" data-bs-toggle="collapse" attr.data-bs-target="#collapse{{lineItem.id}}" aria-expanded="true" attr.aria-controls="collapse{{lineItem.id}}">
{{lineItem.productType}} : {{lineItem?.productOffering?.name}}
</button>
</h2>
<div id="collapse{{lineItem.id}}" class="accordion-collapse collapse show" attr.aria-labelledby="heading{{lineItem.id}}">
<div class="accordion-body">
your body text
</div>
</div>
</div>
</ng-container>
</div>
</div>
</div>
</div>
Even you can create sub-component of what is in ngFor like I commented the <line-item-detail></line-item-detail> tag.
Related
I am building a small application on VueJS 2.5.17 where I am having nested accordion data which I need to display through click events, I have a hierarchy of Root -> Roles -> Specialisation -> withRoles .... and so on. I am trying to get child elements on every click of their respective parents.
Following is my HTML Code:
<div class="panel-group" id="accordion1" v-for="items in accordianData">
<div class="panel my-panel-warning">
<div class="panel-heading">
<h4 class="panel-title">
<a style="color: #000; font-size: 14px" data-toggle="collapse" data-parent="#accordion1" :href="'#'+items.id">DOCUMENTED RELATIONSHIPS ({{count}})</a>
</h4>
</div>
<div :id="items.id" class="panel-collapse collapse">
<div class="my-panel-body">
<div class="my-panel-body">
<div class="panel-group" :id="items.id" >
<div class="panel my-panel-warning" v-for="child1 in roles">
<div class="panel-heading">
<a data-toggle="collapse" :data-parent="'#'+items.id" :id="'child'+child1.id" :href="'#role'+child1.id" #click="getSpecialisation(child1.id, child1.name)">{{child1.name+" (0)"}}</a>
</div>
<div class="my-panel-body">
<div :id="'role'+child1.id" class="panel-collapse collapse">
<div class="panel my-panel-warning" v-if="child1.id === child2.parent_id" v-for="child2 in specialisations[child1.name]">
<div class="panel-heading">
<a data-toggle="collapse" :data-parent="'#role'+child1.id" :id="'child2'+child2.id" :href="'#spec'+child2.id" #click="getWithRoles(child2.id)">{{child2.name+" (0)"}}</a>
</div>
<div class="my-panel-body">
<div :id="'spec'+child2.id" class="panel-collapse collapse">
<div class="my-panel-body">
.
.
.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
And calling following function in my methods:
getSpecialisation(id, name) {
axios.get('specialisations?company_id='+this.company_id+'&role_id='+id, {headers: getHeader()}).then(response => {
if(response.status === 200)
{
this.specialisations[name] = response.data.specialisations
}
})
},
But some how I am unable to display the data. Previously I tried v-for="child2 in specialisations" and in response I did this.specialisations = response.data.specialisations I was getting all similar child element for all parents I know this is wrong but just wanted to inform that I was getting data and my accordion was displaying child elements, but once I did according to above method getSpecialisation mentioned, I am unable to display the data.
I can see my data in vue console:
Suggest me a better way for this.
Thanks
Instead of this.specialisations[name] = response.data.specialisations try:
Vue.set(this.specialisations, name, response.data.specialisations)
That should trigger Vue's change-detection.
Ps. When you see lots of nesting, this might be good time to split a big component up into smaller components.
I'm trying to generate bootstrap collapse panels within Thymeleaf loop, like that:
<table>
<tr th:each="p, iterStat : ${completedList}">
<td style="padding: 0 15px 0 15px;">
<div class="panel panel-success">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion"
href="#collapseOne" th:text="${p.key}">p.k</a>
<span style="float: right;" class="glyphicon glyphicon-ok"></span>
</h4>
</div>
<div th:id="collapseOne" class="panel-collapse collapse in">
<div class="panel-body">
<span th:text="${p.value}"></span>
</div>
</div>
</div>
</td>
</tr>
</table>
But I have the same id and href for every element of the loop so the panels doesn't works.
Can I do something to change dynamically id and href?
You can use the status variable (iterStat) to create a unique id for every row:
<a data-toggle="collapse" data-parent="#accordion" th:href="'#collapse' + ${iterStat.index}" th:text="${p.key}">
and
<div th:id="'collapse' + ${iterStat.index}" class="panel-collapse collapse in">
I just ran into this same issue and what I did was
<div class="accordion" id="accordionExample"
th:each="todo : ${todos}">
<div class="accordion-item">
<h2 class="accordion-header" th:id="heading + ${todo.id}">
<button class="accordion-button" type="button"
data-bs-toggle="collapse"
th:attr="data-bs-target=|#col${todo.id}" aria-expanded="true"
aria-controls="collapseOne">Accordion Item #1</button>
</h2>
<div th:id="col + ${todo.id}"
class="accordion-collapse collapse show"
th:attr="aria-labelledby=|heading${todo.id}|"
data-bs-parent="#accordionExample">
<div class="accordion-body">
<strong>This is the first item's accordion body.</strong> It
is shown by default, until the collapse plugin adds the
appropriate classes that we use to style each element. These
classes control the overall appearance, as well as the showing
and hiding via CSS transitions. You can modify any of this
with custom CSS or overriding our default variables. It's also
worth noting that just about any HTML can go within the
<code>.accordion-body</code>
, though the transition does limit overflow.
</div>
</div>
</div>
</div>
instead of concatenating the id, just add it as a string with the th:attr, then the value should be put inside the | {dynamic value e.g id, uuid, primary key} |.
then concatenate with the + for the th:id which will automatically know its equivalent attribute as set earlier.
I'm having trouble adding dynamic id to the heading template. I tried including an id="{{group.title}}" but it doesn't work. Any help or suggestion would be great!
<div ng-controller="AccordionDemoCtrl">
<uib-accordion close-others="oneAtATime">
<div uib-accordion-group class="panel-default" template-url="group-template.html" ng-repeat="group in groups">
<uib-accordion-heading>
{{group.title}}
</uib-accordion-heading>
{{group.content}}
</div>
</uib-accordion>
</div>
<script type="text/ng-template" id="group-template.html">
<div id="{{group.title}}" class="panel-heading" ng-click="toggleOpen()" uib-accordion-transclude="heading">
<a href tabindex="0" class="accordion-toggle" >
<span uib-accordion-header ng-class="{'text-muted': isDisabled}">
<b>{{heading}}</b>
</span>
</a>
</div>
<div class="panel-collapse collapse" uib-collapse="!isOpen">
<div class="panel-body" style="text-align: left" ng-transclude></div>
</div>
</script>
Here is the plunkr: https://plnkr.co/edit/WbDY1S?p=preview
You can't use group in that context as you have a different scope inside template.
group will be available in a parent scope, so you could use {{$parent.group.title}}
https://plnkr.co/edit/f7fHKohu7StKB4wJLeTv?p=preview
In the lack of my javascript-skills I need some help for my very basic bootstrap problem.
That's the goal: four hidden paragraphs via collapse and a "toggle" button for each to make it visible or hidden.
That's the problem: the first collapse works fine, but the second, third and fourth glyphicon inside the buttons is changing too. And if I want to view the second, third and fourth collapsed paragraph after clicking the appropriate button it only shows the first paragraph. What happen? Is there only the one way to make this works, to include different IDs for each paragraph in the Javascript?
Live on Bootply: http://www.bootply.com/Jyf06v1aiK
<div class="col-xs-6">
<div class="collapse" id="collapseDiv">
<p class="">This is the FIRST collapsible content!</p>
</div>
<button data-toggle="collapse" data-target="#collapseDiv">
<span class="glyphicon glyphicon-large glyphicon-menu-down"></span>
</button>
</div>
<div class="col-xs-6">
<div class="collapse" id="collapseDiv">
<p class="">This is the SECOND collapsible content!</p>
</div>
<button data-toggle="collapse" data-target="#collapseDiv">
<span class="glyphicon glyphicon-large glyphicon-menu-down"></span>
</button>
</div>
<div class="col-xs-6">
<div class="collapse" id="collapseDiv">
<p class="">This is the THIRD collapsible content!</p>
</div>
<button data-toggle="collapse" data-target="#collapseDiv">
<span class="glyphicon glyphicon-large glyphicon-menu-down"></span>
</button>
</div>
<div class="col-xs-6 bg-success">
<div class="collapse" id="collapseDiv">
<p class="">This is the FOURTH collapsible content!</p>
</div>
<button data-toggle="collapse" data-target="#collapseDiv">
<span class="glyphicon glyphicon-large glyphicon-menu-down"></span>
</button>
</div>
Javascript:
$('#collapseDiv').on('shown.bs.collapse', function () {
$(".glyphicon").removeClass("glyphicon-menu-down").addClass("glyphicon-menu-up");
});
$('#collapseDiv').on('hidden.bs.collapse', function () {
$(".glyphicon").removeClass("glyphicon-menu-up").addClass("glyphicon-menu-down");
});
Take a look at my changes on your code here.
Your issue is not actually related to JS, but to concepts around the DOM.
First of all, it isn't a good idea to have the same id in multiple elements, the id attribute is supposed to be unique across the entire DOM.
Second, $('.glyphicon') refers to ALL the elements in the DOM with a class glyphicon not only the closest to your collapse div.
I'm including the code here but feel free to try it in the link above.
<div class="container">
<h1 class="">Bootstrap Collapse Buttons (WIP)</h1>
<div class="col-xs-6 bg-warning">
<div class="collapse customCollapse" id="collapseDiv">
<p class="">This is the FIRST collapsible content!</p>
</div>
<button data-toggle="collapse" data-target="#collapseDiv" class=""> <span class="glyphicon glyphicon-large glyphicon-menu-down"></span>
</button>
</div>
<div class="col-xs-6 bg-info">
<div class="collapse customCollapse" id="collapseDiv2">
<p class="">This is the SECOND collapsible content!</p>
</div>
<button data-toggle="collapse" data-target="#collapseDiv2" class=""> <span class="glyphicon glyphicon-large glyphicon-menu-down"></span>
</button>
</div>
<div class="col-xs-6 bg-danger">
<div class="collapse customCollapse" id="collapseDiv3">
<p class="">This is the FIRST collapsible content!</p>
</div>
<button data-toggle="collapse" data-target="#collapseDiv3" class=""> <span class="glyphicon glyphicon-large glyphicon-menu-down"></span>
</button>
</div>
<div class="col-xs-6 bg-success">
<div class="collapse customCollapse" id="collapseDiv4">
<p class="">This is the SECOND collapsible content!</p>
</div>
<button data-toggle="collapse" data-target="#collapseDiv4" class=""> <span class="glyphicon glyphicon-large glyphicon-menu-down"></span>
</button>
</div>
</div>
And the js
$('.customCollapse').on('shown.bs.collapse', function () {
$(this).parent().find(".glyphicon").removeClass("glyphicon-menu-down").addClass("glyphicon-menu-up");
});
$('.customCollapse').on('hidden.bs.collapse', function () {
$(this).parent().find(".glyphicon").removeClass("glyphicon-menu-up").addClass("glyphicon-menu-down");
});
EDIT
A little extra explanation: if you plan to share js logic across multiple DOM elements a good way to do it is using classes. That is why I changed your js and DOM to use .customCollapse instead of the collapseDiv id.
Note also that I changed the ids on each collapseDiv by adding a number to make it unique.
I'm using mustache templates to correctly generate bootstrap accordions. Now I need to pass a prefix to the top item id = 'accorElM' and thought I could do something like this.
<div class="accordion" id='{{#DataResult[0].prefixID}}_accorElM'>
IE, get the first item in the collection
Is this possible to do?
Code sample:
<div class="accordion" id='accorElM'>
{{#DataResult}}
<div class="accordion-group">
<div class="accordion-heading">
<a style="text-align: left; text-decoration: none" class="accordion-toggle btn" data-toggle="collapse" data-parent='#{{prefixID}}accorEl' href='#{{prefixID}}collapseEl_{{id}}'>
<i class="icon-globe"></i> {{tipo}}<i class="icon-chevron-down pull-right"></i>
</a>
</div>
<div id='collapseEl_{{id}}' class="accordion-body collapse">
<div class="accordion-inner">
<div class="row-fluid">
<div class="span9">
<address>
<br />
{{zona}}
<br />
{{cpostal}}
<br />
{{pais}}
</address>
</div>
<div class="span3">
<div class="pull-right">
.....
</div>
</div>
</div>
</div>
</div>
</div>
{{/DataResult}}
</div>
I'm guessing your data must be something like this:
{"DataResult":[
{"prefixID":"1","name":"first"},
{"prefixID":"2","name":"second"}
]
}
but in the mustache template file, I don't think you can index the object items. The approach that I would have taken would involve manipulating the Json object in javascript(or even in the backend when constructing the object) before rendering it.
In your case, if you need the prefixID of the first item in the DataResult. you could alter the object to make it look like this:
{"DataResult":[
{"prefixID":"1","name":"first"},
{"prefixID":"2","name":"second"}
],
"theIdIWant":"1"
}
and then in the template file:
<div class="accordion" id='{{theIdIWant}}_accorElM'>
{{#DataResult}}
<div class="accordion-group">
///////
</div>
{{/DataResult}}
</div>
Hope this helps.