Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.
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
feat(tooltip): add outsideClick trigger to tooltips/popovers
outsideClick will open a tooltip/popover on click and close that
tooltip/popover when a click happens that is not targeting a
tooltip/popover trigger element or a tooltip/popover itself
  • Loading branch information
SystemDisc committed Nov 10, 2015
commit b48bd7fe8c97fae2e74d8e137ff74c1540492949
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