Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.

Commit 1ec0997

Browse files
squelixwesleycho
authored andcommittedJun 16, 2016
feat(collapse): add horizontal support
- Add support for horizontal collapsing Closes #3593 Closes #6010
1 parent 4e0e34f commit 1ec0997

File tree

5 files changed

+314
-20
lines changed

5 files changed

+314
-20
lines changed
 

Diff for: ‎src/collapse/collapse.js

+47-19
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,44 @@ angular.module('ui.bootstrap.collapse', [])
55
return {
66
link: function(scope, element, attrs) {
77
var expandingExpr = $parse(attrs.expanding),
8-
expandedExpr = $parse(attrs.expanded),
9-
collapsingExpr = $parse(attrs.collapsing),
10-
collapsedExpr = $parse(attrs.collapsed);
8+
expandedExpr = $parse(attrs.expanded),
9+
collapsingExpr = $parse(attrs.collapsing),
10+
collapsedExpr = $parse(attrs.collapsed),
11+
horizontal = false,
12+
css = {},
13+
cssTo = {};
1114

12-
if (!scope.$eval(attrs.uibCollapse)) {
13-
element.addClass('in')
14-
.addClass('collapse')
15-
.attr('aria-expanded', true)
16-
.attr('aria-hidden', false)
17-
.css({height: 'auto'});
15+
init();
16+
17+
function init() {
18+
horizontal = !!('horizontal' in attrs);
19+
if (horizontal) {
20+
css = {
21+
width: 'auto',
22+
height: 'inherit'
23+
};
24+
cssTo = {width: '0'};
25+
} else {
26+
css = {
27+
width: 'inherit',
28+
height: 'auto'
29+
};
30+
cssTo = {height: '0'};
31+
}
32+
if (!scope.$eval(attrs.uibCollapse)) {
33+
element.addClass('in')
34+
.addClass('collapse')
35+
.attr('aria-expanded', true)
36+
.attr('aria-hidden', false)
37+
.css(css);
38+
}
39+
}
40+
41+
function getScrollFromElement(element) {
42+
if (horizontal) {
43+
return {width: element.scrollWidth + 'px'};
44+
}
45+
return {height: element.scrollHeight + 'px'};
1846
}
1947

2048
function expand() {
@@ -33,11 +61,11 @@ angular.module('ui.bootstrap.collapse', [])
3361
$animateCss(element, {
3462
addClass: 'in',
3563
easing: 'ease',
36-
to: { height: element[0].scrollHeight + 'px' }
64+
to: getScrollFromElement(element[0])
3765
}).start()['finally'](expandDone);
3866
} else {
3967
$animate.addClass(element, 'in', {
40-
to: { height: element[0].scrollHeight + 'px' }
68+
to: getScrollFromElement(element[0])
4169
}).then(expandDone);
4270
}
4371
});
@@ -46,7 +74,7 @@ angular.module('ui.bootstrap.collapse', [])
4674
function expandDone() {
4775
element.removeClass('collapsing')
4876
.addClass('collapse')
49-
.css({height: 'auto'});
77+
.css(css);
5078
expandedExpr(scope);
5179
}
5280

@@ -58,10 +86,10 @@ angular.module('ui.bootstrap.collapse', [])
5886
$q.resolve(collapsingExpr(scope))
5987
.then(function() {
6088
element
61-
// IMPORTANT: The height must be set before adding "collapsing" class.
62-
// Otherwise, the browser attempts to animate from height 0 (in
63-
// collapsing class) to the given height here.
64-
.css({height: element[0].scrollHeight + 'px'})
89+
// IMPORTANT: The width must be set before adding "collapsing" class.
90+
// Otherwise, the browser attempts to animate from width 0 (in
91+
// collapsing class) to the given width here.
92+
.css(getScrollFromElement(element[0]))
6593
// initially all panel collapse have the collapse class, this removal
6694
// prevents the animation from jumping to collapsed state
6795
.removeClass('collapse')
@@ -72,18 +100,18 @@ angular.module('ui.bootstrap.collapse', [])
72100
if ($animateCss) {
73101
$animateCss(element, {
74102
removeClass: 'in',
75-
to: {height: '0'}
103+
to: cssTo
76104
}).start()['finally'](collapseDone);
77105
} else {
78106
$animate.removeClass(element, 'in', {
79-
to: {height: '0'}
107+
to: cssTo
80108
}).then(collapseDone);
81109
}
82110
});
83111
}
84112

85113
function collapseDone() {
86-
element.css({height: '0'}); // Required so that collapse works when animation is disabled
114+
element.css(cssTo); // Required so that collapse works when animation is disabled
87115
element.removeClass('collapsing')
88116
.addClass('collapse');
89117
collapsedExpr(scope);

Diff for: ‎src/collapse/docs/demo.html

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
<div ng-controller="CollapseDemoCtrl">
2-
<button type="button" class="btn btn-default" ng-click="isCollapsed = !isCollapsed">Toggle collapse</button>
2+
<button type="button" class="btn btn-default" ng-click="isCollapsed = !isCollapsed">Toggle collapse Vertically</button>
33
<hr>
44
<div uib-collapse="isCollapsed">
55
<div class="well well-lg">Some content</div>
66
</div>
7+
8+
<button type="button" class="btn btn-default" ng-click="isCollapsedHorizontal = !isCollapsedHorizontal">Toggle collapse Horizontally</button>
9+
<hr>
10+
<div uib-collapse="isCollapsedHorizontal" horizontal>
11+
<div class="well well-lg">Some content</div>
12+
</div>
713
</div>

Diff for: ‎src/collapse/docs/demo.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
angular.module('ui.bootstrap.demo').controller('CollapseDemoCtrl', function ($scope) {
22
$scope.isCollapsed = false;
3+
$scope.isCollapsedHorizontal = false;
34
});

Diff for: ‎src/collapse/docs/readme.md

+4
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,8 @@
2727
<i class="glyphicon glyphicon-eye-open"></i>
2828
_(Default: `false`)_ -
2929
Whether the element should be collapsed or not.
30+
31+
* `horizontal`
32+
<small class="badge">$</small> -
33+
An optional attribute that permit to collapse horizontally.
3034

Diff for: ‎src/collapse/test/collapseHorizontally.spec.js

+255
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
describe('collapse directive', function() {
2+
var elementH, compileFnH, scope, $compile, $animate, $q;
3+
4+
beforeEach(module('ui.bootstrap.collapse'));
5+
beforeEach(module('ngAnimateMock'));
6+
beforeEach(inject(function(_$rootScope_, _$compile_, _$animate_, _$q_) {
7+
scope = _$rootScope_;
8+
$compile = _$compile_;
9+
$animate = _$animate_;
10+
$q = _$q_;
11+
}));
12+
13+
beforeEach(function() {
14+
elementH = angular.element(
15+
'<div uib-collapse="isCollapsed" '
16+
+ 'expanding="expanding()" '
17+
+ 'expanded="expanded()" '
18+
+ 'collapsing="collapsing()" '
19+
+ 'collapsed="collapsed()" '
20+
+ 'horizontal>'
21+
+ 'Some Content</div>');
22+
compileFnH = $compile(elementH);
23+
angular.element(document.body).append(elementH);
24+
});
25+
26+
afterEach(function() {
27+
elementH.remove();
28+
});
29+
30+
function initCallbacks() {
31+
scope.collapsing = jasmine.createSpy('scope.collapsing');
32+
scope.collapsed = jasmine.createSpy('scope.collapsed');
33+
scope.expanding = jasmine.createSpy('scope.expanding');
34+
scope.expanded = jasmine.createSpy('scope.expanded');
35+
}
36+
37+
function assertCallbacks(expected) {
38+
['collapsing', 'collapsed', 'expanding', 'expanded'].forEach(function(cbName) {
39+
if (expected[cbName]) {
40+
expect(scope[cbName]).toHaveBeenCalled();
41+
} else {
42+
expect(scope[cbName]).not.toHaveBeenCalled();
43+
}
44+
});
45+
}
46+
47+
it('should be hidden on initialization if isCollapsed = true', function() {
48+
initCallbacks();
49+
scope.isCollapsed = true;
50+
compileFnH(scope);
51+
scope.$digest();
52+
expect(elementH.width()).toBe(0);
53+
assertCallbacks({ collapsed: true });
54+
});
55+
56+
it('should not trigger any animation on initialization if isCollapsed = true', function() {
57+
var wrapperFn = function() {
58+
$animate.flush();
59+
};
60+
61+
scope.isCollapsed = true;
62+
compileFnH(scope);
63+
scope.$digest();
64+
65+
expect(wrapperFn).toThrowError(/No pending animations ready to be closed or flushed/);
66+
});
67+
68+
it('should collapse if isCollapsed = true on subsequent use', function() {
69+
scope.isCollapsed = false;
70+
compileFnH(scope);
71+
scope.$digest();
72+
initCallbacks();
73+
scope.isCollapsed = true;
74+
scope.$digest();
75+
$animate.flush();
76+
expect(elementH.width()).toBe(0);
77+
assertCallbacks({ collapsing: true, collapsed: true });
78+
});
79+
80+
it('should show after toggled from collapsed', function() {
81+
initCallbacks();
82+
scope.isCollapsed = true;
83+
compileFnH(scope);
84+
scope.$digest();
85+
expect(elementH.width()).toBe(0);
86+
assertCallbacks({ collapsed: true });
87+
scope.collapsed.calls.reset();
88+
89+
scope.isCollapsed = false;
90+
scope.$digest();
91+
$animate.flush();
92+
expect(elementH.width()).not.toBe(0);
93+
assertCallbacks({ expanding: true, expanded: true });
94+
});
95+
96+
it('should not trigger any animation on initialization if isCollapsed = false', function() {
97+
var wrapperFn = function() {
98+
$animate.flush();
99+
};
100+
101+
scope.isCollapsed = false;
102+
compileFnH(scope);
103+
scope.$digest();
104+
105+
expect(wrapperFn).toThrowError(/No pending animations ready to be closed or flushed/);
106+
});
107+
108+
it('should expand if isCollapsed = false on subsequent use', function() {
109+
scope.isCollapsed = false;
110+
compileFnH(scope);
111+
scope.$digest();
112+
scope.isCollapsed = true;
113+
scope.$digest();
114+
$animate.flush();
115+
initCallbacks();
116+
scope.isCollapsed = false;
117+
scope.$digest();
118+
$animate.flush();
119+
expect(elementH.width()).not.toBe(0);
120+
assertCallbacks({ expanding: true, expanded: true });
121+
});
122+
123+
it('should collapse if isCollapsed = true on subsequent uses', function() {
124+
scope.isCollapsed = false;
125+
compileFnH(scope);
126+
scope.$digest();
127+
scope.isCollapsed = true;
128+
scope.$digest();
129+
$animate.flush();
130+
scope.isCollapsed = false;
131+
scope.$digest();
132+
$animate.flush();
133+
initCallbacks();
134+
scope.isCollapsed = true;
135+
scope.$digest();
136+
$animate.flush();
137+
expect(elementH.width()).toBe(0);
138+
assertCallbacks({ collapsing: true, collapsed: true });
139+
});
140+
141+
it('should change aria-expanded attribute', function() {
142+
scope.isCollapsed = false;
143+
compileFnH(scope);
144+
scope.$digest();
145+
expect(elementH.attr('aria-expanded')).toBe('true');
146+
147+
scope.isCollapsed = true;
148+
scope.$digest();
149+
$animate.flush();
150+
expect(elementH.attr('aria-expanded')).toBe('false');
151+
});
152+
153+
it('should change aria-hidden attribute', function() {
154+
scope.isCollapsed = false;
155+
compileFnH(scope);
156+
scope.$digest();
157+
expect(elementH.attr('aria-hidden')).toBe('false');
158+
159+
scope.isCollapsed = true;
160+
scope.$digest();
161+
$animate.flush();
162+
expect(elementH.attr('aria-hidden')).toBe('true');
163+
});
164+
165+
describe('expanding callback returning a promise', function() {
166+
var defer, collapsedWidth;
167+
168+
beforeEach(function() {
169+
defer = $q.defer();
170+
171+
scope.isCollapsed = true;
172+
scope.expanding = function() {
173+
return defer.promise;
174+
};
175+
compileFnH(scope);
176+
scope.$digest();
177+
collapsedWidth = elementH.width();
178+
179+
// set flag to expand ...
180+
scope.isCollapsed = false;
181+
scope.$digest();
182+
183+
// ... shouldn't expand yet ...
184+
expect(elementH.attr('aria-expanded')).not.toBe('true');
185+
expect(elementH.width()).toBe(collapsedWidth);
186+
});
187+
188+
it('should wait for it to resolve before animating', function() {
189+
defer.resolve();
190+
191+
// should now expand
192+
scope.$digest();
193+
$animate.flush();
194+
195+
expect(elementH.attr('aria-expanded')).toBe('true');
196+
expect(elementH.width()).toBeGreaterThan(collapsedWidth);
197+
});
198+
199+
it('should not animate if it rejects', function() {
200+
defer.reject();
201+
202+
// should NOT expand
203+
scope.$digest();
204+
205+
expect(elementH.attr('aria-expanded')).not.toBe('true');
206+
expect(elementH.width()).toBe(collapsedWidth);
207+
});
208+
});
209+
210+
describe('collapsing callback returning a promise', function() {
211+
var defer, expandedWidth;
212+
213+
beforeEach(function() {
214+
defer = $q.defer();
215+
scope.isCollapsed = false;
216+
scope.collapsing = function() {
217+
return defer.promise;
218+
};
219+
compileFnH(scope);
220+
scope.$digest();
221+
222+
expandedWidth = elementH.width();
223+
224+
// set flag to collapse ...
225+
scope.isCollapsed = true;
226+
scope.$digest();
227+
228+
// ... but it shouldn't collapse yet ...
229+
expect(elementH.attr('aria-expanded')).not.toBe('false');
230+
expect(elementH.width()).toBe(expandedWidth);
231+
});
232+
233+
it('should wait for it to resolve before animating', function() {
234+
defer.resolve();
235+
236+
// should now collapse
237+
scope.$digest();
238+
$animate.flush();
239+
240+
expect(elementH.attr('aria-expanded')).toBe('false');
241+
expect(elementH.width()).toBeLessThan(expandedWidth);
242+
});
243+
244+
it('should not animate if it rejects', function() {
245+
defer.reject();
246+
247+
// should NOT collapse
248+
scope.$digest();
249+
250+
expect(elementH.attr('aria-expanded')).not.toBe('false');
251+
expect(elementH.width()).toBe(expandedWidth);
252+
});
253+
});
254+
255+
});

0 commit comments

Comments
 (0)
This repository has been archived.