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

Commit

Permalink
refactor(dropdown): use container for class toggle detection
Browse files Browse the repository at this point in the history
  • Loading branch information
wesleycho committed Nov 29, 2016
1 parent 7e2f2c1 commit 1653afa
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 69 deletions.
81 changes: 73 additions & 8 deletions src/dropdown/dropdown.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.position'])

.constant('uibDropdownConfig', {
appendToOpenClass: 'uib-dropdown-open',
openClass: 'open'
})

.service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
.service('uibDropdownService', ['$document', '$rootScope', '$$multiMap', function($document, $rootScope, $$multiMap) {
var openScope = null;
var openedContainers = $$multiMap.createNew();

this.isOnlyOpen = function(dropdownScope, appendTo) {
var openedDropdowns = openedContainers.get(appendTo);
if (openedDropdowns) {
var openDropdown = openedDropdowns.reduce(function(toClose, dropdown) {
if (dropdown.scope === dropdownScope) {
return dropdown;
}

return toClose;
}, {});
if (openDropdown) {
return openedDropdowns.length === 1;
}
}

this.open = function(dropdownScope, element) {
return false;
};

this.open = function(dropdownScope, element, appendTo) {
if (!openScope) {
$document.on('click', closeDropdown);
}
Expand All @@ -18,14 +37,52 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
}

openScope = dropdownScope;

if (!appendTo) {
return;
}

var openedDropdowns = openedContainers.get(appendTo);
if (openedDropdowns) {
var openedScopes = openedDropdowns.map(function(dropdown) {
return dropdown.scope;
});
if (openedScopes.indexOf(dropdownScope) === -1) {
openedContainers.put(appendTo, {
scope: dropdownScope
});
}
} else {
openedContainers.put(appendTo, {
scope: dropdownScope
});
}
};

this.close = function(dropdownScope, element) {
this.close = function(dropdownScope, element, appendTo) {
if (openScope === dropdownScope) {
$document.off('click', closeDropdown);
$document.off('keydown', this.keybindFilter);
openScope = null;
}

if (!appendTo) {
return;
}

var openedDropdowns = openedContainers.get(appendTo);
if (openedDropdowns) {
var dropdownToClose = openedDropdowns.reduce(function(toClose, dropdown) {
if (dropdown.scope === dropdownScope) {
return dropdown;
}

return toClose;
}, {});
if (dropdownToClose) {
openedContainers.remove(appendTo, dropdownToClose);
}
}
};

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

var openContainer = appendTo ? appendTo : $element;
var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass);
var dropdownOpenClass = appendTo ? appendToOpenClass : openClass;
var hasOpenClass = openContainer.hasClass(dropdownOpenClass);
var isOnlyOpen = uibDropdownService.isOnlyOpen($scope, appendTo);

if (hasOpenClass === !isOpen) {
$animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
var toggleClass;
if (appendTo) {
toggleClass = !isOnlyOpen ? 'addClass' : 'removeClass';
} else {
toggleClass = isOpen ? 'addClass' : 'removeClass';
}
$animate[toggleClass](openContainer, dropdownOpenClass).then(function() {
if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
toggleInvoker($scope, { open: !!isOpen });
}
Expand All @@ -270,10 +335,10 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
}

scope.focusToggleElement();
uibDropdownService.open(scope, $element);
uibDropdownService.open(scope, $element, appendTo);
} else {
$document.off('keydown', uibDropdownService.keybindFilter);
uibDropdownService.close(scope, $element);
uibDropdownService.close(scope, $element, appendTo);
if (self.dropdownMenuTemplateUrl) {
if (templateScope) {
templateScope.$destroy();
Expand Down
1 change: 1 addition & 0 deletions src/dropdown/index-nocss.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require('../multiMap');
require('../position/index-nocss.js');
require('./dropdown');

Expand Down
35 changes: 33 additions & 2 deletions src/dropdown/test/dropdown.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ describe('uib-dropdown', function() {
expect(elm1).not.toHaveClass(dropdownConfig.openClass);
expect(elm2).not.toHaveClass(dropdownConfig.openClass);

clickDropdownToggle( elm1 );
clickDropdownToggle(elm1);
expect(elm1).toHaveClass(dropdownConfig.openClass);
expect(elm2).not.toHaveClass(dropdownConfig.openClass);

clickDropdownToggle( elm2 );
clickDropdownToggle(elm2);
expect(elm1).not.toHaveClass(dropdownConfig.openClass);
expect(elm2).toHaveClass(dropdownConfig.openClass);
});
Expand Down Expand Up @@ -303,6 +303,37 @@ describe('uib-dropdown', function() {
});
});

describe('using dropdown-append-to with two dropdowns', function() {
function dropdown() {
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);
}

beforeEach(function() {
$document.find('body').append(angular.element('<div id="dropdown-container"></div>'));

$rootScope.appendTo = $document.find('#dropdown-container');
$rootScope.log = jasmine.createSpy('log');

element = dropdown();
$document.find('body').append(element);
});

afterEach(function() {
// Cleanup the extra elements we appended
$document.find('#dropdown-container').remove();
});

it('should keep the class when toggling from one dropdown to another with the same container', function() {
var container = $document.find('#dropdown-container');

expect(container).not.toHaveClass('uib-dropdown-open');
element.find('.dropdown1 [uib-dropdown-toggle]').click();
expect(container).toHaveClass('uib-dropdown-open');
element.find('.dropdown2 [uib-dropdown-toggle]').click();
expect(container).toHaveClass('uib-dropdown-open');
});
});

describe('using is-open', function() {
describe('with uib-dropdown-toggle', function() {
beforeEach(function() {
Expand Down
1 change: 1 addition & 0 deletions src/modal/index-nocss.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require('../multiMap');
require('../position/index-nocss.js');
require('../stackedMap');
require('../../template/modal/window.html.js');
Expand Down
61 changes: 3 additions & 58 deletions src/modal/modal.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,4 @@
angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
/**
* A helper, internal data structure that stores all references attached to key
*/
.factory('$$multiMap', function() {
return {
createNew: function() {
var map = {};

return {
entries: function() {
return Object.keys(map).map(function(key) {
return {
key: key,
value: map[key]
};
});
},
get: function(key) {
return map[key];
},
hasKey: function(key) {
return !!map[key];
},
keys: function() {
return Object.keys(map);
},
put: function(key, value) {
if (!map[key]) {
map[key] = [];
}

map[key].push(value);
},
remove: function(key, value) {
var values = map[key];

if (!values) {
return;
}

var idx = values.indexOf(value);

if (idx !== -1) {
values.splice(idx, 1);
}

if (!values.length) {
delete map[key];
}
}
};
}
};
})

angular.module('ui.bootstrap.modal', ['ui.bootstrap.multiMap', 'ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
/**
* Pluggable resolve mechanism for the modal resolve resolution
* Supports UI Router's $resolve service
Expand Down Expand Up @@ -569,7 +514,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
ariaHiddenCount = parseInt(sibling.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10);

if (!ariaHiddenCount) {
ariaHiddenCount = elemIsAlreadyHidden ? 1 : 0;
ariaHiddenCount = elemIsAlreadyHidden ? 1 : 0;
}

sibling.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, ariaHiddenCount + 1);
Expand Down Expand Up @@ -607,7 +552,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.p
}
);
}

$modalStack.close = function(modalInstance, result) {
var modalWindow = openedWindows.get(modalInstance);
unhideBackgroundElements();
Expand Down
1 change: 1 addition & 0 deletions src/multiMap/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require('./multiMap.js');
55 changes: 55 additions & 0 deletions src/multiMap/multiMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
angular.module('ui.bootstrap.multiMap', [])
/**
* A helper, internal data structure that stores all references attached to key
*/
.factory('$$multiMap', function() {
return {
createNew: function() {
var map = {};

return {
entries: function() {
return Object.keys(map).map(function(key) {
return {
key: key,
value: map[key]
};
});
},
get: function(key) {
return map[key];
},
hasKey: function(key) {
return !!map[key];
},
keys: function() {
return Object.keys(map);
},
put: function(key, value) {
if (!map[key]) {
map[key] = [];
}

map[key].push(value);
},
remove: function(key, value) {
var values = map[key];

if (!values) {
return;
}

var idx = values.indexOf(value);

if (idx !== -1) {
values.splice(idx, 1);
}

if (!values.length) {
delete map[key];
}
}
};
}
};
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
describe('multi map', function() {
var multiMap;

beforeEach(module('ui.bootstrap.modal'));
beforeEach(module('ui.bootstrap.multiMap'));
beforeEach(inject(function($$multiMap) {
multiMap = $$multiMap.createNew();
}));
Expand Down

0 comments on commit 1653afa

Please sign in to comment.