Skip to content

Instantly share code, notes, and snippets.

@stevenbenner
Created April 4, 2013 06:17
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save stevenbenner/5308254 to your computer and use it in GitHub Desktop.
PowerTip v1.1.0 vs v1.2.0 diff
--- 1.1.0/jquery.powertip.js 2013-04-04 10:11:34.000000000 +0400
+++ 1.2.0/jquery.powertip.js 2013-04-04 10:11:34.000000000 +0400
@@ -1,119 +1,159 @@
-/**
- * PowerTip
- *
- * @fileoverview jQuery plugin that creates hover tooltips.
- * @link http://stevenbenner.github.com/jquery-powertip/
- * @author Steven Benner (http://stevenbenner.com/)
- * @version 1.1.0
- * @requires jQuery 1.7+
- *
- * @license jQuery PowerTip Plugin v1.1.0
- * http://stevenbenner.github.com/jquery-powertip/
- * Copyright 2012 Steven Benner (http://stevenbenner.com/)
- * Released under the MIT license.
- * <https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt>
- */
-
-(function($) {
- 'use strict';
+/*!
+ PowerTip - v1.2.0 - 2013-04-03
+ http://stevenbenner.github.com/jquery-powertip/
+ Copyright (c) 2013 Steven Benner (http://stevenbenner.com/).
+ Released under MIT license.
+ https://raw.github.com/stevenbenner/jquery-powertip/master/LICENSE.txt
+*/
+(function(factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['jquery'], factory);
+ } else {
+ // Browser globals
+ factory(jQuery);
+ }
+}(function($) {
// useful private variables
var $document = $(document),
$window = $(window),
$body = $('body');
+ // constants
+ var DATA_DISPLAYCONTROLLER = 'displayController',
+ DATA_HASACTIVEHOVER = 'hasActiveHover',
+ DATA_FORCEDOPEN = 'forcedOpen',
+ DATA_HASMOUSEMOVE = 'hasMouseMove',
+ DATA_MOUSEONTOTIP = 'mouseOnToPopup',
+ DATA_ORIGINALTITLE = 'originalTitle',
+ DATA_POWERTIP = 'powertip',
+ DATA_POWERTIPJQ = 'powertipjq',
+ DATA_POWERTIPTARGET = 'powertiptarget',
+ RAD2DEG = 180 / Math.PI;
+
/**
* Session data
* Private properties global to all powerTip instances
- * @type Object
*/
var session = {
- isPopOpen: false,
- isFixedPopOpen: false,
+ isTipOpen: false,
+ isFixedTipOpen: false,
isClosing: false,
- popOpenImminent: false,
+ tipOpenImminent: false,
activeHover: null,
currentX: 0,
currentY: 0,
previousX: 0,
previousY: 0,
desyncTimeout: null,
- mouseTrackingActive: false
+ mouseTrackingActive: false,
+ delayInProgress: false,
+ windowWidth: 0,
+ windowHeight: 0,
+ scrollTop: 0,
+ scrollLeft: 0
};
/**
- * Display hover tooltips on the matched elements.
- * @param {Object} opts The options object to use for the plugin.
- * @return {Object} jQuery object for the matched selectors.
+ * Collision enumeration
+ * @enum {number}
*/
- $.fn.powerTip = function(opts) {
+ var Collision = {
+ none: 0,
+ top: 1,
+ bottom: 2,
+ left: 4,
+ right: 8
+ };
+ /**
+ * Display hover tooltips on the matched elements.
+ * @param {(Object|string)} opts The options object to use for the plugin, or
+ * the name of a method to invoke on the first matched element.
+ * @param {*=} [arg] Argument for an invoked method (optional).
+ * @return {jQuery} jQuery object for the matched selectors.
+ */
+ $.fn.powerTip = function(opts, arg) {
// don't do any work if there were no matched elements
if (!this.length) {
return this;
}
- // extend options
+ // handle api method calls on the plugin, e.g. powerTip('hide')
+ if ($.type(opts) === 'string' && $.powerTip[opts]) {
+ return $.powerTip[opts].call(this, this, arg);
+ }
+
+ // extend options and instantiate TooltipController
var options = $.extend({}, $.fn.powerTip.defaults, opts),
tipController = new TooltipController(options);
- // hook mouse tracking
- initMouseTracking();
+ // hook mouse and viewport dimension tracking
+ initTracking();
// setup the elements
- this.each(function() {
+ this.each(function elementSetup() {
var $this = $(this),
- dataPowertip = $this.data('powertip'),
- dataElem = $this.data('powertipjq'),
- dataTarget = $this.data('powertiptarget'),
- title = $this.attr('title');
-
+ dataPowertip = $this.data(DATA_POWERTIP),
+ dataElem = $this.data(DATA_POWERTIPJQ),
+ dataTarget = $this.data(DATA_POWERTIPTARGET),
+ title;
+
+ // handle repeated powerTip calls on the same element by destroying the
+ // original instance hooked to it and replacing it with this call
+ if ($this.data(DATA_DISPLAYCONTROLLER)) {
+ $.powerTip.destroy($this);
+ }
// attempt to use title attribute text if there is no data-powertip,
// data-powertipjq or data-powertiptarget. If we do use the title
// attribute, delete the attribute so the browser will not show it
+ title = $this.attr('title');
if (!dataPowertip && !dataTarget && !dataElem && title) {
- $this.data('powertip', title);
+ $this.data(DATA_POWERTIP, title);
+ $this.data(DATA_ORIGINALTITLE, title);
$this.removeAttr('title');
}
// create hover controllers for each element
$this.data(
- 'displayController',
+ DATA_DISPLAYCONTROLLER,
new DisplayController($this, options, tipController)
);
});
- // attach hover events to all matched elements
- return this.on({
- // mouse events
- mouseenter: function(event) {
- trackMouse(event);
- session.previousX = event.pageX;
- session.previousY = event.pageY;
- $(this).data('displayController').show();
- },
- mouseleave: function() {
- $(this).data('displayController').hide();
- },
-
- // keyboard events
- focus: function() {
- var element = $(this);
- if (!isMouseOver(element)) {
- element.data('displayController').show(true);
- }
- },
- blur: function() {
- $(this).data('displayController').hide(true);
- }
- });
+ // attach events to matched elements if the manual options is not enabled
+ if (!options.manual) {
+ this.on({
+ // mouse events
+ 'mouseenter.powertip': function elementMouseEnter(event) {
+ $.powerTip.show(this, event);
+ },
+ 'mouseleave.powertip': function elementMouseLeave() {
+ $.powerTip.hide(this);
+ },
+ // keyboard events
+ 'focus.powertip': function elementFocus() {
+ $.powerTip.show(this);
+ },
+ 'blur.powertip': function elementBlur() {
+ $.powerTip.hide(this, true);
+ },
+ 'keydown.powertip': function elementKeyDown(event) {
+ // close tooltip when the escape key is pressed
+ if (event.keyCode === 27) {
+ $.powerTip.hide(this, true);
+ }
+ }
+ });
+ }
+ return this;
};
/**
* Default options for the powerTip plugin.
- * @type Object
*/
$.fn.powerTip.defaults = {
fadeInTime: 200,
@@ -126,15 +166,15 @@
placement: 'n',
smartPlacement: false,
offset: 10,
- mouseOnToPopup: false
+ mouseOnToPopup: false,
+ manual: false
};
/**
* Default smart placement priority lists.
- * The first item in the array is the highest priority, the last is the
- * lowest. The last item is also the default, which will be used if all
- * previous options do not fit.
- * @type Object
+ * The first item in the array is the highest priority, the last is the lowest.
+ * The last item is also the default, which will be used if all previous options
+ * do not fit.
*/
$.fn.powerTip.smartPlacementLists = {
n: ['n', 'ne', 'nw', 's'],
@@ -144,47 +184,125 @@
nw: ['nw', 'w', 'sw', 'n', 's', 'se', 'nw'],
ne: ['ne', 'e', 'se', 'n', 's', 'sw', 'ne'],
sw: ['sw', 'w', 'nw', 's', 'n', 'ne', 'sw'],
- se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se']
+ se: ['se', 'e', 'ne', 's', 'n', 'nw', 'se'],
+ 'nw-alt': ['nw-alt', 'n', 'ne-alt', 'sw-alt', 's', 'se-alt', 'w', 'e'],
+ 'ne-alt': ['ne-alt', 'n', 'nw-alt', 'se-alt', 's', 'sw-alt', 'e', 'w'],
+ 'sw-alt': ['sw-alt', 's', 'se-alt', 'nw-alt', 'n', 'ne-alt', 'w', 'e'],
+ 'se-alt': ['se-alt', 's', 'sw-alt', 'ne-alt', 'n', 'nw-alt', 'e', 'w']
};
/**
* Public API
- * @type Object
*/
$.powerTip = {
-
/**
* Attempts to show the tooltip for the specified element.
- * @public
- * @param {Object} element The element that the tooltip should for.
+ * @param {jQuery|Element} element The element to open the tooltip for.
+ * @param {jQuery.Event=} event jQuery event for hover intent and mouse
+ * tracking (optional).
*/
- showTip: function(element) {
- // close any open tooltip
- $.powerTip.closeTip();
- // grab only the first matched element and ask it to show its tip
- element = element.first();
- if (!isMouseOver(element)) {
- element.data('displayController').show(true, true);
+ show: function apiShowTip(element, event) {
+ if (event) {
+ trackMouse(event);
+ session.previousX = event.pageX;
+ session.previousY = event.pageY;
+ $(element).data(DATA_DISPLAYCONTROLLER).show();
+ } else {
+ $(element).first().data(DATA_DISPLAYCONTROLLER).show(true, true);
}
+ return element;
+ },
+
+ /**
+ * Repositions the tooltip on the element.
+ * @param {jQuery|Element} element The element the tooltip is shown for.
+ */
+ reposition: function apiResetPosition(element) {
+ $(element).first().data(DATA_DISPLAYCONTROLLER).resetPosition();
+ return element;
},
/**
* Attempts to close any open tooltips.
- * @public
+ * @param {(jQuery|Element)=} element The element with the tooltip that
+ * should be closed (optional).
+ * @param {boolean=} immediate Disable close delay (optional).
*/
- closeTip: function() {
- $document.triggerHandler('closePowerTip');
- }
+ hide: function apiCloseTip(element, immediate) {
+ if (element) {
+ $(element).first().data(DATA_DISPLAYCONTROLLER).hide(immediate);
+ } else {
+ if (session.activeHover) {
+ session.activeHover.data(DATA_DISPLAYCONTROLLER).hide(true);
+ }
+ }
+ return element;
+ },
+ /**
+ * Destroy and roll back any powerTip() instance on the specified element.
+ * @param {jQuery|Element} element The element with the powerTip instance.
+ */
+ destroy: function apiDestroy(element) {
+ $(element).off('.powertip').each(function destroy() {
+ var $this = $(this),
+ dataAttributes = [
+ DATA_ORIGINALTITLE,
+ DATA_DISPLAYCONTROLLER,
+ DATA_HASACTIVEHOVER,
+ DATA_FORCEDOPEN
+ ];
+
+ if ($this.data(DATA_ORIGINALTITLE)) {
+ $this.attr('title', $this.data(DATA_ORIGINALTITLE));
+ dataAttributes.push(DATA_POWERTIP);
+ }
+
+ $this.removeData(dataAttributes);
+ });
+ return element;
+ }
};
+ // API aliasing
+ $.powerTip.showTip = $.powerTip.show;
+ $.powerTip.closeTip = $.powerTip.hide;
+
+ /**
+ * Creates a new CSSCoordinates object.
+ * @private
+ * @constructor
+ */
+ function CSSCoordinates() {
+ var me = this;
+
+ // initialize object properties
+ me.top = 'auto';
+ me.left = 'auto';
+ me.right = 'auto';
+ me.bottom = 'auto';
+
+ /**
+ * Set a property to a value.
+ * @private
+ * @param {string} property The name of the property.
+ * @param {number} value The value of the property.
+ */
+ me.set = function(property, value) {
+ if ($.isNumeric(value)) {
+ me[property] = Math.round(value);
+ }
+ };
+ }
+
/**
* Creates a new tooltip display controller.
* @private
* @constructor
- * @param {Object} element The element that this controller will handle.
+ * @param {jQuery} element The element that this controller will handle.
* @param {Object} options Options object containing settings.
- * @param {TooltipController} tipController The TooltipController for this instance.
+ * @param {TooltipController} tipController The TooltipController object for
+ * this instance.
*/
function DisplayController(element, options, tipController) {
var hoverTimer = null;
@@ -192,24 +310,25 @@
/**
* Begins the process of showing a tooltip.
* @private
- * @param {Boolean=} immediate Skip intent testing (optional).
- * @param {Boolean=} forceOpen Ignore cursor position and force tooltip to open (optional).
+ * @param {boolean=} immediate Skip intent testing (optional).
+ * @param {boolean=} forceOpen Ignore cursor position and force tooltip to
+ * open (optional).
*/
function openTooltip(immediate, forceOpen) {
cancelTimer();
- if (!element.data('hasActiveHover')) {
+ if (!element.data(DATA_HASACTIVEHOVER)) {
if (!immediate) {
- session.popOpenImminent = true;
+ session.tipOpenImminent = true;
hoverTimer = setTimeout(
- function() {
+ function intentDelay() {
hoverTimer = null;
- checkForIntent(element);
+ checkForIntent();
},
options.intentPollInterval
);
} else {
if (forceOpen) {
- element.data('forcedOpen', true);
+ element.data(DATA_FORCEDOPEN, true);
}
tipController.showTip(element);
}
@@ -219,18 +338,20 @@
/**
* Begins the process of closing a tooltip.
* @private
- * @param {Boolean=} disableDelay Disable close delay (optional).
+ * @param {boolean=} disableDelay Disable close delay (optional).
*/
function closeTooltip(disableDelay) {
cancelTimer();
- if (element.data('hasActiveHover')) {
- session.popOpenImminent = false;
- element.data('forcedOpen', false);
+ session.tipOpenImminent = false;
+ if (element.data(DATA_HASACTIVEHOVER)) {
+ element.data(DATA_FORCEDOPEN, false);
if (!disableDelay) {
+ session.delayInProgress = true;
hoverTimer = setTimeout(
- function() {
+ function closeDelay() {
hoverTimer = null;
tipController.hideTip(element);
+ session.delayInProgress = false;
},
options.closeDelay
);
@@ -241,8 +362,8 @@
}
/**
- * Checks mouse position to make sure that the user intended to hover
- * on the specified element before showing the tooltip.
+ * Checks mouse position to make sure that the user intended to hover on the
+ * specified element before showing the tooltip.
* @private
*/
function checkForIntent() {
@@ -268,14 +389,238 @@
*/
function cancelTimer() {
hoverTimer = clearTimeout(hoverTimer);
+ session.delayInProgress = false;
+ }
+
+ /**
+ * Repositions the tooltip on this element.
+ * @private
+ */
+ function repositionTooltip() {
+ tipController.resetPosition(element);
}
// expose the methods
- return {
- show: openTooltip,
- hide: closeTooltip,
- cancel: cancelTimer
- };
+ this.show = openTooltip;
+ this.hide = closeTooltip;
+ this.cancel = cancelTimer;
+ this.resetPosition = repositionTooltip;
+ }
+
+ /**
+ * Creates a new Placement Calculator.
+ * @private
+ * @constructor
+ */
+ function PlacementCalculator() {
+ /**
+ * Compute the CSS position to display a tooltip at the specified placement
+ * relative to the specified element.
+ * @private
+ * @param {jQuery} element The element that the tooltip should target.
+ * @param {string} placement The placement for the tooltip.
+ * @param {number} tipWidth Width of the tooltip element in pixels.
+ * @param {number} tipHeight Height of the tooltip element in pixels.
+ * @param {number} offset Distance to offset tooltips in pixels.
+ * @return {CSSCoordinates} A CSSCoordinates object with the position.
+ */
+ function computePlacementCoords(element, placement, tipWidth, tipHeight, offset) {
+ var placementBase = placement.split('-')[0], // ignore 'alt' for corners
+ coords = new CSSCoordinates(),
+ position;
+
+ if (isSvgElement(element)) {
+ position = getSvgPlacement(element, placementBase);
+ } else {
+ position = getHtmlPlacement(element, placementBase);
+ }
+
+ // calculate the appropriate x and y position in the document
+ switch (placement) {
+ case 'n':
+ coords.set('left', position.left - (tipWidth / 2));
+ coords.set('bottom', session.windowHeight - position.top + offset);
+ break;
+ case 'e':
+ coords.set('left', position.left + offset);
+ coords.set('top', position.top - (tipHeight / 2));
+ break;
+ case 's':
+ coords.set('left', position.left - (tipWidth / 2));
+ coords.set('top', position.top + offset);
+ break;
+ case 'w':
+ coords.set('top', position.top - (tipHeight / 2));
+ coords.set('right', session.windowWidth - position.left + offset);
+ break;
+ case 'nw':
+ coords.set('bottom', session.windowHeight - position.top + offset);
+ coords.set('right', session.windowWidth - position.left - 20);
+ break;
+ case 'nw-alt':
+ coords.set('left', position.left);
+ coords.set('bottom', session.windowHeight - position.top + offset);
+ break;
+ case 'ne':
+ coords.set('left', position.left - 20);
+ coords.set('bottom', session.windowHeight - position.top + offset);
+ break;
+ case 'ne-alt':
+ coords.set('bottom', session.windowHeight - position.top + offset);
+ coords.set('right', session.windowWidth - position.left);
+ break;
+ case 'sw':
+ coords.set('top', position.top + offset);
+ coords.set('right', session.windowWidth - position.left - 20);
+ break;
+ case 'sw-alt':
+ coords.set('left', position.left);
+ coords.set('top', position.top + offset);
+ break;
+ case 'se':
+ coords.set('left', position.left - 20);
+ coords.set('top', position.top + offset);
+ break;
+ case 'se-alt':
+ coords.set('top', position.top + offset);
+ coords.set('right', session.windowWidth - position.left);
+ break;
+ }
+
+ return coords;
+ }
+
+ /**
+ * Finds the tooltip attachment point in the document for a HTML DOM element
+ * for the specified placement.
+ * @private
+ * @param {jQuery} element The element that the tooltip should target.
+ * @param {string} placement The placement for the tooltip.
+ * @return {Object} An object with the top,left position values.
+ */
+ function getHtmlPlacement(element, placement) {
+ var objectOffset = element.offset(),
+ objectWidth = element.outerWidth(),
+ objectHeight = element.outerHeight(),
+ left,
+ top;
+
+ // calculate the appropriate x and y position in the document
+ switch (placement) {
+ case 'n':
+ left = objectOffset.left + objectWidth / 2;
+ top = objectOffset.top;
+ break;
+ case 'e':
+ left = objectOffset.left + objectWidth;
+ top = objectOffset.top + objectHeight / 2;
+ break;
+ case 's':
+ left = objectOffset.left + objectWidth / 2;
+ top = objectOffset.top + objectHeight;
+ break;
+ case 'w':
+ left = objectOffset.left;
+ top = objectOffset.top + objectHeight / 2;
+ break;
+ case 'nw':
+ left = objectOffset.left;
+ top = objectOffset.top;
+ break;
+ case 'ne':
+ left = objectOffset.left + objectWidth;
+ top = objectOffset.top;
+ break;
+ case 'sw':
+ left = objectOffset.left;
+ top = objectOffset.top + objectHeight;
+ break;
+ case 'se':
+ left = objectOffset.left + objectWidth;
+ top = objectOffset.top + objectHeight;
+ break;
+ }
+
+ return {
+ top: top,
+ left: left
+ };
+ }
+
+ /**
+ * Finds the tooltip attachment point in the document for a SVG element for
+ * the specified placement.
+ * @private
+ * @param {jQuery} element The element that the tooltip should target.
+ * @param {string} placement The placement for the tooltip.
+ * @return {Object} An object with the top,left position values.
+ */
+ function getSvgPlacement(element, placement) {
+ var svgElement = element.closest('svg')[0],
+ domElement = element[0],
+ point = svgElement.createSVGPoint(),
+ boundingBox = domElement.getBBox(),
+ matrix = domElement.getScreenCTM(),
+ halfWidth = boundingBox.width / 2,
+ halfHeight = boundingBox.height / 2,
+ placements = [],
+ placementKeys = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'],
+ coords,
+ rotation,
+ steps,
+ x;
+
+ function pushPlacement() {
+ placements.push(point.matrixTransform(matrix));
+ }
+
+ // get bounding box corners and midpoints
+ point.x = boundingBox.x;
+ point.y = boundingBox.y;
+ pushPlacement();
+ point.x += halfWidth;
+ pushPlacement();
+ point.x += halfWidth;
+ pushPlacement();
+ point.y += halfHeight;
+ pushPlacement();
+ point.y += halfHeight;
+ pushPlacement();
+ point.x -= halfWidth;
+ pushPlacement();
+ point.x -= halfWidth;
+ pushPlacement();
+ point.y -= halfHeight;
+ pushPlacement();
+
+ // determine rotation
+ if (placements[0].y !== placements[1].y || placements[0].x !== placements[7].x) {
+ rotation = Math.atan2(matrix.b, matrix.a) * RAD2DEG;
+ steps = Math.ceil(((rotation % 360) - 22.5) / 45);
+ if (steps < 1) {
+ steps += 8;
+ }
+ while (steps--) {
+ placementKeys.push(placementKeys.shift());
+ }
+ }
+
+ // find placement
+ for (x = 0; x < placements.length; x++) {
+ if (placementKeys[x] === placement) {
+ coords = placements[x];
+ break;
+ }
+ }
+
+ return {
+ top: coords.y + session.scrollTop,
+ left: coords.x + session.scrollLeft
+ };
+ }
+
+ // expose methods
+ this.compute = computePlacementCoords;
}
/**
@@ -285,13 +630,14 @@
* @param {Object} options Options object containing settings.
*/
function TooltipController(options) {
+ var placementCalculator = new PlacementCalculator(),
+ tipElement = $('#' + options.popupId);
- // build and append popup div if it does not already exist
- var tipElement = $('#' + options.popupId);
+ // build and append tooltip div if it does not already exist
if (tipElement.length === 0) {
- tipElement = $('<div></div>', { id: options.popupId });
+ tipElement = $('<div/>', { id: options.popupId });
// grab body element if it was not populated when the script loaded
- // this hack exists solely for jsfiddle support
+ // note: this hack exists solely for jsfiddle support
if ($body.length === 0) {
$body = $('body');
}
@@ -300,79 +646,79 @@
// hook mousemove for cursor follow tooltips
if (options.followMouse) {
- // only one positionTipOnCursor hook per popup element, please
- if (!tipElement.data('hasMouseMove')) {
- $document.on({
- mousemove: positionTipOnCursor,
- scroll: positionTipOnCursor
- });
+ // only one positionTipOnCursor hook per tooltip element, please
+ if (!tipElement.data(DATA_HASMOUSEMOVE)) {
+ $document.on('mousemove', positionTipOnCursor);
+ $window.on('scroll', positionTipOnCursor);
+ tipElement.data(DATA_HASMOUSEMOVE, true);
}
- tipElement.data('hasMouseMove', true);
}
- // if we want to be able to mouse onto the popup then we need to attach
- // hover events to the popup that will cancel a close request on hover
- // and start a new close request on mouseleave
- if (options.followMouse || options.mouseOnToPopup) {
+ // if we want to be able to mouse onto the tooltip then we need to attach
+ // hover events to the tooltip that will cancel a close request on hover and
+ // start a new close request on mouseleave
+ if (options.mouseOnToPopup) {
tipElement.on({
- mouseenter: function() {
- if (tipElement.data('followMouse') || tipElement.data('mouseOnToPopup')) {
- // check activeHover in case the mouse cursor entered
- // the tooltip during the fadeOut and close cycle
+ mouseenter: function tipMouseEnter() {
+ // we only let the mouse stay on the tooltip if it is set to let
+ // users interact with it
+ if (tipElement.data(DATA_MOUSEONTOTIP)) {
+ // check activeHover in case the mouse cursor entered the
+ // tooltip during the fadeOut and close cycle
if (session.activeHover) {
- session.activeHover.data('displayController').cancel();
+ session.activeHover.data(DATA_DISPLAYCONTROLLER).cancel();
}
}
},
- mouseleave: function() {
- if (tipElement.data('mouseOnToPopup')) {
- // check activeHover in case the mouse cursor entered
- // the tooltip during the fadeOut and close cycle
- if (session.activeHover) {
- session.activeHover.data('displayController').hide();
- }
+ mouseleave: function tipMouseLeave() {
+ // check activeHover in case the mouse cursor entered the
+ // tooltip during the fadeOut and close cycle
+ if (session.activeHover) {
+ session.activeHover.data(DATA_DISPLAYCONTROLLER).hide();
}
}
});
}
/**
- * Gives the specified element the active-hover state and queues up
- * the showTip function.
+ * Gives the specified element the active-hover state and queues up the
+ * showTip function.
* @private
- * @param {Object} element The element that the tooltip should target.
+ * @param {jQuery} element The element that the tooltip should target.
*/
function beginShowTip(element) {
- element.data('hasActiveHover', true);
- // show popup, asap
- tipElement.queue(function(next) {
+ element.data(DATA_HASACTIVEHOVER, true);
+ // show tooltip, asap
+ tipElement.queue(function queueTipInit(next) {
showTip(element);
next();
});
}
/**
- * Shows the tooltip popup, as soon as possible.
+ * Shows the tooltip, as soon as possible.
* @private
- * @param {Object} element The element that the popup should target.
+ * @param {jQuery} element The element that the tooltip should target.
*/
function showTip(element) {
- // it is possible, especially with keyboard navigation, to move on
- // to another element with a tooltip during the queue to get to
- // this point in the code. if that happens then we need to not
- // proceed or we may have the fadeout callback for the last tooltip
- // execute immediately after this code runs, causing bugs.
- if (!element.data('hasActiveHover')) {
+ var tipContent;
+
+ // it is possible, especially with keyboard navigation, to move on to
+ // another element with a tooltip during the queue to get to this point
+ // in the code. if that happens then we need to not proceed or we may
+ // have the fadeout callback for the last tooltip execute immediately
+ // after this code runs, causing bugs.
+ if (!element.data(DATA_HASACTIVEHOVER)) {
return;
}
- // if the popup is open and we got asked to open another one then
- // the old one is still in its fadeOut cycle, so wait and try again
- if (session.isPopOpen) {
+ // if the tooltip is open and we got asked to open another one then the
+ // old one is still in its fadeOut cycle, so wait and try again
+ if (session.isTipOpen) {
if (!session.isClosing) {
hideTip(session.activeHover);
}
- tipElement.delay(100).queue(function(next) {
+ tipElement.delay(100).queue(function queueTipAgain(next) {
showTip(element);
next();
});
@@ -382,19 +728,10 @@
// trigger powerTipPreRender event
element.trigger('powerTipPreRender');
- var tipText = element.data('powertip'),
- tipTarget = element.data('powertiptarget'),
- tipElem = element.data('powertipjq'),
- tipContent = tipTarget ? $('#' + tipTarget) : [];
-
- // set popup content
- if (tipText) {
- tipElement.html(tipText);
- } else if (tipElem && tipElem.length > 0) {
- tipElement.empty();
- tipElem.clone(true, true).appendTo(tipElement);
- } else if (tipContent && tipContent.length > 0) {
- tipElement.html($('#' + tipTarget).html());
+ // set tooltip content
+ tipContent = getTooltipContent(element);
+ if (tipContent) {
+ tipElement.empty().append(tipContent);
} else {
// we have no content to display, give up
return;
@@ -403,27 +740,21 @@
// trigger powerTipRender event
element.trigger('powerTipRender');
- // hook close event for triggering from the api
- $document.on('closePowerTip', function() {
- element.data('displayController').hide(true);
- });
-
session.activeHover = element;
- session.isPopOpen = true;
+ session.isTipOpen = true;
- tipElement.data('followMouse', options.followMouse);
- tipElement.data('mouseOnToPopup', options.mouseOnToPopup);
+ tipElement.data(DATA_MOUSEONTOTIP, options.mouseOnToPopup);
- // set popup position
+ // set tooltip position
if (!options.followMouse) {
positionTipOnElement(element);
- session.isFixedPopOpen = true;
+ session.isFixedTipOpen = true;
} else {
positionTipOnCursor();
}
// fadein
- tipElement.fadeIn(options.fadeInTime, function() {
+ tipElement.fadeIn(options.fadeInTime, function fadeInCallback() {
// start desync polling
if (!session.desyncTimeout) {
session.desyncTimeout = setInterval(closeDesyncedTip, 500);
@@ -435,33 +766,37 @@
}
/**
- * Hides the tooltip popup, immediately.
+ * Hides the tooltip.
* @private
- * @param {Object} element The element that the popup should target.
+ * @param {jQuery} element The element that the tooltip should target.
*/
function hideTip(element) {
- session.isClosing = true;
- element.data('hasActiveHover', false);
- element.data('forcedOpen', false);
// reset session
+ session.isClosing = true;
session.activeHover = null;
- session.isPopOpen = false;
+ session.isTipOpen = false;
+
// stop desync polling
session.desyncTimeout = clearInterval(session.desyncTimeout);
- // unhook close event api listener
- $document.off('closePowerTip');
+
+ // reset element state
+ element.data(DATA_HASACTIVEHOVER, false);
+ element.data(DATA_FORCEDOPEN, false);
+
// fade out
- tipElement.fadeOut(options.fadeOutTime, function() {
+ tipElement.fadeOut(options.fadeOutTime, function fadeOutCallback() {
+ var coords = new CSSCoordinates();
+
+ // reset session and tooltip element
session.isClosing = false;
- session.isFixedPopOpen = false;
+ session.isFixedTipOpen = false;
tipElement.removeClass();
- // support mouse-follow and fixed position pops at the same
- // time by moving the popup to the last known cursor location
- // after it is hidden
- setTipPosition(
- session.currentX + options.offset,
- session.currentY + options.offset
- );
+
+ // support mouse-follow and fixed position tips at the same time by
+ // moving the tooltip to the last cursor location after it is hidden
+ coords.set('top', session.currentY + options.offset);
+ coords.set('left', session.currentX + options.offset);
+ tipElement.css(coords);
// trigger powerTipClose event
element.trigger('powerTipClose');
@@ -469,268 +804,245 @@
}
/**
- * Checks for a tooltip desync and closes the tooltip if one occurs.
+ * Moves the tooltip to the users mouse cursor.
* @private
*/
- function closeDesyncedTip() {
- // It is possible for the mouse cursor to leave an element without
- // firing the mouseleave event. This seems to happen (in FF) if the
- // element is disabled under mouse cursor, the element is moved out
- // from under the mouse cursor (such as a slideDown() occurring
- // above it), or if the browser is resized by code moving the
- // element from under the mouse cursor. If this happens it will
- // result in a desynced tooltip because we wait for any exiting
- // open tooltips to close before opening a new one. So we should
- // periodically check for a desync situation and close the tip if
- // such a situation arises.
- if (session.isPopOpen && !session.isClosing) {
- var isDesynced = false;
+ function positionTipOnCursor() {
+ // to support having fixed tooltips on the same page as cursor tooltips,
+ // where both instances are referencing the same tooltip element, we
+ // need to keep track of the mouse position constantly, but we should
+ // only set the tip location if a fixed tip is not currently open, a tip
+ // open is imminent or active, and the tooltip element in question does
+ // have a mouse-follow using it.
+ if (!session.isFixedTipOpen && (session.isTipOpen || (session.tipOpenImminent && tipElement.data(DATA_HASMOUSEMOVE)))) {
+ // grab measurements
+ var tipWidth = tipElement.outerWidth(),
+ tipHeight = tipElement.outerHeight(),
+ coords = new CSSCoordinates(),
+ collisions,
+ collisionCount;
+
+ // grab collisions
+ coords.set('top', session.currentY + options.offset);
+ coords.set('left', session.currentX + options.offset);
+ collisions = getViewportCollisions(
+ coords,
+ tipWidth,
+ tipHeight
+ );
- // case 1: user already moused onto another tip - easy test
- if (session.activeHover.data('hasActiveHover') === false) {
- isDesynced = true;
- } else {
- // case 2: hanging tip - have to test if mouse position is
- // not over the active hover and not over a tooltip set to
- // let the user interact with it.
- // for keyboard navigation, this only counts if the element
- // does not have focus.
- // for tooltips opened via the api we need to check if it
- // has the forcedOpen flag.
- if (!isMouseOver(session.activeHover) && !session.activeHover.is(":focus") && !session.activeHover.data('forcedOpen')) {
- if (tipElement.data('mouseOnToPopup')) {
- if (!isMouseOver(tipElement)) {
- isDesynced = true;
- }
- } else {
- isDesynced = true;
+ // handle tooltip view port collisions
+ if (collisions !== Collision.none) {
+ collisionCount = countFlags(collisions);
+ if (collisionCount === 1) {
+ // if there is only one collision (bottom or right) then
+ // simply constrain the tooltip to the view port
+ if (collisions === Collision.right) {
+ coords.set('left', session.windowWidth - tipWidth);
+ } else if (collisions === Collision.bottom) {
+ coords.set('top', session.scrollTop + session.windowHeight - tipHeight);
}
+ } else {
+ // if the tooltip has more than one collision then it is
+ // trapped in the corner and should be flipped to get it out
+ // of the users way
+ coords.set('left', session.currentX - tipWidth - options.offset);
+ coords.set('top', session.currentY - tipHeight - options.offset);
}
}
- if (isDesynced) {
- // close the desynced tip
- hideTip(session.activeHover);
- }
- }
- }
-
- /**
- * Moves the tooltip popup to the users mouse cursor.
- * @private
- */
- function positionTipOnCursor() {
- // to support having fixed powertips on the same page as cursor
- // powertips, where both instances are referencing the same popup
- // element, we need to keep track of the mouse position constantly,
- // but we should only set the pop location if a fixed pop is not
- // currently open, a pop open is imminent or active, and the popup
- // element in question does have a mouse-follow using it.
- if ((session.isPopOpen && !session.isFixedPopOpen) || (session.popOpenImminent && !session.isFixedPopOpen && tipElement.data('hasMouseMove'))) {
- // grab measurements
- var scrollTop = $window.scrollTop(),
- windowWidth = $window.width(),
- windowHeight = $window.height(),
- popWidth = tipElement.outerWidth(),
- popHeight = tipElement.outerHeight(),
- x = 0,
- y = 0;
-
- // constrain pop to browser viewport
- if ((popWidth + session.currentX + options.offset) < windowWidth) {
- x = session.currentX + options.offset;
- } else {
- x = windowWidth - popWidth;
- }
- if ((popHeight + session.currentY + options.offset) < (scrollTop + windowHeight)) {
- y = session.currentY + options.offset;
- } else {
- y = scrollTop + windowHeight - popHeight;
- }
-
// position the tooltip
- setTipPosition(x, y);
+ tipElement.css(coords);
}
}
/**
- * Sets the tooltip popup too the correct position relative to the
- * specified target element. Based on options settings.
+ * Sets the tooltip to the correct position relative to the specified target
+ * element. Based on options settings.
* @private
- * @param {Object} element The element that the popup should target.
+ * @param {jQuery} element The element that the tooltip should target.
*/
function positionTipOnElement(element) {
- var tipWidth = tipElement.outerWidth(),
- tipHeight = tipElement.outerHeight(),
- priorityList,
- placementCoords,
- finalPlacement,
- collisions;
-
- // with smart placement we will try a series of placement
- // options and use the first one that does not collide with the
- // browser view port boundaries.
- if (options.smartPlacement) {
+ var priorityList,
+ finalPlacement;
- // grab the placement priority list
+ if (options.smartPlacement) {
priorityList = $.fn.powerTip.smartPlacementLists[options.placement];
- // iterate over the priority list and use the first placement
- // option that does not collide with the viewport. if they all
- // collide then the last placement in the list will be used.
+ // iterate over the priority list and use the first placement option
+ // that does not collide with the view port. if they all collide
+ // then the last placement in the list will be used.
$.each(priorityList, function(idx, pos) {
- // get placement coordinates
- placementCoords = computePlacementCoords(
- element,
- pos,
- tipWidth,
- tipHeight
+ // place tooltip and find collisions
+ var collisions = getViewportCollisions(
+ placeTooltip(element, pos),
+ tipElement.outerWidth(),
+ tipElement.outerHeight()
);
- finalPlacement = pos;
- // find collisions
- collisions = getViewportCollisions(
- placementCoords,
- tipWidth,
- tipHeight
- );
+ // update the final placement variable
+ finalPlacement = pos;
// break if there were no collisions
- if (collisions.length === 0) {
+ if (collisions === Collision.none) {
return false;
}
});
-
} else {
-
- // if we're not going to use the smart placement feature then
- // just compute the coordinates and do it
- placementCoords = computePlacementCoords(
- element,
- options.placement,
- tipWidth,
- tipHeight
- );
+ // if we're not going to use the smart placement feature then just
+ // compute the coordinates and do it
+ placeTooltip(element, options.placement);
finalPlacement = options.placement;
-
}
// add placement as class for CSS arrows
tipElement.addClass(finalPlacement);
-
- // position the tooltip
- setTipPosition(placementCoords.x, placementCoords.y);
}
/**
- * Compute the top/left coordinates to display the tooltip at the
- * specified placement relative to the specified element.
+ * Sets the tooltip position to the appropriate values to show the tip at
+ * the specified placement. This function will iterate and test the tooltip
+ * to support elastic tooltips.
* @private
- * @param {Object} element The element that the tooltip should target.
- * @param {String} placement The placement for the tooltip.
- * @param {Number} popWidth Width of the tooltip element in pixels.
- * @param {Number} popHeight Height of the tooltip element in pixels.
- * @retun {Object} An object with the x and y coordinates.
+ * @param {jQuery} element The element that the tooltip should target.
+ * @param {string} placement The placement for the tooltip.
+ * @return {CSSCoordinates} A CSSCoordinates object with the top, left, and
+ * right position values.
*/
- function computePlacementCoords(element, placement, popWidth, popHeight) {
- // grab measurements
- var objectOffset = element.offset(),
- objectWidth = element.outerWidth(),
- objectHeight = element.outerHeight(),
- x = 0,
- y = 0;
+ function placeTooltip(element, placement) {
+ var iterationCount = 0,
+ tipWidth,
+ tipHeight,
+ coords = new CSSCoordinates();
+
+ // set the tip to 0,0 to get the full expanded width
+ coords.set('top', 0);
+ coords.set('left', 0);
+ tipElement.css(coords);
+
+ // to support elastic tooltips we need to check for a change in the
+ // rendered dimensions after the tooltip has been positioned
+ do {
+ // grab the current tip dimensions
+ tipWidth = tipElement.outerWidth();
+ tipHeight = tipElement.outerHeight();
- // calculate the appropriate x and y position in the document
- switch (placement) {
- case 'n':
- x = (objectOffset.left + (objectWidth / 2)) - (popWidth / 2);
- y = objectOffset.top - popHeight - options.offset;
- break;
- case 'e':
- x = objectOffset.left + objectWidth + options.offset;
- y = (objectOffset.top + (objectHeight / 2)) - (popHeight / 2);
- break;
- case 's':
- x = (objectOffset.left + (objectWidth / 2)) - (popWidth / 2);
- y = objectOffset.top + objectHeight + options.offset;
- break;
- case 'w':
- x = objectOffset.left - popWidth - options.offset;
- y = (objectOffset.top + (objectHeight / 2)) - (popHeight / 2);
- break;
- case 'nw':
- x = (objectOffset.left - popWidth) + 20;
- y = objectOffset.top - popHeight - options.offset;
- break;
- case 'ne':
- x = (objectOffset.left + objectWidth) - 20;
- y = objectOffset.top - popHeight - options.offset;
- break;
- case 'sw':
- x = (objectOffset.left - popWidth) + 20;
- y = objectOffset.top + objectHeight + options.offset;
- break;
- case 'se':
- x = (objectOffset.left + objectWidth) - 20;
- y = objectOffset.top + objectHeight + options.offset;
- break;
- }
+ // get placement coordinates
+ coords = placementCalculator.compute(
+ element,
+ placement,
+ tipWidth,
+ tipHeight,
+ options.offset
+ );
- return {
- x: Math.round(x),
- y: Math.round(y)
- };
+ // place the tooltip
+ tipElement.css(coords);
+ } while (
+ // sanity check: limit to 5 iterations, and...
+ ++iterationCount <= 5 &&
+ // try again if the dimensions changed after placement
+ (tipWidth !== tipElement.outerWidth() || tipHeight !== tipElement.outerHeight())
+ );
+
+ return coords;
}
/**
- * Sets the tooltip CSS position on the document.
+ * Checks for a tooltip desync and closes the tooltip if one occurs.
* @private
- * @param {Number} x Left position in pixels.
- * @param {Number} y Top position in pixels.
*/
- function setTipPosition(x, y) {
- tipElement.css('left', x + 'px');
- tipElement.css('top', y + 'px');
+ function closeDesyncedTip() {
+ var isDesynced = false;
+ // It is possible for the mouse cursor to leave an element without
+ // firing the mouseleave or blur event. This most commonly happens when
+ // the element is disabled under mouse cursor. If this happens it will
+ // result in a desynced tooltip because the tooltip was never asked to
+ // close. So we should periodically check for a desync situation and
+ // close the tip if such a situation arises.
+ if (session.isTipOpen && !session.isClosing && !session.delayInProgress) {
+ // user moused onto another tip or active hover is disabled
+ if (session.activeHover.data(DATA_HASACTIVEHOVER) === false || session.activeHover.is(':disabled')) {
+ isDesynced = true;
+ } else {
+ // hanging tip - have to test if mouse position is not over the
+ // active hover and not over a tooltip set to let the user
+ // interact with it.
+ // for keyboard navigation: this only counts if the element does
+ // not have focus.
+ // for tooltips opened via the api: we need to check if it has
+ // the forcedOpen flag.
+ if (!isMouseOver(session.activeHover) && !session.activeHover.is(':focus') && !session.activeHover.data(DATA_FORCEDOPEN)) {
+ if (tipElement.data(DATA_MOUSEONTOTIP)) {
+ if (!isMouseOver(tipElement)) {
+ isDesynced = true;
+ }
+ } else {
+ isDesynced = true;
+ }
+ }
+ }
+
+ if (isDesynced) {
+ // close the desynced tip
+ hideTip(session.activeHover);
+ }
+ }
}
// expose methods
- return {
- showTip: beginShowTip,
- hideTip: hideTip
- };
+ this.showTip = beginShowTip;
+ this.hideTip = hideTip;
+ this.resetPosition = positionTipOnElement;
}
/**
- * Hooks mouse position tracking to mousemove and scroll events.
- * Prevents attaching the events more than once.
+ * Determine whether a jQuery object is an SVG element
* @private
+ * @param {jQuery} element The element to check
+ * @return {boolean} Whether this is an SVG element
*/
- function initMouseTracking() {
- var lastScrollX = 0,
- lastScrollY = 0;
+ function isSvgElement(element) {
+ return window.SVGElement && element[0] instanceof SVGElement;
+ }
+ /**
+ * Initializes the viewport dimension cache and hooks up the mouse position
+ * tracking and viewport dimension tracking events.
+ * Prevents attaching the events more than once.
+ * @private
+ */
+ function initTracking() {
if (!session.mouseTrackingActive) {
session.mouseTrackingActive = true;
- // grab the current scroll position on load
- $(function() {
- lastScrollX = $document.scrollLeft();
- lastScrollY = $document.scrollTop();
+ // grab the current viewport dimensions on load
+ $(function getViewportDimensions() {
+ session.scrollLeft = $window.scrollLeft();
+ session.scrollTop = $window.scrollTop();
+ session.windowWidth = $window.width();
+ session.windowHeight = $window.height();
});
- // hook mouse position tracking
- $document.on({
- mousemove: trackMouse,
- scroll: function() {
- var x = $document.scrollLeft(),
- y = $document.scrollTop();
- if (x !== lastScrollX) {
- session.currentX += x - lastScrollX;
- lastScrollX = x;
+ // hook mouse move tracking
+ $document.on('mousemove', trackMouse);
+
+ // hook viewport dimensions tracking
+ $window.on({
+ resize: function trackResize() {
+ session.windowWidth = $window.width();
+ session.windowHeight = $window.height();
+ },
+ scroll: function trackScroll() {
+ var x = $window.scrollLeft(),
+ y = $window.scrollTop();
+ if (x !== session.scrollLeft) {
+ session.currentX += x - session.scrollLeft;
+ session.scrollLeft = x;
}
- if (y !== lastScrollY) {
- session.currentY += y - lastScrollY;
- lastScrollY = y;
+ if (y !== session.scrollTop) {
+ session.currentY += y - session.scrollTop;
+ session.scrollTop = y;
}
}
});
@@ -738,9 +1050,9 @@
}
/**
- * Saves the current mouse coordinates to the powerTip session object.
+ * Saves the current mouse coordinates to the session object.
* @private
- * @param {Object} event The mousemove event for the document.
+ * @param {jQuery.Event} event The mousemove event for the document.
*/
function trackMouse(event) {
session.currentX = event.pageX;
@@ -750,47 +1062,105 @@
/**
* Tests if the mouse is currently over the specified element.
* @private
- * @param {Object} element The element to check for hover.
- * @return {Boolean}
+ * @param {jQuery} element The element to check for hover.
+ * @return {boolean}
*/
function isMouseOver(element) {
- var elementPosition = element.offset();
+ // use getBoundingClientRect() because jQuery's width() and height()
+ // methods do not work with SVG elements
+ // compute width/height because those properties do not exist on the object
+ // returned by getBoundingClientRect() in older versions of IE
+ var elementPosition = element.offset(),
+ elementBox = element[0].getBoundingClientRect(),
+ elementWidth = elementBox.right - elementBox.left,
+ elementHeight = elementBox.bottom - elementBox.top;
+
return session.currentX >= elementPosition.left &&
- session.currentX <= elementPosition.left + element.outerWidth() &&
+ session.currentX <= elementPosition.left + elementWidth &&
session.currentY >= elementPosition.top &&
- session.currentY <= elementPosition.top + element.outerHeight();
+ session.currentY <= elementPosition.top + elementHeight;
}
/**
- * Finds any viewport collisions that an element (the tooltip) would have
- * if it were absolutely positioned at the specified coordinates.
+ * Fetches the tooltip content from the specified element's data attributes.
* @private
- * @param {Object} coords Coordinates for the element. (e.g. {x: 123, y: 123})
- * @param {Number} elementWidth Width of the element in pixels.
- * @param {Number} elementHeight Height of the element in pixels.
- * @return {Array} Array of words representing directional collisions.
+ * @param {jQuery} element The element to get the tooltip content for.
+ * @return {(string|jQuery|undefined)} The text/HTML string, jQuery object, or
+ * undefined if there was no tooltip content for the element.
+ */
+ function getTooltipContent(element) {
+ var tipText = element.data(DATA_POWERTIP),
+ tipObject = element.data(DATA_POWERTIPJQ),
+ tipTarget = element.data(DATA_POWERTIPTARGET),
+ targetElement,
+ content;
+
+ if (tipText) {
+ if ($.isFunction(tipText)) {
+ tipText = tipText.call(element[0]);
+ }
+ content = tipText;
+ } else if (tipObject) {
+ if ($.isFunction(tipObject)) {
+ tipObject = tipObject.call(element[0]);
+ }
+ if (tipObject.length > 0) {
+ content = tipObject.clone(true, true);
+ }
+ } else if (tipTarget) {
+ targetElement = $('#' + tipTarget);
+ if (targetElement.length > 0) {
+ content = targetElement.html();
+ }
+ }
+
+ return content;
+ }
+
+ /**
+ * Finds any viewport collisions that an element (the tooltip) would have if it
+ * were absolutely positioned at the specified coordinates.
+ * @private
+ * @param {CSSCoordinates} coords Coordinates for the element.
+ * @param {number} elementWidth Width of the element in pixels.
+ * @param {number} elementHeight Height of the element in pixels.
+ * @return {number} Value with the collision flags.
*/
function getViewportCollisions(coords, elementWidth, elementHeight) {
- var scrollLeft = $window.scrollLeft(),
- scrollTop = $window.scrollTop(),
- windowWidth = $window.width(),
- windowHeight = $window.height(),
- collisions = [];
-
- if (coords.y < scrollTop) {
- collisions.push('top');
+ var viewportTop = session.scrollTop,
+ viewportLeft = session.scrollLeft,
+ viewportBottom = viewportTop + session.windowHeight,
+ viewportRight = viewportLeft + session.windowWidth,
+ collisions = Collision.none;
+
+ if (coords.top < viewportTop || Math.abs(coords.bottom - session.windowHeight) - elementHeight < viewportTop) {
+ collisions |= Collision.top;
}
- if (coords.y + elementHeight > scrollTop + windowHeight) {
- collisions.push('bottom');
+ if (coords.top + elementHeight > viewportBottom || Math.abs(coords.bottom - session.windowHeight) > viewportBottom) {
+ collisions |= Collision.bottom;
}
- if (coords.x < scrollLeft) {
- collisions.push('left');
+ if (coords.left < viewportLeft || coords.right + elementWidth > viewportRight) {
+ collisions |= Collision.left;
}
- if (coords.x + elementWidth > scrollLeft + windowWidth) {
- collisions.push('right');
+ if (coords.left + elementWidth > viewportRight || coords.right < viewportLeft) {
+ collisions |= Collision.right;
}
return collisions;
}
-}(jQuery));
+ /**
+ * Counts the number of bits set on a flags value.
+ * @param {number} value The flags value.
+ * @return {number} The number of bits that have been set.
+ */
+ function countFlags(value) {
+ var count = 0;
+ while (value) {
+ value &= value - 1;
+ count++;
+ }
+ return count;
+ }
+
+}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment