{I" class:ETI"ProcessedAsset;FI"logical_path;TI"jquery.powertip.js;FI" pathname;TI"n/usr/local/rvm/gems/ruby-2.0.0-p481/gems/spree_backend-2.2.4/vendor/assets/javascripts/jquery.powertip.js;FI"content_type;TI"application/javascript;TI" mtime;Tl+ƑTI" length;TiZYI" digest;TI"%c973b8a419157fd9d3eb8dbaa645e4eb;FI" source;TI"ZY/** * 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. * */ (function($) { 'use strict'; // useful private variables var $document = $(document), $window = $(window), $body = $('body'); /** * Session data * Private properties global to all powerTip instances * @type Object */ var session = { isPopOpen: false, isFixedPopOpen: false, isClosing: false, popOpenImminent: false, activeHover: null, currentX: 0, currentY: 0, previousX: 0, previousY: 0, desyncTimeout: null, mouseTrackingActive: false }; /** * 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. */ $.fn.powerTip = function(opts) { // don't do any work if there were no matched elements if (!this.length) { return this; } // extend options var options = $.extend({}, $.fn.powerTip.defaults, opts), tipController = new TooltipController(options); // hook mouse tracking initMouseTracking(); // setup the elements this.each(function() { var $this = $(this), dataPowertip = $this.data('powertip'), dataElem = $this.data('powertipjq'), dataTarget = $this.data('powertiptarget'), title = $this.attr('title'); // 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 if (!dataPowertip && !dataTarget && !dataElem && title) { $this.data('powertip', title); $this.removeAttr('title'); } // create hover controllers for each element $this.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); } }); }; /** * Default options for the powerTip plugin. * @type Object */ $.fn.powerTip.defaults = { fadeInTime: 200, fadeOutTime: 100, followMouse: false, popupId: 'powerTip', intentSensitivity: 7, intentPollInterval: 100, closeDelay: 100, placement: 'n', smartPlacement: false, offset: 10, mouseOnToPopup: 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 */ $.fn.powerTip.smartPlacementLists = { n: ['n', 'ne', 'nw', 's'], e: ['e', 'ne', 'se', 'w', 'nw', 'sw', 'n', 's', 'e'], s: ['s', 'se', 'sw', 'n'], w: ['w', 'nw', 'sw', 'e', 'ne', 'se', 'n', 's', 'w'], 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'] }; /** * 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. */ 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); } }, /** * Attempts to close any open tooltips. * @public */ closeTip: function() { $document.triggerHandler('closePowerTip'); } }; /** * Creates a new tooltip display controller. * @private * @constructor * @param {Object} element The element that this controller will handle. * @param {Object} options Options object containing settings. * @param {TooltipController} tipController The TooltipController for this instance. */ function DisplayController(element, options, tipController) { var hoverTimer = null; /** * 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). */ function openTooltip(immediate, forceOpen) { cancelTimer(); if (!element.data('hasActiveHover')) { if (!immediate) { session.popOpenImminent = true; hoverTimer = setTimeout( function() { hoverTimer = null; checkForIntent(element); }, options.intentPollInterval ); } else { if (forceOpen) { element.data('forcedOpen', true); } tipController.showTip(element); } } } /** * Begins the process of closing a tooltip. * @private * @param {Boolean=} disableDelay Disable close delay (optional). */ function closeTooltip(disableDelay) { cancelTimer(); if (element.data('hasActiveHover')) { session.popOpenImminent = false; element.data('forcedOpen', false); if (!disableDelay) { hoverTimer = setTimeout( function() { hoverTimer = null; tipController.hideTip(element); }, options.closeDelay ); } else { tipController.hideTip(element); } } } /** * Checks mouse position to make sure that the user intended to hover * on the specified element before showing the tooltip. * @private */ function checkForIntent() { // calculate mouse position difference var xDifference = Math.abs(session.previousX - session.currentX), yDifference = Math.abs(session.previousY - session.currentY), totalDifference = xDifference + yDifference; // check if difference has passed the sensitivity threshold if (totalDifference < options.intentSensitivity) { tipController.showTip(element); } else { // try again session.previousX = session.currentX; session.previousY = session.currentY; openTooltip(); } } /** * Cancels active hover timer. * @private */ function cancelTimer() { hoverTimer = clearTimeout(hoverTimer); } // expose the methods return { show: openTooltip, hide: closeTooltip, cancel: cancelTimer }; } /** * Creates a new tooltip controller. * @private * @constructor * @param {Object} options Options object containing settings. */ function TooltipController(options) { // build and append popup div if it does not already exist var tipElement = $('#' + options.popupId); if (tipElement.length === 0) { tipElement = $('
', { id: options.popupId }); // grab body element if it was not populated when the script loaded // this hack exists solely for jsfiddle support if ($body.length === 0) { $body = $('body'); } $body.append(tipElement); } // 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 }); } 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) { 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 if (session.activeHover) { session.activeHover.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(); } } } }); } /** * 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. */ function beginShowTip(element) { element.data('hasActiveHover', true); // show popup, asap tipElement.queue(function(next) { showTip(element); next(); }); } /** * Shows the tooltip popup, as soon as possible. * @private * @param {Object} element The element that the popup 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')) { 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 (!session.isClosing) { hideTip(session.activeHover); } tipElement.delay(100).queue(function(next) { showTip(element); next(); }); return; } // 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()); } else { // we have no content to display, give up return; } // 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; tipElement.data('followMouse', options.followMouse); tipElement.data('mouseOnToPopup', options.mouseOnToPopup); // set popup position if (!options.followMouse) { positionTipOnElement(element); session.isFixedPopOpen = true; } else { positionTipOnCursor(); } // fadein tipElement.fadeIn(options.fadeInTime, function() { // start desync polling if (!session.desyncTimeout) { session.desyncTimeout = setInterval(closeDesyncedTip, 500); } // trigger powerTipOpen event element.trigger('powerTipOpen'); }); } /** * Hides the tooltip popup, immediately. * @private * @param {Object} element The element that the popup should target. */ function hideTip(element) { session.isClosing = true; element.data('hasActiveHover', false); element.data('forcedOpen', false); // reset session session.activeHover = null; session.isPopOpen = false; // stop desync polling session.desyncTimeout = clearInterval(session.desyncTimeout); // unhook close event api listener $document.off('closePowerTip'); // fade out tipElement.fadeOut(options.fadeOutTime, function() { session.isClosing = false; session.isFixedPopOpen = 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 ); // trigger powerTipClose event element.trigger('powerTipClose'); }); } /** * Checks for a tooltip desync and closes the tooltip if one occurs. * @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; // 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; } } } 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); } } /** * Sets the tooltip popup too the correct position relative to the * specified target element. Based on options settings. * @private * @param {Object} element The element that the popup 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) { // grab the placement priority list 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. $.each(priorityList, function(idx, pos) { // get placement coordinates placementCoords = computePlacementCoords( element, pos, tipWidth, tipHeight ); finalPlacement = pos; // find collisions collisions = getViewportCollisions( placementCoords, tipWidth, tipHeight ); // break if there were no collisions if (collisions.length === 0) { 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 ); 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. * @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. */ function computePlacementCoords(element, placement, popWidth, popHeight) { // grab measurements var objectOffset = element.offset(), objectWidth = element.outerWidth(), objectHeight = element.outerHeight(), x = 0, y = 0; // 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; } return { x: Math.round(x), y: Math.round(y) }; } /** * Sets the tooltip CSS position on the document. * @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'); } // expose methods return { showTip: beginShowTip, hideTip: hideTip }; } /** * Hooks mouse position tracking to mousemove and scroll events. * Prevents attaching the events more than once. * @private */ function initMouseTracking() { var lastScrollX = 0, lastScrollY = 0; if (!session.mouseTrackingActive) { session.mouseTrackingActive = true; // grab the current scroll position on load $(function() { lastScrollX = $document.scrollLeft(); lastScrollY = $document.scrollTop(); }); // 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; } if (y !== lastScrollY) { session.currentY += y - lastScrollY; lastScrollY = y; } } }); } } /** * Saves the current mouse coordinates to the powerTip session object. * @private * @param {Object} event The mousemove event for the document. */ function trackMouse(event) { session.currentX = event.pageX; session.currentY = event.pageY; } /** * Tests if the mouse is currently over the specified element. * @private * @param {Object} element The element to check for hover. * @return {Boolean} */ function isMouseOver(element) { var elementPosition = element.offset(); return session.currentX >= elementPosition.left && session.currentX <= elementPosition.left + element.outerWidth() && session.currentY >= elementPosition.top && session.currentY <= elementPosition.top + element.outerHeight(); } /** * Finds any viewport collisions that an element (the tooltip) would have * if it were absolutely positioned at the specified coordinates. * @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. */ 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'); } if (coords.y + elementHeight > scrollTop + windowHeight) { collisions.push('bottom'); } if (coords.x < scrollLeft) { collisions.push('left'); } if (coords.x + elementWidth > scrollLeft + windowWidth) { collisions.push('right'); } return collisions; } }(jQuery)); ;TI"dependency_digest;TI"%93f70192c25a137c103b6d2a39b11681;FI"required_paths;T[I"n/usr/local/rvm/gems/ruby-2.0.0-p481/gems/spree_backend-2.2.4/vendor/assets/javascripts/jquery.powertip.js;FI"dependency_paths;T[{I" path;TI"n/usr/local/rvm/gems/ruby-2.0.0-p481/gems/spree_backend-2.2.4/vendor/assets/javascripts/jquery.powertip.js;FI" mtime;TI"2014-09-04T12:22:30-04:00;TI" digest;TI"%8edda80b737fbdfdfda8691d593da200;FI" _version;TI"%5ef14a844324cba3e114bd0123f88a5e;F