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

hide tooltips and popovers when a user clicks away #4419

Closed
wants to merge 1 commit into from
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
3 changes: 3 additions & 0 deletions src/tooltip/docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ provided hide triggers:

- `mouseenter`: `mouseleave`
- `click`: `click`
- `outsideClick`: `outsideClick`
- `focus`: `blur`
- `none`: ``

The `outsideClick` trigger will cause the tooltip to toggle on click, and hide when anything else is clicked.

For any non-supported value, the trigger will be used to both show and hide the
tooltip. Using the 'none' trigger will disable the internal trigger(s), one can
then use the `tooltip-is-open` attribute exclusively to show and hide the tooltip.
Expand Down
25 changes: 25 additions & 0 deletions src/tooltip/test/tooltip.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,31 @@ describe('tooltip', function() {
elm.trigger('mouseenter');
expect(tooltipScope.isOpen).toBeFalsy();
}));

it('should toggle on click and hide when anything else is clicked when trigger is set to "outsideClick"', inject(function($compile, $document) {
elm = $compile(angular.element(
'<span uib-tooltip="tooltip text" tooltip-trigger="outsideClick">Selector Text</span>'
))(scope);
scope.$apply();
elmScope = elm.scope();
tooltipScope = elmScope.$$childTail;

// start off
expect(tooltipScope.isOpen).toBeFalsy();

// toggle
trigger(elm, 'click');
expect(tooltipScope.isOpen).toBeTruthy();
trigger(elm, 'click');
expect(tooltipScope.isOpen).toBeFalsy();

// click on, outsideClick off
trigger(elm, 'click');
expect(tooltipScope.isOpen).toBeTruthy();
angular.element($document[0].body).trigger('click');
tooltipScope.$digest();
expect(tooltipScope.isOpen).toBeFalsy();
}));
});

describe('with an append-to-body attribute', function() {
Expand Down
35 changes: 32 additions & 3 deletions src/tooltip/tooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s
var triggerMap = {
'mouseenter': 'mouseleave',
'click': 'click',
'outsideClick': 'outsideClick',
'focus': 'blur',
'none': ''
};
Expand Down Expand Up @@ -422,13 +423,37 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s
}
}

// hide tooltips/popovers for outsideClick trigger
function bodyHideTooltipBind(e) {
if (ttScope == null || !ttScope.isOpen || tooltip == null) {
return;
}
// make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
hideTooltipBind();
}
}

var unregisterTriggers = function() {
triggers.show.forEach(function(trigger) {
element.unbind(trigger, showTooltipBind);
if (trigger === 'outsideClick') {
element[0].removeEventListener('click', toggleTooltipBind);
}
else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could be rewritten with out the else

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate? I don't understand how this could be rewritten without the else. element[0].removeEventListener(trigger, showTooltipBind); and element[0].removeEventListener(trigger, toggleTooltipBind); should not be called if the trigger is 'clickaway'

{
element[0].removeEventListener(trigger, showTooltipBind);
element[0].removeEventListener(trigger, toggleTooltipBind);
}
});
triggers.hide.forEach(function(trigger) {
trigger.split(' ').forEach(function(hideTrigger) {
element[0].removeEventListener(hideTrigger, hideTooltipBind);
if (trigger === 'outsideClick') {
$document[0].body.removeEventListener('click', bodyHideTooltipBind);
}
else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also be rewritten with out the else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please elaborate. I don't understand how this could be rewritten without the else. element[0].removeEventListener(hideTrigger, hideTooltipBind); should not be called if the trigger is 'clickaway'

{
element[0].removeEventListener(hideTrigger, hideTooltipBind);
}
});
});
};
Expand All @@ -442,7 +467,11 @@ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.s
if (triggers.show !== 'none') {
triggers.show.forEach(function(trigger, idx) {
// Using raw addEventListener due to jqLite/jQuery bug - #4060
if (trigger === triggers.hide[idx]) {
if (trigger === 'outsideClick') {
element[0].addEventListener('click', toggleTooltipBind);
$document[0].body.addEventListener('click', bodyHideTooltipBind);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be added on $document instead of body? Also, don't we need to close the tooltip on contextmenu event?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I add the event listener on body because click events bubble up, and the topmost element that's clickable (or that we care about) is the body.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to have the listener on $document, for consistency with the rest of the library.

}
else if (trigger === triggers.hide[idx]) {
element[0].addEventListener(trigger, toggleTooltipBind);
} else if (trigger) {
element[0].addEventListener(trigger, showTooltipBind);
Expand Down