diff --git a/src/collapse/collapse.js b/src/collapse/collapse.js index dda4b14a6b..9003882b1e 100644 --- a/src/collapse/collapse.js +++ b/src/collapse/collapse.js @@ -1,9 +1,14 @@ angular.module('ui.bootstrap.collapse', []) - .directive('uibCollapse', ['$animate', '$injector', function($animate, $injector) { + .directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function($animate, $q, $parse, $injector) { var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null; return { link: function(scope, element, attrs) { + var expandingExpr = $parse(attrs.expanding), + expandedExpr = $parse(attrs.expanded), + collapsingExpr = $parse(attrs.collapsing), + collapsedExpr = $parse(attrs.collapsed); + if (!scope.$eval(attrs.uibCollapse)) { element.addClass('in') .addClass('collapse') @@ -11,28 +16,32 @@ angular.module('ui.bootstrap.collapse', []) } function expand() { - element.removeClass('collapse') - .addClass('collapsing') - .attr('aria-expanded', true) - .attr('aria-hidden', false); + $q.resolve(expandingExpr(scope)) + .then(function() { + element.removeClass('collapse') + .addClass('collapsing') + .attr('aria-expanded', true) + .attr('aria-hidden', false); - if ($animateCss) { - $animateCss(element, { - addClass: 'in', - easing: 'ease', - to: { height: element[0].scrollHeight + 'px' } - }).start()['finally'](expandDone); - } else { - $animate.addClass(element, 'in', { - to: { height: element[0].scrollHeight + 'px' } - }).then(expandDone); - } + if ($animateCss) { + $animateCss(element, { + addClass: 'in', + easing: 'ease', + to: { height: element[0].scrollHeight + 'px' } + }).start()['finally'](expandDone); + } else { + $animate.addClass(element, 'in', { + to: { height: element[0].scrollHeight + 'px' } + }).then(expandDone); + } + }); } function expandDone() { element.removeClass('collapsing') .addClass('collapse') .css({height: 'auto'}); + expandedExpr(scope); } function collapse() { @@ -40,34 +49,38 @@ angular.module('ui.bootstrap.collapse', []) return collapseDone(); } - element - // IMPORTANT: The height must be set before adding "collapsing" class. - // Otherwise, the browser attempts to animate from height 0 (in - // collapsing class) to the given height here. - .css({height: element[0].scrollHeight + 'px'}) - // initially all panel collapse have the collapse class, this removal - // prevents the animation from jumping to collapsed state - .removeClass('collapse') - .addClass('collapsing') - .attr('aria-expanded', false) - .attr('aria-hidden', true); + $q.resolve(collapsingExpr(scope)) + .then(function() { + element + // IMPORTANT: The height must be set before adding "collapsing" class. + // Otherwise, the browser attempts to animate from height 0 (in + // collapsing class) to the given height here. + .css({height: element[0].scrollHeight + 'px'}) + // initially all panel collapse have the collapse class, this removal + // prevents the animation from jumping to collapsed state + .removeClass('collapse') + .addClass('collapsing') + .attr('aria-expanded', false) + .attr('aria-hidden', true); - if ($animateCss) { - $animateCss(element, { - removeClass: 'in', - to: {height: '0'} - }).start()['finally'](collapseDone); - } else { - $animate.removeClass(element, 'in', { - to: {height: '0'} - }).then(collapseDone); - } + if ($animateCss) { + $animateCss(element, { + removeClass: 'in', + to: {height: '0'} + }).start()['finally'](collapseDone); + } else { + $animate.removeClass(element, 'in', { + to: {height: '0'} + }).then(collapseDone); + } + }); } function collapseDone() { element.css({height: '0'}); // Required so that collapse works when animation is disabled element.removeClass('collapsing') .addClass('collapse'); + collapsedExpr(scope); } scope.$watch(attrs.uibCollapse, function(shouldCollapse) { diff --git a/src/collapse/docs/readme.md b/src/collapse/docs/readme.md index d1b2d9d46a..30a4840811 100644 --- a/src/collapse/docs/readme.md +++ b/src/collapse/docs/readme.md @@ -7,3 +7,23 @@ _(Default: `false`)_ - Whether the element should be collapsed or not. + +* `collapsing()` + $ - + An optional expression called before the element begins collapsing. + If the expression returns a promise, animation won't start until the promise resolves. + If the returned promise is rejected, collapsing will be cancelled. + +* `collapsed()` + $ - + An optional expression called after the element finished collapsing. + +* `expanding()` + $ - + An optional expression called before the element begins expanding. + If the expression returns a promise, animation won't start until the promise resolves. + If the returned promise is rejected, expanding will be cancelled. + +* `expanded()` + $ - + An optional expression called after the element finished expanding. diff --git a/src/collapse/test/collapse.spec.js b/src/collapse/test/collapse.spec.js index 94c3b4b173..76539eb7cf 100644 --- a/src/collapse/test/collapse.spec.js +++ b/src/collapse/test/collapse.spec.js @@ -1,16 +1,23 @@ describe('collapse directive', function() { - var element, compileFn, scope, $compile, $animate; + var element, compileFn, scope, $compile, $animate, $q; beforeEach(module('ui.bootstrap.collapse')); beforeEach(module('ngAnimateMock')); - beforeEach(inject(function(_$rootScope_, _$compile_, _$animate_) { + beforeEach(inject(function(_$rootScope_, _$compile_, _$animate_, _$q_) { scope = _$rootScope_; $compile = _$compile_; $animate = _$animate_; + $q = _$q_; })); beforeEach(function() { - element = angular.element('