After some reflection, I devised a new strategy and wanted to share it with those who are interested.
To begin, let's establish the states required for transition. I'll demonstrate this using only two collapse panels instead of the original three. It may seem like a lot of code, but the solution is valuable enough to warrant sharing.
Routing
app.js
.state('home.checkout', {
url: 'checkout',
views: {
'@home': {
templateUrl: 'views/partials/generic/checkout-process/order-checkout-root.html'
}
}
})
.state('home.checkout.shoppingcart', {
url: '^/shoppingcart',
views: {
'<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="aad9c2c5dadac3c4cd87c9cbd8deeac2c5c7cf84c9c2cfc9c1c5dfde">[email protected]</a>': {
templateUrl: 'views/partials/generic/checkout-process/shoppingcart/shopping-cart-partial.html',
controller: 'ShoppingCartController'
},
'<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="b4dbc6d0d1c699d7dbdaf2dd69c4dce3dfd7dcced6dddacbcedfd0c6cacec8cdc7c6">[email protected]</a>' : {
templateUrl: 'views/partials/generic/checkout-process/closed-state.html',
controller: function($scope) {
$scope.page = {name: 'Order Confirmation'};
$scope.state = {name: 'home.checkout.confirm'};
}
}
}
})
.state('home.checkout.confirm', {
url: '/confirmation',
views: {
'<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="1e6d76716e6e777079337d7f6c6a5e7671737b307d767b7d75716b6a">[email protected]</a>': {
templateUrl: 'views/partials/generic/checkout-process/closed-state.html',
controller: function($scope) {
$scope.page = {name: 'Shopping Cart'};
$scope.state = {name: 'home.checkout.shoppingcart'};
}
},
'<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="abc4d9cfced986c8c4c5cdc2d9c6cadfc2c4c5ebc3c4c6ce85c8c3cec8c0c4dedf">[email protected]</a>': {
templateUrl: '../views/partials/generic/checkout-process/confirmation/order-confirmation-partial.html',
controller: 'OrderConfirmationController'
}
}
})
HTML
order-checkout-root.html
<div class="row checkout-process">
<section class="col-sm-8 col-md-8 col-lg-8 panel-group" id="accordion">
<div class="shopping-cart panel panel-default" ui-view="shopping-cart" autoscroll="false"></div>
<div class="order-confirmation panel panel-default" ui-view="order-confirmation" autoscroll="false"></div>
</section>
</div>
closed-state.html
<article class="col-sm-12 col-md-12 col-lg-12 panel-heading closed-state">
<h4 class="panel-title">
<a ui-sref="{{state.name}}">
{{page.name}}
</a>
</h4>
</article>
order-confirmation-partial.html
I will only include this one and not the other partial as its the same idea.
<div class="order-confirmation-page row">
<div class="panel-heading">
<h4 class="panel-title">Order Confirmation</h4>
</div>
<div class="panel-collapse collapse" kx-collapse-toggler data-toggle="collapse">
<div class="panel-body">
<!--Code for the collapse body goes here-->
</div>
</div>
</div>
Whats important from this last partial is to note the inclusion of the directive
kx-collapse-toggler
This is where we do our work and the most interesting part of the code
collapseTogglerDirective.js
'use strict';
angular.module('App.Directives.CollapseToggler', [])
.directive('kxCollapseToggler', function ($rootScope, $state, $q, $timeout) {
var linker = function(scope, element) {
var
collapse = $q.defer(),
changeEventStarted = false
;
//Expand the panel on directive instantiation
$timeout(function() {
$(element).collapse('show');
}, 300);
$rootScope.$on('$stateChangeStart', function(event, toState) {
//Check to make sure we arent in the middle of a $stateChangeEvent
if(changeEventStarted) {
return;
}
//Stop the state transition
event.preventDefault();
//Collapse the panel
$(element).collapse('hide');
//Wait for the panel to collapse completely
collapse.promise.then(function() {
changeEventStarted = true;
//Then transiton the state
$state.transitionTo(toState);
});
});
//Event listener for the collapse completed
$(element).on('hidden.bs.collapse', function() {
collapse.resolve();
});
};
return {
restrict: 'A',
link: linker
};
});
In short what we do here is:
- Setup a promise to know when we can transition again.
- Intercept the $stateChangeStart event and stop it from happening.
- Then we collapse the panel we are interested in
- When the collapse is finished bootstrap fires an event saying I am done collapsing which we listen for and in turn resolve the promise
- When the promise is resolved we can safely transition to the next state
I hope that this isnt too much to follow, but if you do the potential it has for other kinds of animation is pretty great.
I am working on putting together a plunker so its possible to see the animation.