Created
March 25, 2014 00:28
-
-
Save tlvince/9752424 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-strap | |
* @version v2.0.0-rc.4 - 2014-03-07 | |
* @link http://mgcrea.github.io/angular-strap | |
* @author Olivier Louvignes (olivier@mg-crea.com) | |
* @license MIT License, http://www.opensource.org/licenses/MIT | |
*/ | |
'use strict'; | |
angular.module('mgcrea.ngStrap.tooltip', [ | |
'ngAnimate', | |
'mgcrea.ngStrap.helpers.dimensions' | |
]).provider('$tooltip', function () { | |
var defaults = this.defaults = { | |
animation: 'am-fade', | |
prefixClass: 'tooltip', | |
container: false, | |
placement: 'top', | |
template: '', | |
contentTemplate: false, | |
trigger: 'hover focus', | |
keyboard: false, | |
html: false, | |
show: false, | |
title: '', | |
type: '', | |
delay: 0 | |
}; | |
this.$get = [ | |
'$window', | |
'$rootScope', | |
'$compile', | |
'$q', | |
'$templateCache', | |
'$http', | |
'$animate', | |
'$timeout', | |
'dimensions', | |
'$$animateReflow', | |
function ($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $timeout, dimensions, $$animateReflow) { | |
var trim = String.prototype.trim; | |
var isTouch = 'createTouch' in $window.document; | |
var htmlReplaceRegExp = /ng-bind="/gi; | |
function TooltipFactory(element, config) { | |
var $tooltip = {}; | |
// Common vars | |
var options = $tooltip.$options = angular.extend({}, defaults, config); | |
$tooltip.$promise = fetchTemplate(options.template); | |
var scope = $tooltip.$scope = options.scope && options.scope.$new() || $rootScope.$new(); | |
if (options.delay && angular.isString(options.delay)) { | |
options.delay = parseFloat(options.delay); | |
} | |
// Support scope as string options | |
if (options.title) { | |
$tooltip.$scope.title = options.title; | |
} | |
// Provide scope helpers | |
scope.$hide = function () { | |
scope.$$postDigest(function () { | |
$tooltip.hide(); | |
}); | |
}; | |
scope.$show = function () { | |
scope.$$postDigest(function () { | |
$tooltip.show(); | |
}); | |
}; | |
scope.$toggle = function () { | |
scope.$$postDigest(function () { | |
$tooltip.toggle(); | |
}); | |
}; | |
$tooltip.$isShown = scope.$isShown = false; | |
// Private vars | |
var timeout, hoverState; | |
// Support contentTemplate option | |
if (options.contentTemplate) { | |
$tooltip.$promise = $tooltip.$promise.then(function (template) { | |
var templateEl = angular.element(template); | |
return fetchTemplate(options.contentTemplate).then(function (contentTemplate) { | |
findElement('[ng-bind="content"]', templateEl[0]).removeAttr('ng-bind').html(contentTemplate); | |
return templateEl[0].outerHTML; | |
}); | |
}); | |
} | |
// Fetch, compile then initialize tooltip | |
var tipLinker, tipElement, tipTemplate, tipContainer; | |
$tooltip.$promise.then(function (template) { | |
if (angular.isObject(template)) | |
template = template.data; | |
if (options.html) | |
template = template.replace(htmlReplaceRegExp, 'ng-bind-html="'); | |
template = trim.apply(template); | |
tipTemplate = template; | |
tipLinker = $compile(template); | |
$tooltip.init(); | |
}); | |
$tooltip.init = function () { | |
// Options: delay | |
if (options.delay && angular.isNumber(options.delay)) { | |
options.delay = { | |
show: options.delay, | |
hide: options.delay | |
}; | |
} | |
// Replace trigger on touch devices ? | |
// if(isTouch && options.trigger === defaults.trigger) { | |
// options.trigger.replace(/hover/g, 'click'); | |
// } | |
// Options : container | |
if (options.container === 'self') { | |
tipContainer = element; | |
} else if (options.container) { | |
tipContainer = findElement(options.container); | |
} | |
// Options: trigger | |
var triggers = options.trigger.split(' '); | |
angular.forEach(triggers, function (trigger) { | |
if (trigger === 'click') { | |
element.on('click', $tooltip.toggle); | |
} else if (trigger !== 'manual') { | |
if(!element.on) { return; } | |
element.on(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter); | |
element.on(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave); | |
trigger !== 'hover' && element.on(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown); | |
} | |
}); | |
// Options: show | |
if (options.show) { | |
scope.$$postDigest(function () { | |
options.trigger === 'focus' ? element[0].focus() : $tooltip.show(); | |
}); | |
} | |
}; | |
$tooltip.destroy = function () { | |
// Unbind events | |
var triggers = options.trigger.split(' '); | |
for (var i = triggers.length; i--;) { | |
var trigger = triggers[i]; | |
if (trigger === 'click') { | |
element.off('click', $tooltip.toggle); | |
} else if (trigger !== 'manual') { | |
element.off(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter); | |
element.off(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave); | |
trigger !== 'hover' && element.off(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown); | |
} | |
} | |
// Remove element | |
if (tipElement) { | |
tipElement.remove(); | |
tipElement = null; | |
} | |
// Destroy scope | |
scope.$destroy(); | |
}; | |
$tooltip.enter = function () { | |
console.log("value"); | |
clearTimeout(timeout); | |
if (!options.delay || !options.delay.show) { | |
return $tooltip.show(); | |
} | |
timeout = setTimeout(function () { | |
if (hoverState === 'in') | |
$tooltip.show(); | |
}, options.delay.show); | |
}; | |
$tooltip.show = function () { | |
var parent = options.container ? tipContainer : null; | |
var after = options.container ? null : element; | |
// Remove any existing tipElement | |
if (tipElement) | |
tipElement.remove(); | |
// Fetch a cloned element linked from template | |
tipElement = $tooltip.$element = tipLinker(scope, function (clonedElement, scope) { | |
}); | |
// Set the initial positioning. | |
tipElement.css({ | |
top: '0px', | |
left: '0px', | |
display: 'block' | |
}).addClass(options.placement); | |
// Options: animation | |
if (options.animation) | |
tipElement.addClass(options.animation); | |
// Options: type | |
if (options.type) | |
tipElement.addClass(options.prefixClass + '-' + options.type); | |
$animate.enter(tipElement, parent, after, function () { | |
}); | |
$tooltip.$isShown = scope.$isShown = true; | |
scope.$$phase || scope.$digest(); | |
$$animateReflow($tooltip.$applyPlacement); | |
// Bind events | |
if (options.keyboard) { | |
if (options.trigger !== 'focus') { | |
$tooltip.focus(); | |
tipElement.on('keyup', $tooltip.$onKeyUp); | |
} else { | |
element.on('keyup', $tooltip.$onFocusKeyUp); | |
} | |
} | |
}; | |
$tooltip.leave = function () { | |
clearTimeout(timeout); | |
hoverState = 'out'; | |
if (!options.delay || !options.delay.hide) { | |
return $tooltip.hide(); | |
} | |
timeout = setTimeout(function () { | |
if (hoverState === 'out') { | |
$tooltip.hide(); | |
} | |
}, options.delay.hide); | |
}; | |
$tooltip.hide = function (blur) { | |
if (!$tooltip.$isShown) | |
return; | |
$animate.leave(tipElement, function () { | |
tipElement = null; | |
}); | |
$tooltip.$isShown = scope.$isShown = false; | |
scope.$$phase || scope.$digest(); | |
// Unbind events | |
if (options.keyboard) { | |
tipElement.off('keyup', $tooltip.$onKeyUp); | |
} | |
// Allow to blur the input when hidden, like when pressing enter key | |
if (blur && options.trigger === 'focus') { | |
return element[0].blur(); | |
} | |
}; | |
$tooltip.toggle = function () { | |
$tooltip.$isShown ? $tooltip.leave() : $tooltip.enter(); | |
}; | |
$tooltip.focus = function () { | |
tipElement[0].focus(); | |
}; | |
// Protected methods | |
$tooltip.$applyPlacement = function () { | |
if (!tipElement) | |
return; | |
// Get the position of the tooltip element. | |
var elementPosition = getPosition(); | |
// Get the height and width of the tooltip so we can center it. | |
var tipWidth = tipElement.prop('offsetWidth'), tipHeight = tipElement.prop('offsetHeight'); | |
// Get the tooltip's top and left coordinates to center it with this directive. | |
var tipPosition = getCalculatedOffset(options.placement, elementPosition, tipWidth, tipHeight); | |
// Now set the calculated positioning. | |
tipPosition.top += 'px'; | |
tipPosition.left += 'px'; | |
tipElement.css(tipPosition); | |
}; | |
$tooltip.$onKeyUp = function (evt) { | |
evt.which === 27 && $tooltip.hide(); | |
}; | |
$tooltip.$onFocusKeyUp = function (evt) { | |
evt.which === 27 && element[0].blur(); | |
}; | |
$tooltip.$onFocusElementMouseDown = function (evt) { | |
evt.preventDefault(); | |
evt.stopPropagation(); | |
// Some browsers do not auto-focus buttons (eg. Safari) | |
$tooltip.$isShown ? element[0].blur() : element[0].focus(); | |
}; | |
// Private methods | |
function getPosition() { | |
if (options.container === 'body') { | |
return dimensions.offset(element[0]); | |
} else { | |
return dimensions.position(element[0]); | |
} | |
} | |
function getCalculatedOffset(placement, position, actualWidth, actualHeight) { | |
var offset; | |
var split = placement.split('-'); | |
switch (split[0]) { | |
case 'right': | |
offset = { | |
top: position.top + position.height / 2 - actualHeight / 2, | |
left: position.left + position.width | |
}; | |
break; | |
case 'bottom': | |
offset = { | |
top: position.top + position.height, | |
left: position.left + position.width / 2 - actualWidth / 2 | |
}; | |
break; | |
case 'left': | |
offset = { | |
top: position.top + position.height / 2 - actualHeight / 2, | |
left: position.left - actualWidth | |
}; | |
break; | |
default: | |
offset = { | |
top: position.top - actualHeight, | |
left: position.left + position.width / 2 - actualWidth / 2 | |
}; | |
break; | |
} | |
if (!split[1]) { | |
return offset; | |
} | |
// Add support for corners @todo css | |
if (split[0] === 'top' || split[0] === 'bottom') { | |
switch (split[1]) { | |
case 'left': | |
offset.left = position.left; | |
break; | |
case 'right': | |
offset.left = position.left + position.width - actualWidth; | |
} | |
} else if (split[0] === 'left' || split[0] === 'right') { | |
switch (split[1]) { | |
case 'top': | |
offset.top = position.top - actualHeight; | |
break; | |
case 'bottom': | |
offset.top = position.top + position.height; | |
} | |
} | |
return offset; | |
} | |
return $tooltip; | |
} | |
// Helper functions | |
function findElement(query, element) { | |
return angular.element((element || document).querySelectorAll(query)); | |
} | |
function fetchTemplate(template) { | |
return $q.when($templateCache.get(template) || $http.get(template)).then(function (res) { | |
if (angular.isObject(res)) { | |
$templateCache.put(template, res.data); | |
return res.data; | |
} | |
return res; | |
}); | |
} | |
return TooltipFactory; | |
} | |
]; | |
}).directive('bsTooltip', [ | |
'$window', | |
'$location', | |
'$sce', | |
'$tooltip', | |
'$$animateReflow', | |
function ($window, $location, $sce, $tooltip, $$animateReflow) { | |
return { | |
restrict: 'EAC', | |
scope: true, | |
link: function postLink(scope, element, attr, transclusion) { | |
// Directive options | |
var options = { scope: scope }; | |
angular.forEach([ | |
'template', | |
'contentTemplate', | |
'placement', | |
'container', | |
'delay', | |
'trigger', | |
'keyboard', | |
'html', | |
'animation', | |
'type' | |
], function (key) { | |
if (angular.isDefined(attr[key])) | |
options[key] = attr[key]; | |
}); | |
// Observe scope attributes for change | |
angular.forEach(['title'], function (key) { | |
attr[key] && attr.$observe(key, function (newValue, oldValue) { | |
scope[key] = $sce.trustAsHtml(newValue); | |
angular.isDefined(oldValue) && $$animateReflow(function () { | |
tooltip && tooltip.$applyPlacement(); | |
}); | |
}); | |
}); | |
// Support scope as an object | |
attr.bsTooltip && scope.$watch(attr.bsTooltip, function (newValue, oldValue) { | |
if (angular.isObject(newValue)) { | |
angular.extend(scope, newValue); | |
} else { | |
scope.content = newValue; | |
} | |
angular.isDefined(oldValue) && $$animateReflow(function () { | |
tooltip && tooltip.$applyPlacement(); | |
}); | |
}, true); | |
// Initialize popover | |
var tooltip = $tooltip(element, options); | |
// Garbage collection | |
scope.$on('$destroy', function () { | |
tooltip.destroy(); | |
options = null; | |
tooltip = null; | |
}); | |
} | |
}; | |
} | |
]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment