I have a form that is wired into angular, using it for validation. I am able to display error messages using ng-show directives like so:
<span ng-show="t3.f.needsAttention(f.fieldName)" ng-cloak>
<span ng-show="f.fieldName.$error.required && !f.fieldName.$viewValue">
This field is required.
</span>
</span>
.. where f
is the form, and t3
comes from a custom directive on the form which detects whether a submission was attempted, and contains functions for checking the validity of fields.
What I am trying to accomplish is to display validation message(s) inside a popover instead. Either bootstrap's native popover, or the popover from UI Bootstrap, I have both loaded. I may also consider AngularStrap if it is easier to do it using that lib.
What I'm struggling with right now is the nature of popovers in general -- they autodisplay based on user events like click, mouseenter, blur, etc. What I want to do is show & hide the popover(s) based on the same functions in the ng-show attributes above. So that when the expression returns false hide it, and when it returns true, show it.
I know bootstrap has the .popover('show') for this, but I'm not supposed to tell angular anything about the dom, so I'm not sure how I would get access to $(element).popover() if doing this in a custom form controller function. Am I missing something?
Update
The solution mentioned in the duplicate vote still only shows the popover on mouseenter. I want to force it to display, as if doing $('#popover_id').popover('show')
.
You can also build your own extended triggers. This will apply to both Tooltip and Popover.
First extend the Tooltip triggers as follows:
// define additional triggers on Tooltip and Popover
app.config(['$tooltipProvider', function($tooltipProvider){
$tooltipProvider.setTriggers({
'show': 'hide'
});
}]);
Then define the trigger on the HTML tag like this:
<div id="RegisterHelp" popover-trigger="show" popover-placement="left" popover="{{ 'Login or register here'}}">
And now you can call hide and show from JavaScript, this is a show in 3 seconds.
$("#RegisterHelp").trigger('show');
//Close the info again
$timeout(function () {
$("#RegisterHelp").trigger('hide');
}, 3000);
As it turns out, it's not very difficult to decorate either the ui-bootstrap tooltip or the popover with a custom directive. This is written in typescript, but the javascript parts of it should be obvious. This single piece of code works to decorate either a tooltip or a popover:
'use strict';
module App.Directives.TooltipToggle {
export interface DirectiveSettings {
directiveName: string;
directive: any[];
directiveConfig?: any[];
}
export function directiveSettings(tooltipOrPopover = 'tooltip'): DirectiveSettings {
var directiveName = tooltipOrPopover;
// events to handle show & hide of the tooltip or popover
var showEvent = 'show-' + directiveName;
var hideEvent = 'hide-' + directiveName;
// set up custom triggers
var directiveConfig = ['$tooltipProvider', ($tooltipProvider: ng.ui.bootstrap.ITooltipProvider): void => {
var trigger = {};
trigger[showEvent] = hideEvent;
$tooltipProvider.setTriggers(trigger);
}];
var directiveFactory = (): any[] => {
return ['$timeout', ($timeout: ng.ITimeoutService): ng.IDirective => {
var d: ng.IDirective = {
name: directiveName,
restrict: 'A',
link: (scope: ng.IScope, element: JQuery, attr: ng.IAttributes) => {
if (angular.isUndefined(attr[directiveName + 'Toggle'])) return;
// set the trigger to the custom show trigger
attr[directiveName + 'Trigger'] = showEvent;
// redraw the popover when responsive UI moves its source
var redrawPromise: ng.IPromise<void>;
$(window).on('resize', (): void => {
if (redrawPromise) $timeout.cancel(redrawPromise);
redrawPromise = $timeout((): void => {
if (!scope['tt_isOpen']) return;
element.triggerHandler(hideEvent);
element.triggerHandler(showEvent);
}, 100);
});
scope.$watch(attr[directiveName + 'Toggle'], (value: boolean): void => {
if (value && !scope['tt_isOpen']) {
// tooltip provider will call scope.$apply, so need to get out of this digest cycle first
$timeout((): void => {
element.triggerHandler(showEvent);
});
}
else if (!value && scope['tt_isOpen']) {
$timeout((): void => {
element.triggerHandler(hideEvent);
});
}
});
}
};
return d;
}];
};
var directive = directiveFactory();
var directiveSettings: DirectiveSettings = {
directiveName: directiveName,
directive: directive,
directiveConfig: directiveConfig,
};
return directiveSettings;
}
}
With this single piece of code, you can set up programmatic hide and show of either a tooltip or popover like so:
var tooltipToggle = App.Directives.TooltipToggle.directiveSettings();
var popoverToggle = App.Directives.TooltipToggle.directiveSettings('popover');
var myModule = angular.module('my-mod', ['ui.bootstrap.popover', 'ui.bootstrap.tpls'])
.directive(tooltipToggle.directiveName, tooltipToggle.directive)
.config(tooltipToggle.directiveConfig)
.directive(popoverToggle.directiveName, popoverToggle.directive)
.config(popoverToggle.directiveConfig);
Usage:
<span tooltip="This field is required."
tooltip-toggle="formName.fieldName.$error.required"
tooltip-animation="false" tooltip-placement="right"></span>
or
<span popover="This field is required."
popover-toggle="formName.fieldName.$error.required"
popover-animation="false" popover-placement="right"></span>
So we are reusing everything else that comes with the ui-bootstrap tooltip or popover, and only implementing the -toggle
attribute. The decorative directive watches that attribute, and fires custom events to show or hide, which are then handled by the ui-bootstrap tooltip provider.
Update:
Since this answer seems to be helping others, here is the code written as javascript (the above typescript more or less compiles to this javascript):
'use strict';
function directiveSettings(tooltipOrPopover) {
if (typeof tooltipOrPopover === "undefined") {
tooltipOrPopover = 'tooltip';
}
var directiveName = tooltipOrPopover;
// events to handle show & hide of the tooltip or popover
var showEvent = 'show-' + directiveName;
var hideEvent = 'hide-' + directiveName;
// set up custom triggers
var directiveConfig = ['$tooltipProvider', function ($tooltipProvider) {
var trigger = {};
trigger[showEvent] = hideEvent;
$tooltipProvider.setTriggers(trigger);
}];
var directiveFactory = function() {
return ['$timeout', function($timeout) {
var d = {
name: directiveName,
restrict: 'A',
link: function(scope, element, attr) {
if (angular.isUndefined(attr[directiveName + 'Toggle']))
return;
// set the trigger to the custom show trigger
attr[directiveName + 'Trigger'] = showEvent;
// redraw the popover when responsive UI moves its source
var redrawPromise;
$(window).on('resize', function() {
if (redrawPromise) $timeout.cancel(redrawPromise);
redrawPromise = $timeout(function() {
if (!scope['tt_isOpen']) return;
element.triggerHandler(hideEvent);
element.triggerHandler(showEvent);
}, 100);
});
scope.$watch(attr[directiveName + 'Toggle'], function(value) {
if (value && !scope['tt_isOpen']) {
// tooltip provider will call scope.$apply, so need to get out of this digest cycle first
$timeout(function() {
element.triggerHandler(showEvent);
});
}
else if (!value && scope['tt_isOpen']) {
$timeout(function() {
element.triggerHandler(hideEvent);
});
}
});
}
};
return d;
}];
};
var directive = directiveFactory();
var directiveSettings = {
directiveName: directiveName,
directive: directive,
directiveConfig: directiveConfig,
};
return directiveSettings;
}
ui.bootstrap
0.13.4 and newer:A new parameter (popover-is-open
) was introduced to control popovers in the official ui.bootstrap
repo. This is how you use it in the latest version:
<a uib-popover="Hello world!" popover-is-open="isOpen" ng-click="isOpen = !isOpen">
Click me to show the popover!
</a>
ui.bootstrap
0.13.3 and older:I just published a small directive that adds more control over popovers on GitHub:
https://github.com/Elijen/angular-popover-toggle
You can use a scope variable to show/hide the popover using popover-toggle="variable"
directive like this:
<span popover="Hello world!" popover-toggle="isOpen">
Popover here
</span>
Here is a demo Plunkr:
http://plnkr.co/edit/QeQqqEJAu1dCuDtSvomD?p=preview
My approach:
The idea being to leave the DOM manipulation to the directives.
I have put together a fiddle that I hope gives a better explain, but you'll find much more sophisticated solutions in UI Bootstrap which you mentioned.
jsfiddle
Markup:
<div ng-repeat="element in elements" class="element">
<!-- Only want to show a popup if the element has an error and is being hovered -->
<div class="popover" ng-show="element.hovered && element.error" ng-style>Popover</div>
<div class="popoverable" ng-mouseEnter="popoverShow(element)" ng-mouseLeave="popoverHide(element)">
{{ element.name }}
</div>
</div>
JS:
function DemoCtrl($scope)
{
$scope.elements = [
{name: 'Element1 (Error)', error: true, hovered: false},
{name: 'Element2 (no error)', error: false, hovered: false},
{name: 'Element3 (Error)', error: true, hovered: false},
{name: 'Element4 (no error)', error: false, hovered: false},
{name: 'Element5 (Error)', error: true, hovered: false},
];
$scope.popoverShow = function(element)
{
element.hovered = true;
}
$scope.popoverHide = function(element)
{
element.hovered = false
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With