popover-trigger="mouseenter outsideClick"
for the uib-popover directive does not seem to work as one would think.
Initially, I thought it meant the following:
- On mouse enter show the popover
- On mouse leave hide the popover
- On click keep popover open in an active state
- On outside click close popover if it is in an active state
Since it does not I needed a manual approach, the following is stated in the documentation.
For any non-supported value, the trigger will be used to both show and hide the popover. Using the 'none' trigger will disable the internal trigger(s), one can then use the popover-is-open attribute exclusively to show and hide the popover.
So I created some HTML like:
<span class="glyphicon glyphicon-info-sign"
ng-class="{'text-primary' : isInfoPopoverClicked}"
ng-click="toggleInfoPopoverClicked()"
ng-mouseenter="enterInfoPopover()"
ng-mouseleave="leaveInfoPopover()"
custom-click-outside="closeInfoPopover()"
uib-popover-template="'info.html'"
popover-trigger="'none'"
popover-is-open="isInfoPopoverOpen()"
popover-placement="auto top"
popover-append-to-body="true" >
</span>
The JS in the controller:
// Toggle popover's clicked active state
$scope.toggleInfoPopoverClicked = function() {
$scope.isInfoPopoverClicked = !$scope.isInfoPopoverClicked;
};
// Close the popover, used for outside click and close action inside the template
$scope.closeInfoPopover = function() {
delete $scope.isInfoPopoverClicked;
};
// On mouse enter, show the popover
$scope.enterInfoPopover = function() {
$scope.isInfoPopoverMouseEnter = true;
};
// On mouse leave, close the popover.
// If clicked active state is false set to undefined.
// This supports when the user clicks the icon to close,
// that mouse enter does not immediately display the popover again.
$scope.leaveInfoPopover = function() {
$scope.isInfoPopoverMouseEnter = false;
if(false === $scope.isInfoPopoverClicked) {
delete $scope.isInfoPopoverClicked;
}
};
// Expression used in the popover-is-open attribute
$scope.isInfoPopoverOpen = function() {
if($scope.isInfoPopoverClicked) {
return true;
} else if(false === $scope.isInfoPopoverClicked){
return false;
}
return $scope.isInfoPopoverMouseEnter;
};
The template for the uib-popover-template
I used:
<div custom-stop-event="click" class="pull-right">
<span ng-click="closeInfoPopover()" class="glyphicon glyphicon-remove"></span>
<section>{{info}}</section>
</div>
Now the trickier part was that this solution required me to create two more directives.
- One to close the popover when clicking outside the element.
- Another to stop the click event fired inside the pop-up. Preventing it from closing the popover.
The custom-click-outside
directive:
angular.module('LSPApp').directive('customClickOutside', ['$document', function ($document) {
return {
restrict: 'A',
scope: {
clickOutside: '&customClickOutside'
},
link: function (scope, element) {
var handler = function (event) {
if (element !== event.target && !element[0].contains(event.target)) {
scope.$applyAsync(function () {
scope.clickOutside();
});
}
};
// Might not work on elements that stop events from bubbling up
$document.on('click', handler);
// Clean up event so it does not keep firing after leaving scope
scope.$on('$destroy', function() {
$document.off('click', handler);
});
}
};
}]);
The custom-stop-event
directive called from the template's HTML:
angular.module('LSPApp').directive('stopEvent', function () {
return {
restrict: 'A',
link: function (scope, element, attr) {
element.on(attr.stopEvent, function (e) {
e.stopPropagation();
});
}
};
});
Hopefully, this helps someone, my final solution had all this encapsulated in it's own directive to promote reuse.