diff --git a/src/popover/docs/readme.md b/src/popover/docs/readme.md index eb5f53428a..87316ddeee 100644 --- a/src/popover/docs/readme.md +++ b/src/popover/docs/readme.md @@ -1,2 +1,5 @@ A lightweight, extensible directive for fancy popover creation. The popover directive supports multiple placements, optional transition animation, and more. + +Like the Twitter Bootstrap jQuery plugin, the popover **requires** the tooltip +module. diff --git a/src/popover/popover.js b/src/popover/popover.js index d64e729eff..1cee58f851 100644 --- a/src/popover/popover.js +++ b/src/popover/popover.js @@ -3,152 +3,16 @@ * function, placement as a function, inside, support for more triggers than * just mouse enter/leave, html popovers, and selector delegatation. */ -angular.module( 'ui.bootstrap.popover', [] ) +angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) .directive( 'popoverPopup', function () { return { restrict: 'EA', replace: true, - scope: { popoverTitle: '@', popoverContent: '@', placement: '@', animation: '&', isOpen: '&' }, + scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, templateUrl: 'template/popover/popover.html' }; }) -.directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', function ( $compile, $timeout, $parse, $window ) { - - var template = - ''+ - ''; - - return { - scope: true, - link: function ( scope, element, attr ) { - var popover = $compile( template )( scope ), - transitionTimeout; - - attr.$observe( 'popover', function ( val ) { - scope.tt_popover = val; - }); - - attr.$observe( 'popoverTitle', function ( val ) { - scope.tt_title = val; - }); - - attr.$observe( 'popoverPlacement', function ( val ) { - // If no placement was provided, default to 'top'. - scope.tt_placement = val || 'top'; - }); - - attr.$observe( 'popoverAnimation', function ( val ) { - scope.tt_animation = $parse( val ); - }); - - // By default, the popover is not open. - scope.tt_isOpen = false; - - // Calculate the current position and size of the directive element. - function getPosition() { - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: element.prop( 'offsetWidth' ), - height: element.prop( 'offsetHeight' ), - top: boundingClientRect.top + $window.pageYOffset, - left: boundingClientRect.left + $window.pageXOffset - }; - } - - function show() { - var position, - ttWidth, - ttHeight, - ttPosition; - - // If there is a pending remove transition, we must cancel it, lest the - // toolip be mysteriously removed. - if ( transitionTimeout ) { - $timeout.cancel( transitionTimeout ); - } - - // Set the initial positioning. - popover.css({ top: 0, left: 0, display: 'block' }); - - // Now we add it to the DOM because need some info about it. But it's not - // visible yet anyway. - element.after( popover ); - - // Get the position of the directive element. - position = getPosition(); - - // Get the height and width of the popover so we can center it. - ttWidth = popover.prop( 'offsetWidth' ); - ttHeight = popover.prop( 'offsetHeight' ); - - // Calculate the popover's top and left coordinates to center it with - // this directive. - switch ( scope.tt_placement ) { - case 'right': - ttPosition = { - top: (position.top + position.height / 2 - ttHeight / 2) + 'px', - left: (position.left + position.width) + 'px' - }; - break; - case 'bottom': - ttPosition = { - top: (position.top + position.height) + 'px', - left: (position.left + position.width / 2 - ttWidth / 2) + 'px' - }; - break; - case 'left': - ttPosition = { - top: (position.top + position.height / 2 - ttHeight / 2) + 'px', - left: (position.left - ttWidth) + 'px' - }; - break; - default: - ttPosition = { - top: (position.top - ttHeight) + 'px', - left: (position.left + position.width / 2 - ttWidth / 2) + 'px' - }; - break; - } - - // Now set the calculated positioning. - popover.css( ttPosition ); - - // And show the popover. - scope.tt_isOpen = true; - } - - // Hide the popover popup element. - function hide() { - // First things first: we don't show it anymore. - //popover.removeClass( 'in' ); - scope.tt_isOpen = false; - - // And now we remove it from the DOM. However, if we have animation, we - // need to wait for it to expire beforehand. - // FIXME: this is a placeholder for a port of the transitions library. - if ( angular.isDefined( scope.tt_animation ) && scope.tt_animation() ) { - transitionTimeout = $timeout( function () { popover.remove(); }, 500 ); - } else { - popover.remove(); - } - } - - // Register the event listeners. - element.bind( 'click', function() { - if(scope.tt_isOpen){ - scope.$apply( hide ); - } else { - scope.$apply( show ); - } - - }); - } - }; +.directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) { + return $tooltip( 'popover', 'click' ); }]); diff --git a/src/popover/test/popoverSpec.js b/src/popover/test/popoverSpec.js index f98d292b21..633aeb8b2c 100644 --- a/src/popover/test/popoverSpec.js +++ b/src/popover/test/popoverSpec.js @@ -81,7 +81,7 @@ describe('popover', function() { tt.trigger( 'click' ); expect( tt.text() ).toBe( scope.items[0].name ); - expect( tt.scope().tt_popover ).toBe( scope.items[0].popover ); + expect( tt.scope().tt_content ).toBe( scope.items[0].popover ); tt.trigger( 'click' ); })); @@ -89,12 +89,12 @@ describe('popover', function() { it('should only have an isolate scope on the popup', inject( function ( $compile ) { var ttScope; - scope.popoverMsg = "popover Text"; - scope.popoverTitle = "popover Text"; + scope.popoverContent = "Popover Content"; + scope.popoverTitle = "Popover Title"; scope.alt = "Alt Message"; elmBody = $compile( angular.element( - '
Selector Text
' + '
Selector Text
' ) )( scope ); $compile( elmBody )( scope ); @@ -107,8 +107,8 @@ describe('popover', function() { ttScope = angular.element( elmBody.children()[1] ).scope(); expect( ttScope.placement ).toBe( 'top' ); - expect( ttScope.popoverTitle ).toBe( scope.popoverTitle ); - expect( ttScope.popoverContent ).toBe( scope.popoverMsg ); + expect( ttScope.title ).toBe( scope.popoverTitle ); + expect( ttScope.content ).toBe( scope.popoverContent ); elm.trigger( 'click' ); })); diff --git a/src/tooltip/docs/demo.html b/src/tooltip/docs/demo.html index 815c540e2e..954c70e57e 100644 --- a/src/tooltip/docs/demo.html +++ b/src/tooltip/docs/demo.html @@ -12,7 +12,7 @@ nunc sed velit dignissim sodales ut eu sem integer vitae. Turpis egestas bottom pharetra convallis posuere morbi leo urna, - fading + fading at elementum eu, facilisis sed odio morbi quis commodo odio. In cursus turpis massa tincidunt dui ut.

diff --git a/src/tooltip/test/tooltip.spec.js b/src/tooltip/test/tooltip.spec.js index 1eb537a7e8..b9849df338 100644 --- a/src/tooltip/test/tooltip.spec.js +++ b/src/tooltip/test/tooltip.spec.js @@ -81,7 +81,7 @@ describe('tooltip', function() { tt.trigger( 'mouseenter' ); expect( tt.text() ).toBe( scope.items[0].name ); - expect( tt.scope().tt_tooltip ).toBe( scope.items[0].tooltip ); + expect( tt.scope().tt_content ).toBe( scope.items[0].tooltip ); tt.trigger( 'mouseleave' ); })); @@ -106,7 +106,7 @@ describe('tooltip', function() { ttScope = angular.element( elmBody.children()[1] ).scope(); expect( ttScope.placement ).toBe( 'top' ); - expect( ttScope.tooltipTitle ).toBe( scope.tooltipMsg ); + expect( ttScope.content ).toBe( scope.tooltipMsg ); elm.trigger( 'mouseleave' ); })); diff --git a/src/tooltip/tooltip.js b/src/tooltip/tooltip.js index 7af22a45b3..15617274a8 100644 --- a/src/tooltip/tooltip.js +++ b/src/tooltip/tooltip.js @@ -1,52 +1,57 @@ /** * The following features are still outstanding: popup delay, animation as a * function, placement as a function, inside, support for more triggers than - * just mouse enter/leave, html tooltips, and selector delegatation. + * just mouse enter/leave, html tooltips, and selector delegation. */ angular.module( 'ui.bootstrap.tooltip', [] ) -.directive( 'tooltipPopup', function () { - return { - restrict: 'EA', - replace: true, - scope: { tooltipTitle: '@', placement: '@', animation: '&', isOpen: '&' }, - templateUrl: 'template/tooltip/tooltip-popup.html' + +/** + * The $tooltip service creates tooltip- and popover-like directives as well as + * houses global options for them. + */ +.provider( '$tooltip', function () { + // The default options tooltip and popover. + var defaultOptions = { + placement: 'top', + animation: true }; -}) -.directive( 'tooltip', [ '$compile', '$timeout', '$parse', '$window', function ( $compile, $timeout, $parse, $window) { - - var template = - ''+ - ''; + + // The options specified to the provider globally. + var globalOptions = {}; - return { - scope: true, - link: function ( scope, element, attr ) { - var tooltip = $compile( template )( scope ), - transitionTimeout; - - attr.$observe( 'tooltip', function ( val ) { - scope.tt_tooltip = val; - }); - - attr.$observe( 'tooltipPlacement', function ( val ) { - // If no placement was provided, default to 'top'. - scope.tt_placement = val || 'top'; - }); - - attr.$observe( 'tooltipAnimation', function ( val ) { - scope.tt_animation = $parse( val ); - }); - - // By default, the tooltip is not open. - scope.tt_isOpen = false; - + /** + * The `options({})` allows global configuration of all dialogs in the + * application. + * + * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { + * // place tooltips left instead of top by default + * $tooltipProvider.options( { placement: 'left' } ); + * }); + */ + this.options = function( value ) { + angular.extend( globalOptions, value ); + }; + + /** + * Returns the actual instance of the $tooltip service. + * TODO support multiple triggers + */ + this.$get = [ '$window', '$compile', '$timeout', '$parse', function ( $window, $compile, $timeout, $parse ) { + return function $tooltip ( type, defaultTriggerShow, defaultTriggerHide ) { + var options = angular.extend( {}, defaultOptions, globalOptions ); + + var template = + '<'+ type +'-popup '+ + 'title="{{tt_title}}" '+ + 'content="{{tt_content}}" '+ + 'placement="{{tt_placement}}" '+ + 'animation="tt_animation()" '+ + 'is-open="tt_isOpen"'+ + '>'+ + ''; + // Calculate the current position and size of the directive element. - function getPosition() { + function getPosition( element ) { var boundingClientRect = element[0].getBoundingClientRect(); return { width: element.prop( 'offsetWidth' ), @@ -55,99 +60,153 @@ angular.module( 'ui.bootstrap.tooltip', [] ) left: boundingClientRect.left + $window.pageXOffset }; } - - // Show the tooltip popup element. - function show() { - var position, - ttWidth, - ttHeight, - ttPosition; - - //don't show empty tooltips - if (!scope.tt_tooltip) { - return; - } + + return { + restrict: 'EA', + scope: true, + link: function link ( scope, element, attrs ) { + var tooltip = $compile( template )( scope ), + transitionTimeout; - // If there is a pending remove transition, we must cancel it, lest the - // toolip be mysteriously removed. - if ( transitionTimeout ) { - $timeout.cancel( transitionTimeout ); - } - - // Set the initial positioning. - tooltip.css({ top: 0, left: 0, display: 'block' }); - - // Now we add it to the DOM because need some info about it. But it's not - // visible yet anyway. - element.after( tooltip ); - - // Get the position of the directive element. - position = getPosition(); - - // Get the height and width of the tooltip so we can center it. - ttWidth = tooltip.prop( 'offsetWidth' ); - ttHeight = tooltip.prop( 'offsetHeight' ); - - // Calculate the tooltip's top and left coordinates to center it with - // this directive. - switch ( scope.tt_placement ) { - case 'right': - ttPosition = { - top: (position.top + position.height / 2 - ttHeight / 2) + 'px', - left: (position.left + position.width) + 'px' - }; - break; - case 'bottom': - ttPosition = { - top: (position.top + position.height) + 'px', - left: (position.left + position.width / 2 - ttWidth / 2) + 'px' - }; - break; - case 'left': - ttPosition = { - top: (position.top + position.height / 2 - ttHeight / 2) + 'px', - left: (position.left - ttWidth) + 'px' - }; - break; - default: - ttPosition = { - top: (position.top - ttHeight) + 'px', - left: (position.left + position.width / 2 - ttWidth / 2) + 'px' - }; - break; - } - - // Now set the calculated positioning. - tooltip.css( ttPosition ); + attrs.$observe( type, function ( val ) { + scope.tt_content = val; + }); + + attrs.$observe( type+'Title', function ( val ) { + scope.tt_title = val; + }); + + attrs.$observe( type+'Placement', function ( val ) { + scope.tt_placement = angular.isDefined( val ) ? val : options.placement; + }); + + attrs.$observe( type+'Animation', function ( val ) { + scope.tt_animation = angular.isDefined( val ) ? $parse( val ) : function(){ return options.animation; }; + }); + + // By default, the tooltip is not open. + // TODO add ability to start tooltip opened + scope.tt_isOpen = false; - // And show the tooltip. - scope.tt_isOpen = true; - } - - // Hide the tooltip popup element. - function hide() { - // First things first: we don't show it anymore. - //tooltip.removeClass( 'in' ); - scope.tt_isOpen = false; - - // And now we remove it from the DOM. However, if we have animation, we - // need to wait for it to expire beforehand. - // FIXME: this is a placeholder for a port of the transitions library. - if ( angular.isDefined( scope.tt_animation ) && scope.tt_animation() ) { - transitionTimeout = $timeout( function () { tooltip.remove(); }, 500 ); - } else { - tooltip.remove(); + // Show the tooltip popup element. + function show() { + var position, + ttWidth, + ttHeight, + ttPosition; + + // Don't show empty tooltips. + if ( ! scope.tt_content ) { + return; + } + + // If there is a pending remove transition, we must cancel it, lest the + // toolip be mysteriously removed. + if ( transitionTimeout ) { + $timeout.cancel( transitionTimeout ); + } + + // Set the initial positioning. + tooltip.css({ top: 0, left: 0, display: 'block' }); + + // Now we add it to the DOM because need some info about it. But it's not + // visible yet anyway. + element.after( tooltip ); + + // Get the position of the directive element. + position = getPosition( element ); + + // Get the height and width of the tooltip so we can center it. + ttWidth = tooltip.prop( 'offsetWidth' ); + ttHeight = tooltip.prop( 'offsetHeight' ); + + // Calculate the tooltip's top and left coordinates to center it with + // this directive. + switch ( scope.tt_placement ) { + case 'right': + ttPosition = { + top: (position.top + position.height / 2 - ttHeight / 2) + 'px', + left: (position.left + position.width) + 'px' + }; + break; + case 'bottom': + ttPosition = { + top: (position.top + position.height) + 'px', + left: (position.left + position.width / 2 - ttWidth / 2) + 'px' + }; + break; + case 'left': + ttPosition = { + top: (position.top + position.height / 2 - ttHeight / 2) + 'px', + left: (position.left - ttWidth) + 'px' + }; + break; + default: + ttPosition = { + top: (position.top - ttHeight) + 'px', + left: (position.left + position.width / 2 - ttWidth / 2) + 'px' + }; + break; + } + + // Now set the calculated positioning. + tooltip.css( ttPosition ); + + // And show the tooltip. + scope.tt_isOpen = true; + } + + // Hide the tooltip popup element. + function hide() { + // First things first: we don't show it anymore. + //tooltip.removeClass( 'in' ); + scope.tt_isOpen = false; + + // And now we remove it from the DOM. However, if we have animation, we + // need to wait for it to expire beforehand. + // FIXME: this is a placeholder for a port of the transitions library. + if ( angular.isDefined( scope.tt_animation ) && scope.tt_animation() ) { + transitionTimeout = $timeout( function () { tooltip.remove(); }, 500 ); + } else { + tooltip.remove(); + } + } + + // Register the event listeners. If only one event listener was + // supplied, we use the same event listener for showing and hiding. + // TODO add ability to customize event triggers + if ( ! angular.isDefined( defaultTriggerHide ) ) { + element.bind( defaultTriggerShow, function toggleTooltipBind () { + if ( ! scope.tt_isOpen ) { + scope.$apply( show ); + } else { + scope.$apply( hide ); + } + }); + } else { + element.bind( defaultTriggerShow, function showTooltipBind() { + scope.$apply( show ); + }); + element.bind( defaultTriggerHide, function hideTooltipBind() { + scope.$apply( hide ); + }); + } } - } - - // Register the event listeners. - element.bind( 'mouseenter', function() { - scope.$apply( show ); - }); - element.bind( 'mouseleave', function() { - scope.$apply( hide ); - }); - } + }; + }; + }]; +}) + +.directive( 'tooltipPopup', function () { + return { + restrict: 'E', + replace: true, + scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'template/tooltip/tooltip-popup.html' }; +}) + +.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { + return $tooltip( 'tooltip', 'mouseenter', 'mouseleave' ); }]); diff --git a/template/popover/popover.html b/template/popover/popover.html index 1d1104dd2b..5929ee6e6a 100644 --- a/template/popover/popover.html +++ b/template/popover/popover.html @@ -2,7 +2,7 @@
-

-
+

+
diff --git a/template/tooltip/tooltip-popup.html b/template/tooltip/tooltip-popup.html index 77d35de8e2..fd51120774 100644 --- a/template/tooltip/tooltip-popup.html +++ b/template/tooltip/tooltip-popup.html @@ -1,4 +1,4 @@
-
+