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

Commit 1653afa

Browse files
committed
refactor(dropdown): use container for class toggle detection
1 parent 7e2f2c1 commit 1653afa

File tree

8 files changed

+168
-69
lines changed

8 files changed

+168
-69
lines changed

Diff for: src/dropdown/dropdown.js

+73-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
1-
angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
1+
angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.position'])
22

33
.constant('uibDropdownConfig', {
44
appendToOpenClass: 'uib-dropdown-open',
55
openClass: 'open'
66
})
77

8-
.service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
8+
.service('uibDropdownService', ['$document', '$rootScope', '$$multiMap', function($document, $rootScope, $$multiMap) {
99
var openScope = null;
10+
var openedContainers = $$multiMap.createNew();
11+
12+
this.isOnlyOpen = function(dropdownScope, appendTo) {
13+
var openedDropdowns = openedContainers.get(appendTo);
14+
if (openedDropdowns) {
15+
var openDropdown = openedDropdowns.reduce(function(toClose, dropdown) {
16+
if (dropdown.scope === dropdownScope) {
17+
return dropdown;
18+
}
19+
20+
return toClose;
21+
}, {});
22+
if (openDropdown) {
23+
return openedDropdowns.length === 1;
24+
}
25+
}
1026

11-
this.open = function(dropdownScope, element) {
27+
return false;
28+
};
29+
30+
this.open = function(dropdownScope, element, appendTo) {
1231
if (!openScope) {
1332
$document.on('click', closeDropdown);
1433
}
@@ -18,14 +37,52 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
1837
}
1938

2039
openScope = dropdownScope;
40+
41+
if (!appendTo) {
42+
return;
43+
}
44+
45+
var openedDropdowns = openedContainers.get(appendTo);
46+
if (openedDropdowns) {
47+
var openedScopes = openedDropdowns.map(function(dropdown) {
48+
return dropdown.scope;
49+
});
50+
if (openedScopes.indexOf(dropdownScope) === -1) {
51+
openedContainers.put(appendTo, {
52+
scope: dropdownScope
53+
});
54+
}
55+
} else {
56+
openedContainers.put(appendTo, {
57+
scope: dropdownScope
58+
});
59+
}
2160
};
2261

23-
this.close = function(dropdownScope, element) {
62+
this.close = function(dropdownScope, element, appendTo) {
2463
if (openScope === dropdownScope) {
2564
$document.off('click', closeDropdown);
2665
$document.off('keydown', this.keybindFilter);
2766
openScope = null;
2867
}
68+
69+
if (!appendTo) {
70+
return;
71+
}
72+
73+
var openedDropdowns = openedContainers.get(appendTo);
74+
if (openedDropdowns) {
75+
var dropdownToClose = openedDropdowns.reduce(function(toClose, dropdown) {
76+
if (dropdown.scope === dropdownScope) {
77+
return dropdown;
78+
}
79+
80+
return toClose;
81+
}, {});
82+
if (dropdownToClose) {
83+
openedContainers.remove(appendTo, dropdownToClose);
84+
}
85+
}
2986
};
3087

3188
var closeDropdown = function(evt) {
@@ -244,10 +301,18 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
244301
}
245302

246303
var openContainer = appendTo ? appendTo : $element;
247-
var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass);
304+
var dropdownOpenClass = appendTo ? appendToOpenClass : openClass;
305+
var hasOpenClass = openContainer.hasClass(dropdownOpenClass);
306+
var isOnlyOpen = uibDropdownService.isOnlyOpen($scope, appendTo);
248307

249308
if (hasOpenClass === !isOpen) {
250-
$animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
309+
var toggleClass;
310+
if (appendTo) {
311+
toggleClass = !isOnlyOpen ? 'addClass' : 'removeClass';
312+
} else {
313+
toggleClass = isOpen ? 'addClass' : 'removeClass';
314+
}
315+
$animate[toggleClass](openContainer, dropdownOpenClass).then(function() {
251316
if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
252317
toggleInvoker($scope, { open: !!isOpen });
253318
}
@@ -270,10 +335,10 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
270335
}
271336

272337
scope.focusToggleElement();
273-
uibDropdownService.open(scope, $element);
338+
uibDropdownService.open(scope, $element, appendTo);
274339
} else {
275340
$document.off('keydown', uibDropdownService.keybindFilter);
276-
uibDropdownService.close(scope, $element);
341+
uibDropdownService.close(scope, $element, appendTo);
277342
if (self.dropdownMenuTemplateUrl) {
278343
if (templateScope) {
279344
templateScope.$destroy();

Diff for: src/dropdown/index-nocss.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require('../multiMap');
12
require('../position/index-nocss.js');
23
require('./dropdown');
34

Diff for: src/dropdown/test/dropdown.spec.js

+33-2
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,11 @@ describe('uib-dropdown', function() {
9999
expect(elm1).not.toHaveClass(dropdownConfig.openClass);
100100
expect(elm2).not.toHaveClass(dropdownConfig.openClass);
101101

102-
clickDropdownToggle( elm1 );
102+
clickDropdownToggle(elm1);
103103
expect(elm1).toHaveClass(dropdownConfig.openClass);
104104
expect(elm2).not.toHaveClass(dropdownConfig.openClass);
105105

106-
clickDropdownToggle( elm2 );
106+
clickDropdownToggle(elm2);
107107
expect(elm1).not.toHaveClass(dropdownConfig.openClass);
108108
expect(elm2).toHaveClass(dropdownConfig.openClass);
109109
});
@@ -303,6 +303,37 @@ describe('uib-dropdown', function() {
303303
});
304304
});
305305

306+
describe('using dropdown-append-to with two dropdowns', function() {
307+
function dropdown() {
308+
return $compile('<div><div class="dropdown1" uib-dropdown dropdown-append-to="appendTo" on-toggle="log(1, open)"><a href uib-dropdown-toggle></a><ul class="dropdown-menu" uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Container</a></li></ul></div><div class="dropdown2" uib-dropdown dropdown-append-to="appendTo" on-toggle="log(2, open)"><a href uib-dropdown-toggle></a><ul class="dropdown-menu" uib-dropdown-menu id="dropdown-menu"><li><a href>Hello On Container</a></li></ul></div></div>')($rootScope);
309+
}
310+
311+
beforeEach(function() {
312+
$document.find('body').append(angular.element('<div id="dropdown-container"></div>'));
313+
314+
$rootScope.appendTo = $document.find('#dropdown-container');
315+
$rootScope.log = jasmine.createSpy('log');
316+
317+
element = dropdown();
318+
$document.find('body').append(element);
319+
});
320+
321+
afterEach(function() {
322+
// Cleanup the extra elements we appended
323+
$document.find('#dropdown-container').remove();
324+
});
325+
326+
it('should keep the class when toggling from one dropdown to another with the same container', function() {
327+
var container = $document.find('#dropdown-container');
328+
329+
expect(container).not.toHaveClass('uib-dropdown-open');
330+
element.find('.dropdown1 [uib-dropdown-toggle]').click();
331+
expect(container).toHaveClass('uib-dropdown-open');
332+
element.find('.dropdown2 [uib-dropdown-toggle]').click();
333+
expect(container).toHaveClass('uib-dropdown-open');
334+
});
335+
});
336+
306337
describe('using is-open', function() {
307338
describe('with uib-dropdown-toggle', function() {
308339
beforeEach(function() {

Diff for: src/modal/index-nocss.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require('../multiMap');
12
require('../position/index-nocss.js');
23
require('../stackedMap');
34
require('../../template/modal/window.html.js');

Diff for: src/modal/modal.js

+3-58
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,4 @@
1-
angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
2-
/**
3-
* A helper, internal data structure that stores all references attached to key
4-
*/
5-
.factory('$$multiMap', function() {
6-
return {
7-
createNew: function() {
8-
var map = {};
9-
10-
return {
11-
entries: function() {
12-
return Object.keys(map).map(function(key) {
13-
return {
14-
key: key,
15-
value: map[key]
16-
};
17-
});
18-
},
19-
get: function(key) {
20-
return map[key];
21-
},
22-
hasKey: function(key) {
23-
return !!map[key];
24-
},
25-
keys: function() {
26-
return Object.keys(map);
27-
},
28-
put: function(key, value) {
29-
if (!map[key]) {
30-
map[key] = [];
31-
}
32-
33-
map[key].push(value);
34-
},
35-
remove: function(key, value) {
36-
var values = map[key];
37-
38-
if (!values) {
39-
return;
40-
}
41-
42-
var idx = values.indexOf(value);
43-
44-
if (idx !== -1) {
45-
values.splice(idx, 1);
46-
}
47-
48-
if (!values.length) {
49-
delete map[key];
50-
}
51-
}
52-
};
53-
}
54-
};
55-
})
56-
1+
angular.module('ui.bootstrap.modal', ['ui.bootstrap.multiMap', 'ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
572
/**
583
* Pluggable resolve mechanism for the modal resolve resolution
594
* Supports UI Router's $resolve service
@@ -569,7 +514,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
569514
ariaHiddenCount = parseInt(sibling.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10);
570515

571516
if (!ariaHiddenCount) {
572-
ariaHiddenCount = elemIsAlreadyHidden ? 1 : 0;
517+
ariaHiddenCount = elemIsAlreadyHidden ? 1 : 0;
573518
}
574519

575520
sibling.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, ariaHiddenCount + 1);
@@ -607,7 +552,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
607552
}
608553
);
609554
}
610-
555+
611556
$modalStack.close = function(modalInstance, result) {
612557
var modalWindow = openedWindows.get(modalInstance);
613558
unhideBackgroundElements();

Diff for: src/multiMap/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require('./multiMap.js');

Diff for: src/multiMap/multiMap.js

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
angular.module('ui.bootstrap.multiMap', [])
2+
/**
3+
* A helper, internal data structure that stores all references attached to key
4+
*/
5+
.factory('$$multiMap', function() {
6+
return {
7+
createNew: function() {
8+
var map = {};
9+
10+
return {
11+
entries: function() {
12+
return Object.keys(map).map(function(key) {
13+
return {
14+
key: key,
15+
value: map[key]
16+
};
17+
});
18+
},
19+
get: function(key) {
20+
return map[key];
21+
},
22+
hasKey: function(key) {
23+
return !!map[key];
24+
},
25+
keys: function() {
26+
return Object.keys(map);
27+
},
28+
put: function(key, value) {
29+
if (!map[key]) {
30+
map[key] = [];
31+
}
32+
33+
map[key].push(value);
34+
},
35+
remove: function(key, value) {
36+
var values = map[key];
37+
38+
if (!values) {
39+
return;
40+
}
41+
42+
var idx = values.indexOf(value);
43+
44+
if (idx !== -1) {
45+
values.splice(idx, 1);
46+
}
47+
48+
if (!values.length) {
49+
delete map[key];
50+
}
51+
}
52+
};
53+
}
54+
};
55+
});

Diff for: src/modal/test/multiMap.spec.js renamed to src/multiMap/test/multiMap.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
describe('multi map', function() {
22
var multiMap;
33

4-
beforeEach(module('ui.bootstrap.modal'));
4+
beforeEach(module('ui.bootstrap.multiMap'));
55
beforeEach(inject(function($$multiMap) {
66
multiMap = $$multiMap.createNew();
77
}));

0 commit comments

Comments
 (0)