Good way to dynamically open / close a popover (or tooltip) using angular, based on expression?

前端 未结 6 1247
既然无缘
既然无缘 2020-12-01 07:50

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:



        
相关标签:
6条回答
  • 2020-12-01 08:12

    My approach:

    • Track the state of the popover in the model
    • Change this state per element using the appropriate directives.

    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
        }
    
    }
    
    0 讨论(0)
  • 2020-12-01 08:18

    For others coming here, as of the 0.13.4 release, we have added the ability to programmatically open and close popovers via the *-is-open attribute on both tooltips and popovers in the Angular UI Bootstrap library. Thus, there is no longer any reason to have to roll your own code/solution.

    0 讨论(0)
  • 2020-12-01 08:21

    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;
    }
    
    0 讨论(0)
  • 2020-12-01 08:24

    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);
    
    0 讨论(0)
  • 2020-12-01 08:24

    From Michael Stramel's answer, but with a full angularJS solution:

    // define additional triggers on Tooltip and Popover
    app.config(['$tooltipProvider', function($tooltipProvider){
        $tooltipProvider.setTriggers({
           'show': 'hide'
        });
    }])
    

    Now add this directive:

    app.directive('ntTriggerIf', ['$timeout',
    function ($timeout) {
        /*
        Intended use:
            <div nt-trigger-if={ 'triggerName':{{someCodition === SomeValue}},'anotherTriggerName':{{someOtherCodition === someOtherValue}} } ></div>
        */
        return {
    
            restrict: 'A',
            link: function (scope, element, attrs) {
    
                attrs.$observe('ntTriggerIf', function (val) {
                    try {
    
                        var ob_options = JSON.parse(attrs.ntTriggerIf.split("'").join('"') || "");
                    }
                    catch (e) {
                        return
                    }
    
                    $timeout(function () {
                        for (var st_name in ob_options) {
                            var condition = ob_options[st_name];
                            if (condition) {
                                element.trigger(st_name);
                            }
                        }
                    })
    
                })
            }
        }
    }])
    

    Then in your markup:

    <span tooltip-trigger="show" tooltip="Login or register here" nt-trigger-if="{'show':{{ (errorConidtion) }}, 'hide':{{ !(errorConidtion) }} }"></span>
    
    0 讨论(0)
  • 2020-12-01 08:27

    For 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>
    

    For 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

    0 讨论(0)
提交回复
热议问题