From 99b87cc07bda1bdf90f68b39d439700ae2f34ed0 Mon Sep 17 00:00:00 2001 From: RobJacobs Date: Mon, 10 Aug 2015 14:36:16 -0400 Subject: [PATCH] feat(tooltip): expose isOpen property Add support for toggling the isOpen property of the tooltip. Closes #4179 Closes #2148 Fixes #590 --- src/popover/docs/readme.md | 3 ++ src/popover/test/popover.spec.js | 31 ++++++++++++++++++++ src/tooltip/docs/readme.md | 7 ++++- src/tooltip/test/tooltip.spec.js | 43 ++++++++++++++++++++++++++++ src/tooltip/tooltip.js | 49 +++++++++++++++++++++++--------- 5 files changed, 119 insertions(+), 14 deletions(-) diff --git a/src/popover/docs/readme.md b/src/popover/docs/readme.md index be398d6c4b..03a52f6108 100644 --- a/src/popover/docs/readme.md +++ b/src/popover/docs/readme.md @@ -26,6 +26,9 @@ will display: `tooltip` directive for supported values. - `popover-append-to-body`: Should the tooltip be appended to `$body` instead of the parent element? +- `popover-is-open` + _(Default: false)_: + Whether to show the popover. The popover directives require the `$position` service. diff --git a/src/popover/test/popover.spec.js b/src/popover/test/popover.spec.js index bc7f59167e..17e684bfa1 100644 --- a/src/popover/test/popover.spec.js +++ b/src/popover/test/popover.spec.js @@ -137,6 +137,37 @@ describe('popover', function() { })); }); + + describe( 'is-open', function() { + beforeEach(inject(function ($compile) { + scope.isOpen = false; + elmBody = angular.element( + '
Trigger here
' + ); + $compile(elmBody)(scope); + scope.$digest(); + elm = elmBody.find('span'); + elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; + })); + + it( 'should show and hide with the controller value', function() { + expect(tooltipScope.isOpen).toBe(false); + elmScope.isOpen = true; + elmScope.$digest(); + expect(tooltipScope.isOpen).toBe(true); + elmScope.isOpen = false; + elmScope.$digest(); + expect(tooltipScope.isOpen).toBe(false); + }); + + it( 'should update the controller value', function() { + elm.trigger('click'); + expect(elmScope.isOpen).toBe(true); + elm.trigger('click'); + expect(elmScope.isOpen).toBe(false); + }); + }); }); diff --git a/src/tooltip/docs/readme.md b/src/tooltip/docs/readme.md index 4c60b305ea..b0ae385518 100644 --- a/src/tooltip/docs/readme.md +++ b/src/tooltip/docs/readme.md @@ -27,6 +27,9 @@ will display: - `tooltip-append-to-body`: Should the tooltip be appended to `$body` instead of the parent element? - `tooltip-class`: Custom class to be applied to the tooltip. +- `tooltip-is-open` + _(Default: false)_: + Whether to show the tooltip. The tooltip directives require the `$position` service. @@ -38,9 +41,11 @@ provided hide triggers: - `mouseenter`: `mouseleave` - `click`: `click` - `focus`: `blur` +- `none`: `` For any non-supported value, the trigger will be used to both show and hide the -tooltip. +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. **$tooltipProvider** diff --git a/src/tooltip/test/tooltip.spec.js b/src/tooltip/test/tooltip.spec.js index 578b95ac6b..46d80688e7 100644 --- a/src/tooltip/test/tooltip.spec.js +++ b/src/tooltip/test/tooltip.spec.js @@ -307,6 +307,35 @@ describe('tooltip', function() { })); }); + + describe( 'with an is-open attribute', function() { + beforeEach(inject(function ($compile) { + scope.isOpen = false; + elm = $compile(angular.element( + 'Selector Text' + ))(scope); + elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; + scope.$digest(); + })); + + it( 'should show and hide with the controller value', function() { + expect(tooltipScope.isOpen).toBe(false); + elmScope.isOpen = true; + elmScope.$digest(); + expect(tooltipScope.isOpen).toBe(true); + elmScope.isOpen = false; + elmScope.$digest(); + expect(tooltipScope.isOpen).toBe(false); + }); + + it( 'should update the controller value', function() { + elm.trigger('mouseenter'); + expect(elmScope.isOpen).toBe(true); + elm.trigger('mouseleave'); + expect(elmScope.isOpen).toBe(false); + }); + }); describe( 'with a trigger attribute', function() { var scope, elmBody, elm, elmScope; @@ -398,6 +427,20 @@ describe('tooltip', function() { elm.trigger('fakeTriggerAttr'); expect( tooltipScope.isOpen ).toBeFalsy(); })); + + it( 'should not show when trigger is set to "none"', inject( function( $compile ) { + elmBody = angular.element( + '
' + ); + $compile(elmBody)(scope); + scope.$apply(); + elm = elmBody.find('input'); + elmScope = elm.scope(); + tooltipScope = elmScope.$$childTail; + expect( tooltipScope.isOpen ).toBeFalsy(); + elm.trigger('mouseenter'); + expect( tooltipScope.isOpen ).toBeFalsy(); + })); }); describe( 'with an append-to-body attribute', function() { diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index fce1018f49..0cf9a14b90 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -22,7 +22,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap var triggerMap = { 'mouseenter': 'mouseleave', 'click': 'click', - 'focus': 'blur' + 'focus': 'blur', + 'none': '' }; // The options specified to the provider globally. @@ -65,7 +66,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap * Returns the actual instance of the $tooltip service. * TODO support multiple triggers */ - this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', '$rootScope', function ( $window, $compile, $timeout, $document, $position, $interpolate, $rootScope ) { + this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', '$rootScope', '$parse', function ( $window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse ) { return function $tooltip ( type, prefix, defaultTriggerShow, options ) { options = angular.extend( {}, defaultOptions, globalOptions, options ); @@ -127,6 +128,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); var ttScope = scope.$new(true); var repositionScheduled = false; + var isOpenExp = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false; var positionTooltip = function () { if (!tooltip) { return; } @@ -211,7 +213,13 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap // And show the tooltip. ttScope.isOpen = true; - ttScope.$apply(); // digest required as $apply is not called + if (isOpenExp) { + isOpenExp.assign(ttScope.origScope, ttScope.isOpen); + } + + if (!$rootScope.$$phase) { + ttScope.$apply(); // digest required as $apply is not called + } // Return positioning function as promise callback for correct // positioning after draw. @@ -222,7 +230,10 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap function hide() { // First things first: we don't show it anymore. ttScope.isOpen = false; - + if (isOpenExp) { + isOpenExp.assign(ttScope.origScope, ttScope.isOpen); + } + //if tooltip is going to be shown after delay, we must cancel this $timeout.cancel( popupTimeout ); popupTimeout = null; @@ -265,7 +276,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap repositionScheduled = true; tooltipLinkedScope.$$postDigest(function() { repositionScheduled = false; - positionTooltipAsync(); + if (ttScope.isOpen) { + positionTooltipAsync(); + } }); } }); @@ -333,6 +346,14 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap }, 0, false); } }); + + if (isOpenExp) { + scope.$watch(isOpenExp, function(val) { + if (val !== ttScope.isOpen) { + toggleTooltipBind(); + } + }); + } function prepPopupClass() { ttScope.popupClass = attrs[prefix + 'Class']; @@ -364,14 +385,16 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap triggers = getTriggers( val ); - triggers.show.forEach(function(trigger, idx) { - if (trigger === triggers.hide[idx]) { - element.bind(trigger, toggleTooltipBind); - } else if (trigger) { - element.bind(trigger, showTooltipBind); - element.bind(triggers.hide[idx], hideTooltipBind); - } - }); + if (triggers.show !== 'none') { + triggers.show.forEach(function(trigger, idx) { + if (trigger === triggers.hide[idx]) { + element.bind(trigger, toggleTooltipBind); + } else if (trigger) { + element.bind(trigger, showTooltipBind); + element.bind(triggers.hide[idx], hideTooltipBind); + } + }); + } } prepTriggers();