Created
May 1, 2013 21:48
-
-
Save thatmarvin/5498659 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.position","ui.bootstrap.tooltip","ui.bootstrap.popover"]); | |
angular.module("ui.bootstrap.tpls", ["template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html"]); | |
angular.module('ui.bootstrap.position', []) | |
/** | |
* A set of utility methods that can be use to retrieve position of DOM elements. | |
* It is meant to be used where we need to absolute-position DOM elements in | |
* relation to other, existing elements (this is the case for tooltips, popovers, | |
* typeahead suggestions etc.). | |
*/ | |
.factory('$position', ['$document', '$window', function ($document, $window) { | |
function getStyle(el, cssprop) { | |
if (el.currentStyle) { //IE | |
return el.currentStyle[cssprop]; | |
} else if ($window.getComputedStyle) { | |
return $window.getComputedStyle(el)[cssprop]; | |
} | |
// finally try and get inline style | |
return el.style[cssprop]; | |
} | |
/** | |
* Checks if a given element is statically positioned | |
* @param element - raw DOM element | |
*/ | |
function isStaticPositioned(element) { | |
return (getStyle(element, "position") || 'static' ) === 'static'; | |
} | |
/** | |
* returns the closest, non-statically positioned parentOffset of a given element | |
* @param element | |
*/ | |
var parentOffsetEl = function (element) { | |
var docDomEl = $document[0]; | |
var offsetParent = element.offsetParent || docDomEl; | |
while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { | |
offsetParent = offsetParent.offsetParent; | |
} | |
return offsetParent || docDomEl; | |
}; | |
return { | |
/** | |
* Provides read-only equivalent of jQuery's position function: | |
* http://api.jquery.com/position/ | |
*/ | |
position: function (element) { | |
var elBCR = this.offset(element); | |
var offsetParentBCR = { top: 0, left: 0 }; | |
var offsetParentEl = parentOffsetEl(element[0]); | |
if (offsetParentEl != $document[0]) { | |
offsetParentBCR = this.offset(angular.element(offsetParentEl)); | |
offsetParentBCR.top += offsetParentEl.clientTop; | |
offsetParentBCR.left += offsetParentEl.clientLeft; | |
} | |
return { | |
width: element.prop('offsetWidth'), | |
height: element.prop('offsetHeight'), | |
top: elBCR.top - offsetParentBCR.top, | |
left: elBCR.left - offsetParentBCR.left | |
}; | |
}, | |
/** | |
* Provides read-only equivalent of jQuery's offset function: | |
* http://api.jquery.com/offset/ | |
*/ | |
offset: function (element) { | |
var boundingClientRect = element[0].getBoundingClientRect(); | |
return { | |
width: element.prop('offsetWidth'), | |
height: element.prop('offsetHeight'), | |
top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop), | |
left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft) | |
}; | |
} | |
}; | |
}]); | |
/** | |
* The following features are still outstanding: animation as a | |
* function, placement as a function, inside, support for more triggers than | |
* just mouse enter/leave, html tooltips, and selector delegation. | |
*/ | |
angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] ) | |
/** | |
* 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, | |
popupDelay: 0 | |
}; | |
// Default hide triggers for each show trigger | |
var triggerMap = { | |
'mouseenter': 'mouseleave', | |
'click': 'click', | |
'focus': 'blur', | |
'groupenter': 'groupleave' | |
}; | |
// The options specified to the provider globally. | |
var globalOptions = {}; | |
/** | |
* `options({})` allows global configuration of all tooltips 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 ); | |
}; | |
/** | |
* This is a helper function for translating camel-case to snake-case. | |
*/ | |
function snake_case(name){ | |
var regexp = /[A-Z]/g; | |
var separator = '-'; | |
return name.replace(regexp, function(letter, pos) { | |
return (pos ? separator : '') + letter.toLowerCase(); | |
}); | |
} | |
/** | |
* Returns the actual instance of the $tooltip service. | |
* TODO support multiple triggers | |
*/ | |
this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', function ( $window, $compile, $timeout, $parse, $document, $position ) { | |
return function $tooltip ( type, prefix, defaultTriggerShow ) { | |
var options = angular.extend( {}, defaultOptions, globalOptions ); | |
/** | |
* Returns an object of show and hide triggers. | |
* | |
* If a trigger is supplied, | |
* it is used to show the tooltip; otherwise, it will use the `trigger` | |
* option passed to the `$tooltipProvider.options` method; else it will | |
* default to the trigger supplied to this directive factory. | |
* | |
* The hide trigger is based on the show trigger. If the `trigger` option | |
* was passed to the `$tooltipProvider.options` method, it will use the | |
* mapped trigger from `triggerMap` or the passed trigger if the map is | |
* undefined; otherwise, it uses the `triggerMap` value of the show | |
* trigger; else it will just use the show trigger. | |
*/ | |
function setTriggers ( trigger ) { | |
var show, hide; | |
show = trigger || options.trigger || defaultTriggerShow; | |
if ( angular.isDefined ( options.trigger ) ) { | |
hide = triggerMap[options.trigger] || show; | |
} else { | |
hide = triggerMap[show] || show; | |
} | |
return { | |
show: show, | |
hide: hide | |
}; | |
} | |
var directiveName = snake_case( type ); | |
var triggers = setTriggers( undefined ); | |
var template = | |
'<'+ directiveName +'-popup '+ | |
'title="{{tt_title}}" '+ | |
'content="{{tt_content}}" '+ | |
'placement="{{tt_placement}}" '+ | |
'animation="tt_animation()" '+ | |
'is-open="tt_isOpen"'+ | |
'>'+ | |
'</'+ directiveName +'-popup>'; | |
return { | |
restrict: 'EA', | |
scope: true, | |
link: function link ( scope, element, attrs ) { | |
var tooltip = $compile( template )( scope ); | |
var transitionTimeout; | |
var popupTimeout; | |
var $body; | |
// By default, the tooltip is not open. | |
// TODO add ability to start tooltip opened | |
scope.tt_isOpen = false; | |
function toggleTooltipBind () { | |
if ( ! scope.tt_isOpen ) { | |
showTooltipBind(); | |
} else { | |
hideTooltipBind(); | |
} | |
} | |
// Show the tooltip with delay if specified, otherwise show it immediately | |
function showTooltipBind() { | |
if ( scope.tt_popupDelay ) { | |
popupTimeout = $timeout( show, scope.tt_popupDelay ); | |
} else { | |
scope.$apply( show ); | |
} | |
} | |
function hideTooltipBind () { | |
scope.$apply(function () { | |
hide(); | |
}); | |
} | |
// 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 | |
// tooltip 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. | |
if ( options.appendToBody ) { | |
$body = $body || $document.find( 'body' ); | |
$body.append( tooltip ); | |
} else { | |
element.after( tooltip ); | |
} | |
// Get the position of the directive element. | |
position = $position.position( 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. | |
scope.tt_isOpen = false; | |
//if tooltip is going to be shown after delay, we must cancel this | |
$timeout.cancel( popupTimeout ); | |
// 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(); | |
} | |
} | |
/** | |
* Observe the relevant attributes. | |
*/ | |
attrs.$observe( type, function ( val ) { | |
scope.tt_content = val; | |
}); | |
attrs.$observe( prefix+'Title', function ( val ) { | |
scope.tt_title = val; | |
}); | |
attrs.$observe( prefix+'Placement', function ( val ) { | |
scope.tt_placement = angular.isDefined( val ) ? val : options.placement; | |
}); | |
attrs.$observe( prefix+'Animation', function ( val ) { | |
scope.tt_animation = angular.isDefined( val ) ? $parse( val ) : function(){ return options.animation; }; | |
}); | |
attrs.$observe( prefix+'PopupDelay', function ( val ) { | |
var delay = parseInt( val, 10 ); | |
scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay; | |
}); | |
attrs.$observe( prefix+'Trigger', function ( val ) { | |
element.unbind( triggers.show ); | |
element.unbind( triggers.hide ); | |
triggers = setTriggers( val ); | |
if ( triggers.show === triggers.hide ) { | |
element.bind( triggers.show, toggleTooltipBind ); | |
} else { | |
element.bind( triggers.show, showTooltipBind ); | |
element.bind( triggers.hide, hideTooltipBind ); | |
} | |
}); | |
} | |
}; | |
}; | |
}]; | |
}) | |
.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', 'tooltip', 'mouseenter' ); | |
}]) | |
.directive( 'tooltipHtmlUnsafePopup', function () { | |
return { | |
restrict: 'E', | |
replace: true, | |
scope: { content: '@', placement: '@', animation: '&', isOpen: '&' }, | |
templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' | |
}; | |
}) | |
.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) { | |
return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); | |
}]) | |
; | |
/** | |
* 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 popovers, and selector delegatation. | |
*/ | |
angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) | |
.directive( 'popoverPopup', function () { | |
return { | |
restrict: 'EA', | |
replace: true, | |
scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, | |
templateUrl: 'template/popover/popover.html' | |
}; | |
}) | |
.directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) { | |
return $tooltip( 'popover', 'popover', 'click' ); | |
}]); | |
angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache){ | |
$templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html", | |
"<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">" + | |
" <div class=\"tooltip-arrow\"></div>" + | |
" <div class=\"tooltip-inner\" ng-bind-html-unsafe=\"content\"></div>" + | |
"</div>" + | |
""); | |
}]); | |
angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache){ | |
$templateCache.put("template/tooltip/tooltip-popup.html", | |
"<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">" + | |
" <div class=\"tooltip-arrow\"></div>" + | |
" <div class=\"tooltip-inner\" ng-bind=\"content\"></div>" + | |
"</div>" + | |
""); | |
}]); | |
angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache){ | |
$templateCache.put("template/popover/popover.html", | |
"<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">" + | |
" <div class=\"arrow\"></div>" + | |
"" + | |
" <div class=\"popover-inner\">" + | |
" <h3 class=\"popover-title\" ng-bind=\"title\" ng-show=\"title\"></h3>" + | |
" <div class=\"popover-content\" ng-bind=\"content\"></div>" + | |
" </div>" + | |
"</div>" + | |
""); | |
}]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment