I have made some collapsing panels with bootstrap. Then added a method to "close" and remove the panels using angular. Only problem is that when I close the panel, the collapsing, open/close functionality still works. So the panel might open and then disappear, when it really should just disappear.
Here is a simplified version of the HTML
<div class="panel-group">
<div class="panel panel-info fadein fadeout" ng-repeat="p in panels">
<div class="panel-heading" data-toggle="collapse" data-target="#test_{{p}}" style="cursor:pointer">
<h4 class="panel-title">
open / close - {{p}}
<span class="glyphicon glyphicon-remove pull-right" ng-click="close(p)"></span>
</h4>
</div>
<div id="test_{{p}}" class="panel-collapse collapse" aria-expanded="false">
<div class="panel-body">
hello {{p}}
</div>
</div>
</div>
</div>
and angularJS
angular.module('app', ['ngAnimate'])
.controller('controller', function($scope){
var i = 4;
$scope.panels = ['panel_1', 'panel_2', 'panel_3']
$scope.add = function(){
console.log('add');
$scope.panels.push('panel_' + i++);
}
$scope.close = function (p) {
console.log('close ' + p);
for (var i = 0; i < $scope.panels.length; i++){
if ($scope.panels[i] == p){
$scope.panels.splice(i, 1);
}
}
}
});
Made a fiddle to illustrate:
https://jsfiddle.net/fiddlejan/82bmcyt0/
When you click on close, the cross on the right. The panel will "open", then disappear (the fadeOut animation does not seem to work here). I would like the close button to just remove the panel when you click close. Not "open" or "close" the panel.
So in case of clicking the close button, the panels collapsing, opening/close function should be disabled.
Add the event object to your ng-click definition:
<span class="glyphicon glyphicon-remove pull-right" ng-click="close(p, $event)"></span>
And use that object in your event handler:
$scope.close = function (p, e) {
console.log('close ' + p);
for (var i = 0; i < $scope.panels.length; i++){
if ($scope.panels[i] == p){
$scope.panels.splice(i, 1);
}
}
e.stopPropagation();
}
Related
When a panel is opened I need to have it scroll back to the ".panel-title" of the one that was just clicked. I know that I can create a directive to do this such as (got this from this site):
.directive( 'scrollTop', scrollTop );
function scrollTop() {
return {
restrict: 'A',
link: link
};
}
function link( $scope, element ) {
$scope.collapsing = false;
$scope.$watch( function() {
return $(element).find( '.panel-collapse' ).hasClass( 'collapsing' );
}, function( status ) {
if ( $scope.collapsing && !status ) {
if ( $(element).hasClass( 'panel-open' ) ) {
$( 'html,body' ).animate({
scrollTop: $(element).offset().top - 20
}, 500 );
}
}
$scope.collapsing = status;
} );
}
And in the HTML I was supposed to use:
<div uib-accordion-group is-open="status.openPanel" scroll-top></div>
But this does not seem to work. When you click the second panel it opens the first and second panel and it never even scrolls to the top when opening the third one.
I just need it to scroll back to the ".panel-title" of the panel that was clicked. I think it is pretty ridiculous that this seems to be so difficult to do when a lot of the time the panel content can get pretty long and I think most people would want to scroll to the top of the info when a panel is opened.
I have seen other posts on here but they don't seem to be dealing with AngularJS. I am using " ui-bootstrap-tpls-2.1.3 "
EDIT - Here is a Plunker to play with if you would like.
Any help is greatly appreciated.
this may be a quick and dirty method, but works flawless
angular.module('ui.bootstrap.demo', ['ngAnimate', 'ui.bootstrap'])
.controller('AccordionDemoCtrl', function($scope) {
$scope.oneAtATime = true;
$scope.groups = [{
title: 'Dynamic Group Header - 1',
content: 'Dynamic Group Body - 1'
}, {
title: 'Dynamic Group Header - 2',
content: 'Dynamic Group Body - 2 Dynamic Group Header - 2 Dynamic Group Header - 2 Dynamic Group Header - 2 Dynamic Group Header - 2 Dynamic Group Header - 2'
}];
$scope.items = ['Item 1', 'Item 2', 'Item 3'];
$scope.addItem = function() {
var newItemNo = $scope.items.length + 1;
$scope.items.push('Item ' + newItemNo);
};
$scope.status = {
isFirstOpen: true,
isFirstDisabled: false
};
//scrolling
var accordion = $('.accordion'), timeOut;
accordion.on('click', '.panel-heading', function(e) {
if(e.target.nodeName != 'SPAN') return;
var element = this;
clearTimeout(timeOut);
//since we dont know the exact offsetTop for dynamic content
//using timeout 350 which let angular complete its render
timeOut = setTimeout(function() {
accordion.animate({
scrollTop: element.offsetTop -2
}, 300);
}, 350);
});
});
.accordion {
max-height: 220px;
overflow: auto;
padding: 2px; 8px 0 0;
}
*:focus { outline: none !important; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.6/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.6/angular-animate.js"></script>
<script src="https://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.14.0.js"></script>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<div ng-app="ui.bootstrap.demo">
<div ng-controller="AccordionDemoCtrl">
<script type="text/ng-template" id="group-template.html">
<div class="panel {{panelClass || 'panel-default'}}">
<div class="panel-heading">
<h4 class="panel-title" style="color:#fa39c3">
<a href tabindex="0" class="accordion-toggle" ng-click="toggleOpen()" uib-accordion-transclude="heading"><span
ng-class="{'text-muted': isDisabled}">{{heading}}</span></a>
</h4>
</div>
<div class="panel-collapse collapse" uib-collapse="!isOpen">
<div class="panel-body" style="text-align: right" ng-transclude></div>
</div>
</div>
</script>
<div class="accordion">
<uib-accordion close-others="oneAtATime">
<uib-accordion-group heading="Static Header, initially expanded" is-open="status.isFirstOpen" is-disabled="status.isFirstDisabled">
This content is straight in the template.
</uib-accordion-group>
<uib-accordion-group heading="{{group.title}}" ng-repeat="group in groups">
{{group.content}}
</uib-accordion-group>
<uib-accordion-group heading="Dynamic Body Content">
<p>The body of the uib-accordion group grows to fit the contents</p>
<button type="button" class="btn btn-default btn-sm" ng-click="addItem()">Add Item</button>
<div ng-repeat="item in items">{{item}}</div>
</uib-accordion-group>
<uib-accordion-group heading="Custom template" template-url="group-template.html">
Hello
</uib-accordion-group>
<uib-accordion-group heading="Delete account" panel-class="panel-danger">
<p>Please, to delete your account, click the button below</p>
<p>Please, to delete your account, click the button below</p>
<p>Please, to delete your account, click the button below</p>
<button class="btn btn-danger">Delete</button>
</uib-accordion-group>
<uib-accordion-group is-open="status.open">
<uib-accordion-heading>
I can have markup, too! <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}"></i>
</uib-accordion-heading>
This is just some content to illustrate fancy headings.
</uib-accordion-group>
</uib-accordion>
</div>
</div>
</div>
I am using a snippet to load external divs to Bootstrap accordion items.
Initially, the divs were loaded on page load, but then I modified the code so that they would load on button click.
I have used two different snippets to make this happen, and they both result in a rather jumpy transition of the accordion items when expanding. However, this didn't happen when the divs where loaded on page load.
I have searched for a solution to this problem, I have tried many things that worked for other people (such as zero margin and padding or enclosing both the button and the panel-body inside a div), but none of them worked in this case.
If you have any suggestions, please tell me.
Thank you in advance
HTML
<div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="headingOne">
<h4 class="panel-title">
<a data-href="/path/to.html #firstdiv" class="ajax-link" data-ajaxtarget="#first" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Read more
</a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingOne">
<div class="panel-body" id="first"></div>
</div>
</div>
</div>
JS/JQuery
(function($){
$(document).ready(function(){
ajaxLink();
linkPreventDefault();
});
function ajaxLink(){
$("a.ajax-link").on('click', function(event){
var thisLink = $(this);
var targetUrl = thisLink.attr("data-href");
var target = thisLink.data('ajaxtarget');
console.log("loading via ajax: "+ targetUrl + ",in div: "+ target);
$(target).load( targetUrl, function( response, status, xhr ) {
if ( status == "error" ) {
var msg = "Sorry but there was an error: ";
console.error (msg + xhr.status + " " + xhr.statusText );
}else if(status="success"){
console.log("successfully loaded");
//OPTIONAL: unbind the links click event and bind a new one
noMoreAjax(thisLink);
}
});
event.preventDefault();
});
}
function linkPreventDefault(){
$('a.link-prevent').click(function(event){
console.log
event.preventDefault();
});
}
function noMoreAjax(item){
var linkItem = $(item);
console.log("No more Ajax for this link");
linkItem.removeClass('ajax-link');
linkItem.addClass('link-prevent');
linkItem.unbind('click');
$(linkItem).click(function(event){
console.log("Preventing after ajax");
event.preventDefault();
});
}
})(jQuery);
(OLD JS:
$(document).ready(function(){
$('#accordion').click(function(){
$('#first').load('/path/to.html #firstdiv');
});
})
)
You should expand accordion item once received response for the ajax call. So remove data-toggle="collapse" href="#collapseOne" attributes from your link and call $('#collapseOne').collapse() in callback function, like this:
$(target).load( targetUrl, function( response, status, xhr ) {
if ( status == "error" ) {
var msg = "Sorry but there was an error: ";
console.error (msg + xhr.status + " " + xhr.statusText );
}else if(status="success"){
$('#collapseOne').collapse();
console.log("successfully loaded");
//OPTIONAL: unbind the links click event and bind a new one
noMoreAjax(thisLink);
}
});
See a working sample here
I've used setTimeout() to fake ajax call, so you'll need to update that part, but other than that, everything is there.
I have modal dialog like this one
<div id="tenantDialog" class="modal fade" tabindex="-1" data-backdrop="static">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">
<i class="fa fa-users"></i>
<spring:message code="label.tenant.person.title"/>
</h4>
</div>
<div class="modal-body">
<%--We load here the persons table here to be reloaded in any moment--%>
<div id="personTableDiv">
<c:set var="preferredCollaborators" value="${preferredCollaborators}" scope="request"/>
<jsp:include page="personsTable.jsp"/>
</div>
</div>
<div class="modal-footer">
</div>
</div>
</div>
Then in the personsTable.js associate to the include page I have the logic to open a bootbox confirm. But my problem is, that this bootbox it´s showing under the modal dialog, so it´s not intractable.
Here my bootbox creation
bootbox.confirm(customText, function (confirmed) {
if (confirmed) {
var regularityDocumentsIds = [];
var i;
for (i = 0; i < selectedPersons.length; i += 1) {
regularityDocumentsIds.push($(selectedPersons[i]).attr("data-preferredcollaboratorid"));
}
removePersons(regularityDocumentsIds);
}
});
Any idea what can I do to show this bootbox over the modal dialog?
This is a confirmed issue: using bootbox.confirm() within a bootstrap modal.
You have a workaround. Add this in your CSS:
.bootbox.modal {z-index: 9999 !important;}
my humble (working in production) solution
You should edit bootbox, look for the callback of:
dialog.one("hidden.bs.modal", function (e) {
// ensure we don't accidentally intercept hidden events triggered
// by children of the current dialog. We shouldn't anymore now BS
// namespaces its events; but still worth doing
if (e.target === this) {
dialog.remove();
}
});
and change it to:
dialog.one("hidden.bs.modal", function (e) {
// ensure we don't accidentally intercept hidden events triggered
// by children of the current dialog. We shouldn't anymore now BS
// namespaces its events; but still worth doing
if (e.target === this) {
dialog.remove();
}
// inspired by : https://github.com/makeusabrew/bootbox/issues/356#issuecomment-159208242
// if there is another modal, stop the event from propgating to bootstrap.js
// bootstrap.js uses the same event to remove the "modal-open" class from the body, and it creates an unscrolable modals
if ($('.modal.in').css('display') == 'block') {
// do not notify bootstrap about this change!!
e.stopPropagation();
}
});
At http://www.dentalo.se/contact it is using a responsive menu for mobile devices.
When clicking on the menu button it collapses the menu but the same time reloads the page.
Can anyone tell my why and how I can fix it?
Thanks for the time you are taking to help me.
Edit
HTML
<div class="container">
<div class="navbar-header">
<!-- BEGIN RESPONSIVE MENU TOGGLER -->
<button type="button" class="navbar-toggle btn navbar-btn" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- END RESPONSIVE MENU TOGGLER -->
<!-- BEGIN LOGO (you can use logo image instead of text)-->
<a class="navbar-brand logo-v1" href="/">
<img src="/assets/img/logo_blue.png" id="logoimg" alt="">
</a>
<!-- END LOGO -->
</div>
<!-- BEGIN TOP NAVIGATION MENU -->
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li id="MainMenuHome">Start</li>
<li class="dropdown" id="MainMenuDentalo">
<a class="dropdown-toggle" data-toggle="dropdown" data-hover="dropdown" data-delay="0" data-close-others="false" href="#">
Dentalo
<i class="icon-angle-down"></i>
</a>
<ul class="dropdown-menu">
<li id="SubMenuAbout">Om Dentalo</li>
<li id="SubMenuJob">Lediga tjänster</li>
<li id="SubMenuConnect">Anslut</li>
</ul>
</li>
<li id="MainMenuLogIn">Logga in</li>
<li id="MainMenuContact">Kontakt</li>
<li class="menu-search">
<span class="sep"></span>
<i class="icon-search search-btn"></i>
<div class="search-box">
<form action="#">
<div class="input-group input-large">
<asp:TextBox runat="server" placeholder="Sök..." CssClass="form-control" ID="textBoxSearch"></asp:TextBox>
<span class="input-group-btn">
<asp:Button runat="server" CssClass="btn theme-btn" ID="ButtonSearch" OnClick="ButtonSearch_Click" Text="Sök" />
</span>
</div>
</form>
</div>
</li>
</ul>
</div>
<!-- BEGIN TOP NAVIGATION MENU -->
</div>
JavaScript
/*
* Project: Twitter Bootstrap Hover Dropdown
* Author: Cameron Spear
* Contributors: Mattia Larentis
*
* Dependencies?: Twitter Bootstrap's Dropdown plugin
*
* A simple plugin to enable twitter bootstrap dropdowns to active on hover and provide a nice user experience.
*
* No license, do what you want. I'd love credit or a shoutout, though.
*
* http://cameronspear.com/blog/twitter-bootstrap-dropdown-on-hover-plugin/
*/
;(function($, window, undefined) {
// outside the scope of the jQuery plugin to
// keep track of all dropdowns
var $allDropdowns = $();
// if instantlyCloseOthers is true, then it will instantly
// shut other nav items when a new one is hovered over
$.fn.dropdownHover = function(options) {
// the element we really care about
// is the dropdown-toggle's parent
$allDropdowns = $allDropdowns.add(this.parent());
return this.each(function() {
var $this = $(this),
$parent = $this.parent(),
defaults = {
delay: 500,
instantlyCloseOthers: true
},
data = {
delay: $(this).data('delay'),
instantlyCloseOthers: $(this).data('close-others')
},
settings = $.extend(true, {}, defaults, options, data),
timeout;
$parent.hover(function(event) {
// so a neighbor can't open the dropdown
if(!$parent.hasClass('open') && !$this.is(event.target)) {
return true;
}
if(shouldHover) {
if(settings.instantlyCloseOthers === true)
$allDropdowns.removeClass('open');
window.clearTimeout(timeout);
$parent.addClass('open');
}
}, function() {
if(shouldHover) {
timeout = window.setTimeout(function() {
$parent.removeClass('open');
}, settings.delay);
}
});
// this helps with button groups!
$this.hover(function() {
if(shouldHover) {
if(settings.instantlyCloseOthers === true)
$allDropdowns.removeClass('open');
window.clearTimeout(timeout);
$parent.addClass('open');
}
});
// handle submenus
$parent.find('.dropdown-submenu').each(function(){
var $this = $(this);
var subTimeout;
$this.hover(function() {
if(shouldHover) {
window.clearTimeout(subTimeout);
$this.children('.dropdown-menu').show();
// always close submenu siblings instantly
$this.siblings().children('.dropdown-menu').hide();
}
}, function() {
var $submenu = $this.children('.dropdown-menu');
if(shouldHover) {
subTimeout = window.setTimeout(function() {
$submenu.hide();
}, settings.delay);
} else {
// emulate Twitter Bootstrap's default behavior
$submenu.hide();
}
});
});
});
};
// helper variables to guess if they are using a mouse
var shouldHover = false,
mouse_info = {
hits: 0,
x: null,
y: null
};
$(document).ready(function() {
// apply dropdownHover to all elements with the data-hover="dropdown" attribute
$('[data-hover="dropdown"]').dropdownHover();
// if the mouse movements are "smooth" or there are more than 20, they probably have a real mouse
$(document).mousemove(function(e){
mouse_info.hits++;
if (mouse_info.hits > 20 || (Math.abs(e.pageX - mouse_info.x) + Math.abs(e.pageY - mouse_info.y)) < 4) {
$(this).unbind(e);
shouldHover = true;
}
else {
mouse_info.x = e.pageX;
mouse_info.y = e.pageY;
}
});
});
// for the submenu to close on delay, we need to override Bootstrap's CSS in this case
var css = '.dropdown-submenu:hover>.dropdown-menu{display:none}';
var style = document.createElement('style');
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
$('head')[0].appendChild(style);
})(jQuery, this);
It appears that your button element (navbar-btn) is submitting your form. Try adding type="button" to the button element.
Also in the click event handler you can also try to add e.preventDefault() to your click event handler.
I cant seem to find the JavaScript from the click event of your button so I can help much more. If this does not work, please edit your question to show the relevant HTML and JavaScript sections.
If I have this code:
<accordion-group heading="{{group.title}}" ng-repeat="group in groups">
{{group.content}}
</accordion-group>
Using AngularJS, angular-ui and Twitter Bootstrap, is it possible to make the accordion call some action when opened? I know I can't simply add ng-click, because that is already used after it's "compiled" to HTML for opening/collapsing of the group.
Accordion groups also allow for an accordion-heading directive instead of providing it as an attribute. You can use that and then wrap your header in another tag with an ng-click.
<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.open">
<accordion-heading>
<span ng-click="opened(group, $index)">{{group.content}}</span>
</accordion-heading>
</accordion-group>
Example: http://plnkr.co/edit/B3LC1X?p=preview
Here's a solution based on pkozlowski.opensource solution.
Instead of adding a $watch on each item of the collection, you can use a dynamically defined Property. Here, you can bind the IsOpened property of the group to the is-open attribute.
<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.IsOpened">
{{group.content}}
</accordion-group>
So, you can dynamically add the IsOpened property on each item of the collection in the controller :
$scope.groups.forEach(function(item) {
var isOpened = false;
Object.defineProperty(item, "IsOpened", {
get: function() {
return isOpened;
},
set: function(newValue) {
isOpened = newValue;
if (isOpened) {
console.log(item); // do something...
}
}
});
});
Using properties instead of watches is better for performances.
There is the is-open attribute on the accordion-group which points to a bindable expression. You could watch this expression and execute some logic when a given accordion group is open. Using this technique you would change your markup to:
<accordion-group ng-repeat="group in groups" heading="{{group.title}}" is-open="group.open">
{{group.content}}
</accordion-group>
so that you can, in the controller, prepare a desired watch expression:
$scope.$watch('groups[0].open', function(isOpen){
if (isOpen) {
console.log('First group was opened');
}
});
While the above works it might be a bit cumbersome to use in practice so if you feel like this could be improved open an issue in https://github.com/angular-ui/bootstrap
Here's a solution inspired by kjv's answer, which easily tracks which accordion element is open. I found difficult getting ng-click to work on the accordion heading, though surrounding the element in a <span> tag and adding the ng-click to that worked fine.
Another problem I encountered was, although the accordion elements were added to the page programmatically, the content was not. When I tried loading the content using Angular directives(ie. {{path}}) linked to a $scope variable I would be hit with undefined, hence the use of the bellow method which populates the accordion content using the ID div embedded within.
Controller:
//initialise the open state to false
$scope.routeDescriptors[index].openState == false
function opened(index)
{
//we need to track what state the accordion is in
if ($scope.routeDescriptors[index].openState == true){ //close an accordion
$scope.routeDescriptors[index].openState == false
} else { //open an accordion
//if the user clicks on another accordion element
//then the open element will be closed, so this will handle it
if (typeof $scope.previousAccordionIndex !== 'undefined') {
$scope.routeDescriptors[$scope.previousAccordionIndex].openState = false;
}
$scope.previousAccordionIndex = index;
$scope.routeDescriptors[index].openState = true;
}
function populateDiv(id)
{
for (var x = 0; x < $scope.routeDescriptors.length; x++)
{
$("#_x" + x).html($scope.routeDescriptors[x]);
}
}
HTML:
<div ng-hide="hideDescriptions" class="ng-hide" id="accordionrouteinfo" ng-click="populateDiv()">
<accordion>
<accordion-group ng-repeat="path in routeDescriptors track by $index">
<accordion-heading>
<span ng-click="opened($index)">route {{$index}}</span>
</accordion-heading>
<!-- Notice these divs are given an ID which corresponds to it's index-->
<div id="_x{{$index}}"></div>
</accordion-group>
</accordion>
</div>
I used an associative array to create a relationship between the opened state and the model object.
The HTML is:
<div ng-controller="CaseController as controller">
<accordion close-others="controller.model.closeOthers">
<accordion-group ng-repeat="topic in controller.model.topics track by topic.id" is-open="controller.model.opened[topic.id]">
<accordion-heading>
<h4 class="panel-title clearfix" ng-click="controller.expand(topic)">
<span class="pull-left">{{topic.title}}</span>
<span class="pull-right">Updated: {{topic.updatedDate}}</span>
</h4>
</accordion-heading>
<div class="panel-body">
<div class="btn-group margin-top-10">
<button type="button" class="btn btn-default" ng-click="controller.createComment(topic)">Add Comment<i class="fa fa-plus"></i></button>
</div>
<div class="btn-group margin-top-10">
<button type="button" class="btn btn-default" ng-click="controller.editTopic(topic)">Edit Topic<i class="fa fa-pencil-square-o"></i></button>
</div>
<h4>Topic Description</h4>
<p><strong>{{topic.description}}</strong></p>
<ul class="list-group">
<li class="list-group-item" ng-repeat="comment in topic.comments track by comment.id">
<h5>Comment by: {{comment.author}}<span class="pull-right">Updated: <span class="commentDate">{{comment.updatedDate}}</span> | <span class="commentTime">{{comment.updatedTime}}</span></span></h5>
<p>{{comment.comment}}</p>
<div class="btn-group">
<button type="button" class="btn btn-default btn-xs" ng-click="controller.editComment(topic, comment)">Edit <i class="fa fa-pencil-square-o"></i></button>
<button type="button" class="btn btn-default btn-xs" ng-click="controller.deleteComment(comment)">Delete <i class="fa fa-trash-o"></i></button>
</div>
</li>
</ul>
</div>
</accordion-group>
</accordion>
The controller snippet is:
self.model = {
closeOthers : false,
opened : new Array(),
topics : undefined
};
The 'topics' are populated on an AJAX call. Separating the 'opened' state from the model objects that are updated from the server means the state is preserved across refreshes.
I also declare the controller with ng-controller="CaseController as controller"
accordion-controller.js
MyApp.Controllers
.controller('AccordionCtrl', ['$scope', function ($scope) {
$scope.groups = [
{
title: "Dynamic Group Header - 1",
content: "Dynamic Group Body - 1",
open: false
},
{
title: "Dynamic Group Header - 2",
content: "Dynamic Group Body - 2",
open: false
},
{
title: "Dynamic Group Header - 3",
content: "Dynamic Group Body - 3",
open: false
}
];
/**
* Open panel method
* #param idx {Number} - Array index
*/
$scope.openPanel = function (idx) {
if (!$scope.groups[idx].open) {
console.log("Opened group with idx: " + idx);
$scope.groups[idx].open = true;
}
};
/**
* Close panel method
* #param idx {Number} - Array index
*/
$scope.closePanel = function (idx) {
if ($scope.groups[idx].open) {
console.log("Closed group with idx: " + idx);
$scope.groups[idx].open = false;
}
};
}]);
index.html
<div ng-controller="AccordionCtrl">
<accordion>
<accordion-group ng-repeat="group in groups" is-open="group.open">
<button ng-click="closePanel($index)">Close me</button>
{{group.content}}
</accordion-group>
<button ng-click="openPanel(0)">Set 1</button>
<button ng-click="openPanel(1)">Set 2</button>
<button ng-click="openPanel(2)">Set 3</button>
</accordion>
</div>
You can do it w/ an Angular directive:
html
<div uib-accordion-group is-open="property.display_detail" ng-repeat="property in properties">
<div uib-accordion-heading ng-click="property.display_detail = ! property.display_detail">
some heading text
</div>
<!-- here is the accordion body -->
<div ng-init="i=$index"> <!-- I keep track of the index of ng-repeat -->
<!-- and I call a custom directive -->
<mydirective mydirective_model="properties" mydirective_index="{% verbatim ng %}{{ i }}{% endverbatim ng %}">
here is the body
</mydirective>
</div>
</div>
js
app.directive("mydirective", function() {
return {
restrict: "EAC",
link: function(scope, element, attrs) {
/* note that ng converts everything to camelCase */
var model = attrs["mydirectiveModel"];
var index = attrs["mydirectiveIndex"];
var watched_name = model + "[" + index + "].display_detail"
scope.$watch(watched_name, function(is_displayed) {
if (is_displayed) {
alert("you opened something");
}
else {
alert("you closed something");
}
});
}
}
});
There are some idiosyncrasies about my setup there (I use Django, hence the "{% verbatim %}" tags), but the method should work.