/*global angular*/
(function withAngular(angular) {
'use strict';
angular.module('720kb.tooltips', [])
.directive('tooltips', ['$window', '$compile', '$interpolate',
function manageDirective($window, $compile, $interpolate) {
var TOOLTIP_SMALL_MARGIN = 8 //px
, TOOLTIP_MEDIUM_MARGIN = 9 //px
, TOOLTIP_LARGE_MARGIN = 10 //px
, CSS_PREFIX = '_720kb-tooltip-'
, INTERPOLATE_START_SYM = $interpolate.startSymbol()
, INTERPOLATE_END_SYM = $interpolate.endSymbol();
return {
'restrict': 'A',
'scope': {},
'link': function linkingFunction($scope, element, attr) {
var initialized = false
, thisElement = angular.element(element[0])
, body = angular.element($window.document.getElementsByTagName('body')[0])
, theTooltip
, theTooltipHeight
, theTooltipWidth
, theTooltipMargin //used both for margin top left right bottom
, height
, width
, offsetTop
, offsetLeft
, title = attr.tooltipTitle || attr.title || ''
, content = attr.tooltipContent || ''
, html = attr.tooltipHtml || ''
, showTriggers = attr.tooltipShowTrigger || 'mouseover'
, hideTriggers = attr.tooltipHideTrigger || 'mouseleave'
, originSide = attr.tooltipSide || 'top'
, side = originSide
, size = attr.tooltipSize || 'medium'
, tryPosition = typeof attr.tooltipTry !== 'undefined' && attr.tooltipTry !== null ? $scope.$eval(attr.tooltipTry) : true
, className = attr.tooltipClass || ''
, speed = (attr.tooltipSpeed || 'medium').toLowerCase()
, delay = attr.tooltipDelay || 0
, lazyMode = typeof attr.tooltipLazy !== 'undefined' && attr.tooltipLazy !== null ? $scope.$eval(attr.tooltipLazy) : true
, hasCloseButton = typeof attr.tooltipCloseButton !== 'undefined' && attr.tooltipCloseButton !== null
, closeButtonContent = attr.tooltipCloseButton || ''
, htmlTemplate = '
';
$scope.title = title;
$scope.content = content;
$scope.html = html;
//parse the animation speed of tooltips
$scope.parseSpeed = function parseSpeed () {
switch (speed) {
case 'fast':
speed = 100;
break;
case 'medium':
speed = 450;
break;
case 'slow':
speed = 800;
break;
default:
speed = Number(speed);
}
};
//create the tooltip
theTooltip = $compile(htmlTemplate)($scope);
theTooltip.addClass(className);
body.append(theTooltip);
$scope.isTooltipEmpty = function checkEmptyTooltip () {
if (!$scope.title && !$scope.content && !$scope.html) {
return true;
}
};
$scope.initTooltip = function initTooltip (tooltipSide) {
if (!$scope.isTooltipEmpty()) {
theTooltip.css('visibility', '');
height = thisElement[0].offsetHeight;
width = thisElement[0].offsetWidth;
offsetTop = $scope.getRootOffsetTop(thisElement[0], 0);
offsetLeft = $scope.getRootOffsetLeft(thisElement[0], 0);
//get tooltip dimension
theTooltipHeight = theTooltip[0].offsetHeight;
theTooltipWidth = theTooltip[0].offsetWidth;
$scope.parseSpeed();
$scope.tooltipPositioning(tooltipSide);
} else {
theTooltip.css('visibility', 'hidden');
}
};
$scope.getRootOffsetTop = function getRootOffsetTop (elem, val){
if (!elem.offsetParent){
return val + elem.offsetTop;
}
return $scope.getRootOffsetTop(elem.offsetParent, val + elem.offsetTop - elem.scrollTop);
};
$scope.getRootOffsetLeft = function getRootOffsetLeft (elem, val){
if (!elem.offsetParent){
return val + elem.offsetLeft;
}
return $scope.getRootOffsetLeft(elem.offsetParent, val + elem.offsetLeft - elem.scrollLeft);
};
function onMouseEnterAndMouseOver() {
if (!lazyMode || !initialized) {
initialized = true;
$scope.initTooltip(side);
}
if (tryPosition) {
$scope.tooltipTryPosition();
}
$scope.showTooltip();
}
$scope.bindShowTriggers = function() {
thisElement.bind(showTriggers, onMouseEnterAndMouseOver);
};
function onMouseLeaveAndMouseOut() {
$scope.hideTooltip();
}
$scope.bindHideTriggers = function() {
thisElement.bind(hideTriggers, onMouseLeaveAndMouseOut);
};
$scope.clearTriggers = function() {
thisElement.unbind(showTriggers, onMouseEnterAndMouseOver);
thisElement.unbind(hideTriggers, onMouseLeaveAndMouseOut);
};
$scope.bindShowTriggers();
$scope.showTooltip = function showTooltip () {
theTooltip.addClass(CSS_PREFIX + 'open');
theTooltip.css('transition', 'opacity ' + speed + 'ms linear');
if (delay) {
theTooltip.css('transition-delay', delay + 'ms' );
}
$scope.clearTriggers();
$scope.bindHideTriggers();
};
$scope.hideTooltip = function hideTooltip () {
theTooltip.removeClass(CSS_PREFIX + 'open');
theTooltip.css('transition', '');
$scope.clearTriggers();
$scope.bindShowTriggers();
};
$scope.removePosition = function removeTooltipPosition () {
theTooltip
.removeClass(CSS_PREFIX + 'left')
.removeClass(CSS_PREFIX + 'right')
.removeClass(CSS_PREFIX + 'top')
.removeClass(CSS_PREFIX + 'bottom ');
};
$scope.tooltipPositioning = function tooltipPositioning (tooltipSide) {
$scope.removePosition();
var topValue
, leftValue;
if (size === 'small') {
theTooltipMargin = TOOLTIP_SMALL_MARGIN;
} else if (size === 'medium') {
theTooltipMargin = TOOLTIP_MEDIUM_MARGIN;
} else if (size === 'large') {
theTooltipMargin = TOOLTIP_LARGE_MARGIN;
}
if (tooltipSide === 'left') {
topValue = offsetTop + height / 2 - theTooltipHeight / 2;
leftValue = offsetLeft - (theTooltipWidth + theTooltipMargin);
theTooltip.css('top', topValue + 'px');
theTooltip.css('left', leftValue + 'px');
theTooltip.addClass(CSS_PREFIX + 'left');
}
if (tooltipSide === 'right') {
topValue = offsetTop + height / 2 - theTooltipHeight / 2;
leftValue = offsetLeft + width + theTooltipMargin;
theTooltip.css('top', topValue + 'px');
theTooltip.css('left', leftValue + 'px');
theTooltip.addClass(CSS_PREFIX + 'right');
}
if (tooltipSide === 'top') {
topValue = offsetTop - theTooltipMargin - theTooltipHeight;
leftValue = offsetLeft + width / 2 - theTooltipWidth / 2;
theTooltip.css('top', topValue + 'px');
theTooltip.css('left', leftValue + 'px');
theTooltip.addClass(CSS_PREFIX + 'top');
}
if (tooltipSide === 'bottom') {
topValue = offsetTop + height + theTooltipMargin;
leftValue = offsetLeft + width / 2 - theTooltipWidth / 2;
theTooltip.css('top', topValue + 'px');
theTooltip.css('left', leftValue + 'px');
theTooltip.addClass(CSS_PREFIX + 'bottom');
}
};
$scope.tooltipTryPosition = function tooltipTryPosition () {
var theTooltipH = theTooltip[0].offsetHeight
, theTooltipW = theTooltip[0].offsetWidth
, topOffset = theTooltip[0].offsetTop
, leftOffset = theTooltip[0].offsetLeft
, winWidth = $window.outerWidth
, winHeight = $window.outerHeight
, rightOffset = winWidth - (theTooltipW + leftOffset)
, bottomOffset = winHeight - (theTooltipH + topOffset)
//element OFFSETS (not tooltip offsets)
, elmHeight = thisElement[0].offsetHeight
, elmWidth = thisElement[0].offsetWidth
, elmOffsetLeft = thisElement[0].offsetLeft
, elmOffsetTop = thisElement[0].offsetTop
, elmOffsetRight = winWidth - (elmOffsetLeft + elmWidth)
, elmOffsetBottom = winHeight - (elmHeight + elmOffsetTop)
, offsets = {
'left': leftOffset,
'top': topOffset,
'bottom': bottomOffset,
'right': rightOffset
}
, posix = {
'left': elmOffsetLeft,
'right': elmOffsetRight,
'top': elmOffsetTop,
'bottom': elmOffsetBottom
}
, bestPosition = Object.keys(posix).reduce(function (best, key) {
return posix[best] > posix[key] ? best : key;
})
, worstOffset = Object.keys(offsets).reduce(function (worst, key) {
return offsets[worst] < offsets[key] ? worst : key;
});
if (originSide !== bestPosition && offsets[worstOffset] < 20) {
side = bestPosition;
$scope.tooltipPositioning(side);
$scope.initTooltip(bestPosition);
}
};
function onResize() {
$scope.hideTooltip();
$scope.initTooltip(originSide);
}
angular.element($window).bind('resize', onResize);
// destroy the tooltip when the directive is destroyed
// unbind all dom event handlers
$scope.$on('$destroy', function() {
angular.element($window).unbind('resize', onResize);
$scope.clearTriggers();
theTooltip.remove();
});
if (attr.tooltipTitle) {
attr.$observe('tooltipTitle', function(val) {
$scope.title = val;
$scope.initTooltip(side);
});
}
if (attr.title) {
attr.$observe('title', function(val) {
$scope.title = val;
$scope.initTooltip(side);
});
}
if (attr.tooltipContent) {
attr.$observe('tooltipContent', function(val) {
$scope.content = val;
$scope.initTooltip(side);
});
}
if (attr.tooltipHtml) {
attr.$observe('tooltipHtml', function(val) {
$scope.html = val;
$scope.initTooltip(side);
});
}
}
};
}]);
}(angular));