541 lines
18 KiB
JavaScript
541 lines
18 KiB
JavaScript
|
/*
|
||
|
* angular-ui-bootstrap
|
||
|
* http://angular-ui.github.io/bootstrap/
|
||
|
|
||
|
* Version: 0.12.1 - 2015-02-20
|
||
|
* License: MIT
|
||
|
*/
|
||
|
//angular.module("ui.bootstrap", ["ui.bootstrap.tooltip","ui.bootstrap.position","ui.bootstrap.bindHtml"]);
|
||
|
/**
|
||
|
* 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', 'ui.bootstrap.bindHtml' ] )
|
||
|
angular.module('View.Directive')
|
||
|
|
||
|
/**
|
||
|
* 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'
|
||
|
};
|
||
|
|
||
|
// 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 allows you to extend the set of trigger mappings available. E.g.:
|
||
|
*
|
||
|
* $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
|
||
|
*/
|
||
|
this.setTriggers = function setTriggers ( triggers ) {
|
||
|
angular.extend( triggerMap, triggers );
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
* TO DO support multiple triggers
|
||
|
*/
|
||
|
this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $document, $position, $interpolate ) {
|
||
|
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 getTriggers ( trigger ) {
|
||
|
var show = trigger || options.trigger || defaultTriggerShow;
|
||
|
var hide = triggerMap[show] || show;
|
||
|
return {
|
||
|
show: show,
|
||
|
hide: hide
|
||
|
};
|
||
|
}
|
||
|
|
||
|
var directiveName = snake_case( type );
|
||
|
|
||
|
var startSym = $interpolate.startSymbol();
|
||
|
var endSym = $interpolate.endSymbol();
|
||
|
var template =
|
||
|
'<div '+ directiveName +'-popup '+
|
||
|
'title="'+startSym+'title'+endSym+'" '+
|
||
|
'content="'+startSym+'content'+endSym+'" '+
|
||
|
'placement="'+startSym+'placement'+endSym+'" '+
|
||
|
'animation="animation" '+
|
||
|
'is-open="isOpen"'+
|
||
|
'>'+
|
||
|
'</div>';
|
||
|
|
||
|
return {
|
||
|
restrict: 'EA',
|
||
|
compile: function (tElem, tAttrs) {
|
||
|
var tooltipLinker = $compile( template );
|
||
|
|
||
|
return function link ( scope, element, attrs ) {
|
||
|
var tooltip;
|
||
|
var tooltipLinkedScope;
|
||
|
var transitionTimeout;
|
||
|
var popupTimeout;
|
||
|
var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
|
||
|
var triggers = getTriggers( undefined );
|
||
|
var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);
|
||
|
var ttScope = scope.$new(true);
|
||
|
|
||
|
var positionTooltip = function () {
|
||
|
|
||
|
var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
|
||
|
ttPosition.top += 'px';
|
||
|
ttPosition.left += 'px';
|
||
|
|
||
|
// Now set the calculated positioning.
|
||
|
tooltip.css( ttPosition );
|
||
|
};
|
||
|
|
||
|
// By default, the tooltip is not open.
|
||
|
// TO DO add ability to start tooltip opened
|
||
|
ttScope.isOpen = false;
|
||
|
|
||
|
function toggleTooltipBind () {
|
||
|
if ( ! ttScope.isOpen ) {
|
||
|
showTooltipBind();
|
||
|
} else {
|
||
|
hideTooltipBind();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Show the tooltip with delay if specified, otherwise show it immediately
|
||
|
function showTooltipBind() {
|
||
|
if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
prepareTooltip();
|
||
|
|
||
|
if ( ttScope.popupDelay ) {
|
||
|
// Do nothing if the tooltip was already scheduled to pop-up.
|
||
|
// This happens if show is triggered multiple times before any hide is triggered.
|
||
|
if (!popupTimeout) {
|
||
|
popupTimeout = $timeout( show, ttScope.popupDelay, false );
|
||
|
popupTimeout.then(function(reposition){reposition();});
|
||
|
}
|
||
|
} else {
|
||
|
show()();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function hideTooltipBind () {
|
||
|
scope.$apply(function () {
|
||
|
hide();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Show the tooltip popup element.
|
||
|
function show() {
|
||
|
|
||
|
popupTimeout = null;
|
||
|
|
||
|
// If there is a pending remove transition, we must cancel it, lest the
|
||
|
// tooltip be mysteriously removed.
|
||
|
if ( transitionTimeout ) {
|
||
|
$timeout.cancel( transitionTimeout );
|
||
|
transitionTimeout = null;
|
||
|
}
|
||
|
|
||
|
// Don't show empty tooltips.
|
||
|
if ( ! ttScope.content ) {
|
||
|
return angular.noop;
|
||
|
}
|
||
|
|
||
|
createTooltip();
|
||
|
|
||
|
// Set the initial positioning.
|
||
|
tooltip.css({ top: 0, left: 0, display: 'block' });
|
||
|
ttScope.$digest();
|
||
|
|
||
|
positionTooltip();
|
||
|
|
||
|
// And show the tooltip.
|
||
|
ttScope.isOpen = true;
|
||
|
ttScope.$digest(); // digest required as $apply is not called
|
||
|
|
||
|
// Return positioning function as promise callback for correct
|
||
|
// positioning after draw.
|
||
|
return positionTooltip;
|
||
|
}
|
||
|
|
||
|
// Hide the tooltip popup element.
|
||
|
function hide() {
|
||
|
// First things first: we don't show it anymore.
|
||
|
ttScope.isOpen = false;
|
||
|
|
||
|
//if tooltip is going to be shown after delay, we must cancel this
|
||
|
$timeout.cancel( popupTimeout );
|
||
|
popupTimeout = null;
|
||
|
|
||
|
// 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 ( ttScope.animation ) {
|
||
|
if (!transitionTimeout) {
|
||
|
transitionTimeout = $timeout(removeTooltip, 500);
|
||
|
}
|
||
|
} else {
|
||
|
removeTooltip();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function createTooltip() {
|
||
|
// There can only be one tooltip element per directive shown at once.
|
||
|
if (tooltip) {
|
||
|
removeTooltip();
|
||
|
}
|
||
|
tooltipLinkedScope = ttScope.$new();
|
||
|
tooltip = tooltipLinker(tooltipLinkedScope, function (tooltip) {
|
||
|
if ( appendToBody ) {
|
||
|
$document.find( 'body' ).append( tooltip );
|
||
|
} else {
|
||
|
element.after( tooltip );
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function removeTooltip() {
|
||
|
transitionTimeout = null;
|
||
|
if (tooltip) {
|
||
|
tooltip.remove();
|
||
|
tooltip = null;
|
||
|
}
|
||
|
if (tooltipLinkedScope) {
|
||
|
tooltipLinkedScope.$destroy();
|
||
|
tooltipLinkedScope = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function prepareTooltip() {
|
||
|
prepPlacement();
|
||
|
prepPopupDelay();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Observe the relevant attributes.
|
||
|
*/
|
||
|
attrs.$observe( type, function ( val ) {
|
||
|
ttScope.content = val;
|
||
|
|
||
|
if (!val && ttScope.isOpen ) {
|
||
|
hide();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
attrs.$observe( prefix+'Title', function ( val ) {
|
||
|
ttScope.title = val;
|
||
|
});
|
||
|
|
||
|
function prepPlacement() {
|
||
|
var val = attrs[ prefix + 'Placement' ];
|
||
|
ttScope.placement = angular.isDefined( val ) ? val : options.placement;
|
||
|
}
|
||
|
|
||
|
function prepPopupDelay() {
|
||
|
var val = attrs[ prefix + 'PopupDelay' ];
|
||
|
var delay = parseInt( val, 10 );
|
||
|
ttScope.popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
|
||
|
}
|
||
|
|
||
|
var unregisterTriggers = function () {
|
||
|
element.unbind(triggers.show, showTooltipBind);
|
||
|
element.unbind(triggers.hide, hideTooltipBind);
|
||
|
};
|
||
|
|
||
|
function prepTriggers() {
|
||
|
var val = attrs[ prefix + 'Trigger' ];
|
||
|
unregisterTriggers();
|
||
|
|
||
|
triggers = getTriggers( val );
|
||
|
|
||
|
if ( triggers.show === triggers.hide ) {
|
||
|
element.bind( triggers.show, toggleTooltipBind );
|
||
|
} else {
|
||
|
element.bind( triggers.show, showTooltipBind );
|
||
|
element.bind( triggers.hide, hideTooltipBind );
|
||
|
}
|
||
|
}
|
||
|
prepTriggers();
|
||
|
|
||
|
var animation = scope.$eval(attrs[prefix + 'Animation']);
|
||
|
ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
|
||
|
|
||
|
var appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']);
|
||
|
appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
|
||
|
|
||
|
// if a tooltip is attached to <body> we need to remove it on
|
||
|
// location change as its parent scope will probably not be destroyed
|
||
|
// by the change.
|
||
|
if ( appendToBody ) {
|
||
|
scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
|
||
|
if ( ttScope.isOpen ) {
|
||
|
hide();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Make sure tooltip is destroyed and removed.
|
||
|
scope.$on('$destroy', function onDestroyTooltip() {
|
||
|
$timeout.cancel( transitionTimeout );
|
||
|
$timeout.cancel( popupTimeout );
|
||
|
unregisterTriggers();
|
||
|
removeTooltip();
|
||
|
ttScope = null;
|
||
|
});
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
}];
|
||
|
})
|
||
|
|
||
|
.directive( 'tooltipPopup', function () {
|
||
|
return {
|
||
|
restrict: 'EA',
|
||
|
replace: true,
|
||
|
scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
|
||
|
template: '<div class="bootstrap-tooltip {{placement}}" ng-class="{ in: isOpen(), fade: animation() }">' +
|
||
|
'<div class="tooltip-arrow"></div>'+
|
||
|
'<div class="tooltip-inner" ng-bind="content"></div>'+
|
||
|
'</div>'
|
||
|
};
|
||
|
})
|
||
|
|
||
|
.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
|
||
|
return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
|
||
|
}])
|
||
|
|
||
|
.directive( 'tooltipHtmlUnsafePopup', function () {
|
||
|
return {
|
||
|
restrict: 'EA',
|
||
|
replace: true,
|
||
|
scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
|
||
|
template: '<div class="bootstrap-tooltip {{placement}}" ng-class="{ in: isOpen(), fade: animation() }">'+
|
||
|
'<div class="tooltip-arrow"></div>'+
|
||
|
'<div class="tooltip-inner" bind-html-unsafe="content"></div>'+
|
||
|
'</div>'
|
||
|
};
|
||
|
})
|
||
|
|
||
|
.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
|
||
|
return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
|
||
|
}])
|
||
|
// ;
|
||
|
|
||
|
// 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 - offsetParentEl.scrollTop;
|
||
|
offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
|
||
|
}
|
||
|
|
||
|
var boundingClientRect = element[0].getBoundingClientRect();
|
||
|
return {
|
||
|
width: boundingClientRect.width || element.prop('offsetWidth'),
|
||
|
height: boundingClientRect.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: boundingClientRect.width || element.prop('offsetWidth'),
|
||
|
height: boundingClientRect.height || element.prop('offsetHeight'),
|
||
|
top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
|
||
|
left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
|
||
|
};
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Provides coordinates for the targetEl in relation to hostEl
|
||
|
*/
|
||
|
positionElements: function (hostEl, targetEl, positionStr, appendToBody) {
|
||
|
|
||
|
var positionStrParts = positionStr.split('-');
|
||
|
var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center';
|
||
|
|
||
|
var hostElPos,
|
||
|
targetElWidth,
|
||
|
targetElHeight,
|
||
|
targetElPos;
|
||
|
|
||
|
hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl);
|
||
|
|
||
|
targetElWidth = targetEl.prop('offsetWidth');
|
||
|
targetElHeight = targetEl.prop('offsetHeight');
|
||
|
|
||
|
var shiftWidth = {
|
||
|
center: function () {
|
||
|
return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
|
||
|
},
|
||
|
left: function () {
|
||
|
return hostElPos.left;
|
||
|
},
|
||
|
right: function () {
|
||
|
return hostElPos.left + hostElPos.width;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var shiftHeight = {
|
||
|
center: function () {
|
||
|
return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
|
||
|
},
|
||
|
top: function () {
|
||
|
return hostElPos.top;
|
||
|
},
|
||
|
bottom: function () {
|
||
|
return hostElPos.top + hostElPos.height;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
switch (pos0) {
|
||
|
case 'right':
|
||
|
targetElPos = {
|
||
|
top: shiftHeight[pos1](),
|
||
|
left: shiftWidth[pos0]()
|
||
|
};
|
||
|
break;
|
||
|
case 'left':
|
||
|
targetElPos = {
|
||
|
top: shiftHeight[pos1](),
|
||
|
left: hostElPos.left - targetElWidth
|
||
|
};
|
||
|
break;
|
||
|
case 'bottom':
|
||
|
targetElPos = {
|
||
|
top: shiftHeight[pos0](),
|
||
|
left: shiftWidth[pos1]()
|
||
|
};
|
||
|
break;
|
||
|
default:
|
||
|
targetElPos = {
|
||
|
top: hostElPos.top - targetElHeight,
|
||
|
left: shiftWidth[pos1]()
|
||
|
};
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return targetElPos;
|
||
|
}
|
||
|
};
|
||
|
}])
|
||
|
// ;
|
||
|
|
||
|
// angular.module('ui.bootstrap.bindHtml', [])
|
||
|
|
||
|
.directive('bindHtmlUnsafe', function () {
|
||
|
return function (scope, element, attr) {
|
||
|
element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe);
|
||
|
scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) {
|
||
|
element.html(value || '');
|
||
|
});
|
||
|
};
|
||
|
});
|