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

feat(modal): support multiple open classes #4226

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 67 additions & 4 deletions src/modal/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,61 @@ angular.module('ui.bootstrap.modal', [])
};
})

/**
* 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];
}
}
};
}
};
})

/**
* A helper directive for the $modal service. It creates a backdrop element.
*/
Expand Down Expand Up @@ -220,10 +275,12 @@ angular.module('ui.bootstrap.modal', [])
'$animate', '$timeout', '$document', '$compile', '$rootScope',
'$q',
'$injector',
'$$multiMap',
'$$stackedMap',
function($animate , $timeout , $document , $compile , $rootScope ,
$q,
$injector,
$$multiMap,
$$stackedMap) {
var $animateCss = null;

Expand All @@ -235,6 +292,7 @@ angular.module('ui.bootstrap.modal', [])

var backdropDomEl, backdropScope;
var openedWindows = $$stackedMap.createNew();
var openedClasses = $$multiMap.createNew();
var $modalStack = {
NOW_CLOSING_EVENT: 'modal.stack.now-closing'
};
Expand Down Expand Up @@ -264,15 +322,16 @@ angular.module('ui.bootstrap.modal', [])
});

function removeModalWindow(modalInstance, elementToReceiveFocus) {

var body = $document.find('body').eq(0);
var modalWindow = openedWindows.get(modalInstance).value;

//clean up the stack
openedWindows.remove(modalInstance);

removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
body.toggleClass(modalWindow.openedClass || OPENED_MODAL_CLASS, openedWindows.length() > 0);
var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
openedClasses.remove(modalBodyClass, modalInstance);
body.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
});
checkRemoveBackdrop();

Expand Down Expand Up @@ -377,7 +436,8 @@ angular.module('ui.bootstrap.modal', [])
});

$modalStack.open = function(modalInstance, modal) {
var modalOpener = $document[0].activeElement;
var modalOpener = $document[0].activeElement,
modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;

openedWindows.add(modalInstance, {
deferred: modal.deferred,
Expand All @@ -388,6 +448,8 @@ angular.module('ui.bootstrap.modal', [])
openedClass: modal.openedClass
});

openedClasses.put(modalBodyClass, modalInstance);

var body = $document.find('body').eq(0),
currBackdropIndex = backdropIndex();

Expand Down Expand Up @@ -419,7 +481,8 @@ angular.module('ui.bootstrap.modal', [])
openedWindows.top().value.modalDomEl = modalDomEl;
openedWindows.top().value.modalOpener = modalOpener;
body.append(modalDomEl);
body.addClass(modal.openedClass || OPENED_MODAL_CLASS);
body.addClass(modalBodyClass);

$modalStack.clearFocusListCache();
};

Expand Down
46 changes: 46 additions & 0 deletions src/modal/test/modal.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,52 @@ describe('$modal', function () {

expect(body).not.toHaveClass('foo');
});

it('should add multiple custom classes to the body element and remove appropriately', function() {
var modal1 = open({
template: '<div>dummy modal</div>',
openedClass: 'foo'
});

expect(body).toHaveClass('foo');
expect(body).not.toHaveClass('modal-open');

var modal2 = open({
template: '<div>dummy modal</div>',
openedClass: 'bar'
});

expect(body).toHaveClass('foo');
expect(body).toHaveClass('bar');
expect(body).not.toHaveClass('modal-open');

var modal3 = open({
template: '<div>dummy modal</div>',
openedClass: 'foo'
});

expect(body).toHaveClass('foo');
expect(body).toHaveClass('bar');
expect(body).not.toHaveClass('modal-open');

close(modal1);

expect(body).toHaveClass('foo');
expect(body).toHaveClass('bar');
expect(body).not.toHaveClass('modal-open');

close(modal2);

expect(body).toHaveClass('foo');
expect(body).not.toHaveClass('bar');
expect(body).not.toHaveClass('modal-open');

close(modal3);

expect(body).not.toHaveClass('foo');
expect(body).not.toHaveClass('bar');
expect(body).not.toHaveClass('modal-open');
});
});
});

Expand Down
58 changes: 58 additions & 0 deletions src/modal/test/multiMap.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
describe('multi map', function() {
var multiMap;

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

it('should add and remove objects by key', function() {
multiMap.put('foo', 'bar');

expect(multiMap.get('foo')).toEqual(['bar']);

multiMap.put('foo', 'baz');

expect(multiMap.get('foo')).toEqual(['bar', 'baz']);

multiMap.remove('foo', 'bar');

expect(multiMap.get('foo')).toEqual(['baz']);

multiMap.remove('foo', 'baz');

expect(multiMap.hasKey('foo')).toBe(false);
});

it('should support getting the keys', function() {
multiMap.put('foo', 'bar');
multiMap.put('baz', 'boo');

expect(multiMap.keys()).toEqual(['foo', 'baz']);
});

it('should return all entries', function() {
multiMap.put('foo', 'bar');
multiMap.put('foo', 'bar2');
multiMap.put('baz', 'boo');

expect(multiMap.entries()).toEqual([
{
key: 'foo',
value: ['bar', 'bar2']
},
{
key: 'baz',
value: ['boo']
}
]);
});

it('should preserve semantic of an empty key', function() {
expect(multiMap.get('key')).toBeUndefined();
});

it('should respect removal of non-existing elements', function() {
expect(multiMap.remove('foo', 'bar')).toBeUndefined();
});
});