From 71f53b929c0b90c1a15f5a1385064ccc1550f977 Mon Sep 17 00:00:00 2001 From: louisameline Date: Mon, 13 Jun 2016 13:56:25 +0200 Subject: [PATCH] fix events --- dist/js/tooltipster-follower.js | 495 +++++++++++++++------------- dist/js/tooltipster-follower.min.js | 2 +- src/js/tooltipster-follower.js | 495 +++++++++++++++------------- 3 files changed, 535 insertions(+), 457 deletions(-) diff --git a/dist/js/tooltipster-follower.js b/dist/js/tooltipster-follower.js index 3130b0d..1dae402 100644 --- a/dist/js/tooltipster-follower.js +++ b/dist/js/tooltipster-follower.js @@ -27,6 +27,7 @@ $.tooltipster._plugin({ instance: { /** * @return {object} An object with the defaults options + * @private */ __defaults: function() { @@ -40,8 +41,10 @@ $.tooltipster._plugin({ /** * Run once at instantiation of the plugin - * + * * @param {object} instance The tooltipster object that instantiated this plugin + * @return {self} + * @private */ __init: function(instance) { @@ -53,7 +56,7 @@ $.tooltipster._plugin({ // the inition repositionOnScroll option value self.__initialROS = instance.option('repositionOnScroll'); self.__instance = instance; - self.__latestMousemove; + self.__latestMouseEvent; self.__namespace = 'tooltipster-follower-'+ Math.round(Math.random()*1000000); self.__pointerPosition; self.__previousState = 'closed'; @@ -89,7 +92,7 @@ $.tooltipster._plugin({ self.__instance._on('start.'+ self.__namespace, function(event) { self.__instance._$origin.on('mousemove.'+ self.__namespace, function(e) { - self.__latestMousemove = e; + self.__latestMouseEvent = e; }); }); @@ -100,7 +103,7 @@ $.tooltipster._plugin({ // forget the event if (event.type == 'startcancel') { - self.__latestMousemove = null; + self.__latestMouseEvent = null; } }); @@ -115,10 +118,15 @@ $.tooltipster._plugin({ self.__previousState = event.state; }); + + return self; }, /** * Called when the tooltip has closed + * + * @return {self} + * @private */ __close: function() { @@ -134,13 +142,19 @@ $.tooltipster._plugin({ // stop listening to mouse moves $($.tooltipster._env.window.document).off('.'+ this.__namespace); + + // reset the event + this.__latestMouseEvent = null; + + return this; }, /** * Contains the HTML markup of the tooltip and the bindings the should * exist as long as the tooltip is open * - * @return {object} The tooltip, as a jQuery-wrapped HTML element + * @return {self} + * @private */ __create: function() { @@ -171,10 +185,15 @@ $.tooltipster._plugin({ // tell the instance that the tooltip element has been created self.__instance._trigger('created'); + + return self; }, /** * Called upon the destruction of the tooltip or the destruction of the plugin + * + * @return {self} + * @private */ __destroy: function() { @@ -183,6 +202,8 @@ $.tooltipster._plugin({ if (!this.__initialROS) { this.__instance.option('repositionOnScroll', false); } + + return this; }, /** @@ -191,265 +212,284 @@ $.tooltipster._plugin({ * Note: this is less "smart" than sideTip, which tests scenarios before choosing one. * Here we have to be fast so the moving animation can stay fluid. So there will be no * constrained widths for example. + * + * @return {self} + * @private */ __follow: function(event) { - // use the latest mousemove event if we were recording them before the tooltip was - // opened, and then let it go (see the comment on the `start` listener). - if (this.__latestMousemove) { - event = this.__latestMousemove; - this.__latestMousemove = null; + // store the event in case it's a method call that triggers this method next time, + // or use the latest mousemove event if we have one. + if (event) { + this.__latestMouseEvent = event; } - - var coord = {}, - anchor = this.__options.anchor, - offset = $.merge([], this.__options.offset); - - // the scroll data of the helper must be updated manually on mousemove when the - // origin is fixed, because Tooltipster will not call __reposition on scroll, so - // it's out of date. Even though the tooltip will be fixed too, we need to know - // the scroll distance to determine the position of the pointer relatively to the - // viewport - this.__helper.geo.window.scroll = { - left: $.tooltipster._env.window.scrollX || $.tooltipster._env.window.document.documentElement.scrollLeft, - top: $.tooltipster._env.window.scrollY || $.tooltipster._env.window.document.documentElement.scrollTop - }; - - // coord left - switch (anchor) { - - case 'top-left': - case 'left-center': - case 'bottom-left': - coord.left = event.pageX + offset[0]; - break; - - case 'top-center': - case 'bottom-center': - coord.left = event.pageX + offset[0] - this.__size.width / 2; - break; - - case 'top-right': - case 'right-center': - case 'bottom-right': - coord.left = event.pageX + offset[0] - this.__size.width; - break; - - default: - console.log('Wrong anchor value'); - break; + else if (this.__latestMouseEvent) { + event = this.__latestMouseEvent; } - // coord top - switch (anchor) { + if (event) { - case 'top-left': - case 'top-center': - case 'top-right': - // minus because the Y axis is reversed (pos above the X axis, neg below) - coord.top = event.pageY - offset[1]; - break; + var coord = {}, + anchor = this.__options.anchor, + offset = $.merge([], this.__options.offset); - case 'left-center': - case 'right-center': - coord.top = event.pageY - offset[1] - this.__size.height / 2; - break; + // the scroll data of the helper must be updated manually on mousemove when the + // origin is fixed, because Tooltipster will not call __reposition on scroll, so + // it's out of date. Even though the tooltip will be fixed too, we need to know + // the scroll distance to determine the position of the pointer relatively to the + // viewport + this.__helper.geo.window.scroll = { + left: $.tooltipster._env.window.scrollX || $.tooltipster._env.window.document.documentElement.scrollLeft, + top: $.tooltipster._env.window.scrollY || $.tooltipster._env.window.document.documentElement.scrollTop + }; - case 'bottom-left': - case 'bottom-center': - case 'bottom-right': - coord.top = event.pageY - offset[1] - this.__size.height; - break; - } - - // if the tooltip does not fit on the given side, see if it could fit on the - // opposite one, otherwise put at the bottom (which may be moved again to the - // top by the rest of the script below) - if ( anchor == 'left-center' - || anchor == 'right-center' - ){ + // coord left + switch (anchor) { + + case 'top-left': + case 'left-center': + case 'bottom-left': + coord.left = event.pageX + offset[0]; + break; + + case 'top-center': + case 'bottom-center': + coord.left = event.pageX + offset[0] - this.__size.width / 2; + break; + + case 'top-right': + case 'right-center': + case 'bottom-right': + coord.left = event.pageX + offset[0] - this.__size.width; + break; + + default: + console.log('Wrong anchor value'); + break; + } - // if the tooltip is on the left of the cursor - if (anchor == 'right-center') { + // coord top + switch (anchor) { - // if it overflows the viewport on the left side - if (coord.left < this.__helper.geo.window.scroll.left) { + case 'top-left': + case 'top-center': + case 'top-right': + // minus because the Y axis is reversed (pos above the X axis, neg below) + coord.top = event.pageY - offset[1]; + break; + + case 'left-center': + case 'right-center': + coord.top = event.pageY - offset[1] - this.__size.height / 2; + break; + + case 'bottom-left': + case 'bottom-center': + case 'bottom-right': + coord.top = event.pageY - offset[1] - this.__size.height; + break; + } + + // if the tooltip does not fit on the given side, see if it could fit on the + // opposite one, otherwise put at the bottom (which may be moved again to the + // top by the rest of the script below) + if ( anchor == 'left-center' + || anchor == 'right-center' + ){ + + // if the tooltip is on the left of the cursor + if (anchor == 'right-center') { - // if it wouldn't overflow on the right - if (event.pageX - offset[0] + this.__size.width <= this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width) { + // if it overflows the viewport on the left side + if (coord.left < this.__helper.geo.window.scroll.left) { - // move to the right - anchor = 'left-center'; - // reverse the offset as well - offset[0] = -offset[0]; - coord.left = event.pageX + offset[0]; + // if it wouldn't overflow on the right + if (event.pageX - offset[0] + this.__size.width <= this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width) { + + // move to the right + anchor = 'left-center'; + // reverse the offset as well + offset[0] = -offset[0]; + coord.left = event.pageX + offset[0]; + } + else { + // move to the bottom left + anchor = 'top-right'; + // we'll use the X offset to move the tooltip on the Y axis. Maybe + // we'll make this configurable at some point + offset[1] = offset[0]; + coord = { + left: 0, + top: event.pageY - offset[1] + }; + } } - else { - // move to the bottom left - anchor = 'top-right'; - // we'll use the X offset to move the tooltip on the Y axis. Maybe - // we'll make this configurable at some point - offset[1] = offset[0]; - coord = { - left: 0, - top: event.pageY - offset[1] - }; + } + else { + + // if it overflows the viewport on the right side + if (coord.left + this.__size.width > this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width) { + + var coordLeft = event.pageX - offset[0] - this.__size.width; + + // if it wouldn't overflow on the left + if (coordLeft >= 0) { + + // move to the left + anchor = 'right-center'; + // reverse the offset as well + offset[0] = -offset[0]; + coord.left = coordLeft; + } + else { + // move to the bottom right + anchor = 'top-left'; + offset[1] = -offset[0]; + coord = { + left: event.pageX + offset[0], + top: event.pageY - offset[1] + }; + } } } - } - else { - // if it overflows the viewport on the right side - if (coord.left + this.__size.width > this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width) { + // if it overflows the viewport at the bottom + if (coord.top + this.__size.height > this.__helper.geo.window.scroll.top + this.__helper.geo.window.size.height) { - var coordLeft = event.pageX - offset[0] - this.__size.width; + // move up + coord.top = this.__helper.geo.window.scroll.top + this.__helper.geo.window.size.height - this.__size.height; + } + // if it overflows the viewport at the top + if (coord.top < this.__helper.geo.window.scroll.top) { - // if it wouldn't overflow on the left - if (coordLeft >= 0) { - - // move to the left - anchor = 'right-center'; - // reverse the offset as well - offset[0] = -offset[0]; - coord.left = coordLeft; - } - else { - // move to the bottom right - anchor = 'top-left'; - offset[1] = -offset[0]; - coord = { - left: event.pageX + offset[0], - top: event.pageY - offset[1] - }; - } + // move down + coord.top = this.__helper.geo.window.scroll.top; + } + // if it overflows the document at the bottom + if (coord.top + this.__size.height > this.__helper.geo.document.size.height) { + + // move up + coord.top = this.__helper.geo.document.size.height - this.__size.height; + } + // if it overflows the document at the top + if (coord.top < 0) { + + // no top document overflow + coord.top = 0; } } - // if it overflows the viewport at the bottom - if (coord.top + this.__size.height > this.__helper.geo.window.scroll.top + this.__helper.geo.window.size.height) { + // when the tooltip is not on a side, it may freely move horizontally because + // it won't go under the pointer + if ( anchor != 'left-center' + && anchor != 'right-center' + ){ - // move up - coord.top = this.__helper.geo.window.scroll.top + this.__helper.geo.window.size.height - this.__size.height; - } - // if it overflows the viewport at the top - if (coord.top < this.__helper.geo.window.scroll.top) { + // left and right overflow - // move down - coord.top = this.__helper.geo.window.scroll.top; - } - // if it overflows the document at the bottom - if (coord.top + this.__size.height > this.__helper.geo.document.size.height) { + if (coord.left + this.__size.width > this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width) { + coord.left = this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width - this.__size.width; + } - // move up - coord.top = this.__helper.geo.document.size.height - this.__size.height; - } - // if it overflows the document at the top - if (coord.top < 0) { + // don't ever let document overflow on the left, only on the right, so the user + // can scroll. Note: right overflow should not happen often because when + // measuring the natural width, text is already broken to fit into the viewport. + if (coord.left < 0) { + coord.left = 0; + } - // no top document overflow - coord.top = 0; - } - } - - // when the tooltip is not on a side, it may freely move horizontally because - // it won't go under the pointer - if ( anchor != 'left-center' - && anchor != 'right-center' - ){ - - // left and right overflow - - if (coord.left + this.__size.width > this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width) { - coord.left = this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width - this.__size.width; - } - - // don't ever let document overflow on the left, only on the right, so the user - // can scroll. Note: right overflow should not happen often because when - // measuring the natural width, text is already broken to fit into the viewport. - if (coord.left < 0) { - coord.left = 0; - } - - // top and bottom overflow - - var pointerViewportY = event.pageY - this.__helper.geo.window.scroll.top; - - // if the tooltip is above the pointer - if (anchor.indexOf('bottom') == 0) { + // top and bottom overflow - // if it overflows the viewport on top - if (coord.top < this.__helper.geo.window.scroll.top) { + var pointerViewportY = event.pageY - this.__helper.geo.window.scroll.top; + + // if the tooltip is above the pointer + if (anchor.indexOf('bottom') == 0) { - // if the tooltip overflows the document at the top - if ( coord.top < 0 - // if there is more space in the viewport below the pointer and that it won't - // overflow the document, switch to the bottom. In the latter case, it might - // seem odd not to switch to the bottom while there is more space, but the - // idea is that the user couldn't close the tooltip, scroll down and try to - // open it again, whereas he can do that at the top - || ( pointerViewportY < this.__helper.geo.window.size.height - pointerViewportY - && event.pageY + offset[1] + this.__size.height <= this.__helper.geo.document.size.height - ) - ) { - coord.top = event.pageY + offset[1]; + // if it overflows the viewport on top + if (coord.top < this.__helper.geo.window.scroll.top) { + + // if the tooltip overflows the document at the top + if ( coord.top < 0 + // if there is more space in the viewport below the pointer and that it won't + // overflow the document, switch to the bottom. In the latter case, it might + // seem odd not to switch to the bottom while there is more space, but the + // idea is that the user couldn't close the tooltip, scroll down and try to + // open it again, whereas he can do that at the top + || ( pointerViewportY < this.__helper.geo.window.size.height - pointerViewportY + && event.pageY + offset[1] + this.__size.height <= this.__helper.geo.document.size.height + ) + ) { + coord.top = event.pageY + offset[1]; + } } } - } - // similar logic - else { - - var coordBottom = coord.top + this.__size.height; - - // if it overflows at the bottom - if (coordBottom > this.__helper.geo.window.scroll.top + this.__helper.geo.window.size.height) { + // similar logic + else { - // if there is more space above the pointer or if it overflows the document - if ( pointerViewportY > this.__helper.geo.window.size.height - pointerViewportY - || pointerViewportY - offset[1] + this.__size.height <= this.__helper.geo.document.size.height - ) { - - // move it unless it would overflow the document at the top too - var coordTop = event.pageY + offset[1] - this.__size.height; + var coordBottom = coord.top + this.__size.height; + + // if it overflows at the bottom + if (coordBottom > this.__helper.geo.window.scroll.top + this.__helper.geo.window.size.height) { - if (coordTop >= 0) { - coord.top = coordTop; + // if there is more space above the pointer or if it overflows the document + if ( pointerViewportY > this.__helper.geo.window.size.height - pointerViewportY + || pointerViewportY - offset[1] + this.__size.height <= this.__helper.geo.document.size.height + ) { + + // move it unless it would overflow the document at the top too + var coordTop = event.pageY + offset[1] - this.__size.height; + + if (coordTop >= 0) { + coord.top = coordTop; + } } } } } + + // ignore the scroll distance if the origin is fixed + if (this.__helper.geo.origin.fixedLineage) { + coord.left -= this.__helper.geo.window.scroll.left; + coord.top -= this.__helper.geo.window.scroll.top; + } + + var position = { coord: coord }; + + this.__instance._trigger({ + edit: function(p) { + position = p; + }, + event: event, + helper: this.__helper, + position: $.extend(true, {}, position), + type: 'follow' + }); + + this.__instance._$tooltip + .css({ + left: position.coord.left, + top: position.coord.top + }) + .show(); } - - // ignore the scroll distance if the origin is fixed - if (this.__helper.geo.origin.fixedLineage) { - coord.left -= this.__helper.geo.window.scroll.left; - coord.top -= this.__helper.geo.window.scroll.top; + else { + // hide until a mouse event happens + this.__instance._$tooltip + .hide(); } - var position = { coord: coord }; - - this.__instance._trigger({ - edit: function(p) { - position = p; - }, - event: event, - helper: this.__helper, - position: $.extend(true, {}, position), - type: 'follow' - }); - - this.__instance._$tooltip - .css({ - left: position.coord.left, - top: position.coord.top - }) - .show(); + return this; }, /** * (Re)compute this.__options from the options declared to the instance + * + * @return {self} + * @private */ __optionsFormat: function() { this.__options = this.__instance._optionsExtract(pluginName, this.__defaults()); + return this; }, /** @@ -457,6 +497,9 @@ $.tooltipster._plugin({ * (there can be many reasons for that). Tooltipster does not take mouse moves * into account, for that we have our own listeners that will adjust the * position (see __follow()) + * + * @return {self} + * @private */ __reposition: function(event, helper) { @@ -506,16 +549,10 @@ $.tooltipster._plugin({ width: position.size.width }); - // if an event triggered this method, we can tell where the mouse is. - // Otherwise, it's a method call (which is a bit weird) - if (event) { - self.__follow(event); - } - else { - // hide until a mouse event fires __follow() - self.__instance._$tooltip - .hide(); - } + // reposition. We don't pass the event as it may be stale if it's the mouseenter + // event that initially started an opening delay. We rely on the events we + // recorded ourselves instead. + self.__follow(); // append the tooltip HTML element to its parent self.__instance._$tooltip.appendTo(self.__instance.option('parent')); @@ -536,6 +573,8 @@ $.tooltipster._plugin({ size: position.size } }); + + return this; } } }); diff --git a/dist/js/tooltipster-follower.min.js b/dist/js/tooltipster-follower.min.js index dae5d0e..475276b 100644 --- a/dist/js/tooltipster-follower.min.js +++ b/dist/js/tooltipster-follower.min.js @@ -1 +1 @@ -/* tooltipster-follower v0.1.0 */!function(a,b){"function"==typeof define&&define.amd?define(["tooltipster"],function(a){return b(a)}):"object"==typeof exports?module.exports=b(require("tooltipster")):b(jQuery)}(this,function(a){var b="laa.follower";return a.tooltipster._plugin({name:b,instance:{__defaults:function(){return{anchor:"top-left",maxWidth:null,minWidth:0,offset:[15,-15]}},__init:function(a){var b=this;b.__helper,b.__initialROS=a.option("repositionOnScroll"),b.__instance=a,b.__latestMousemove,b.__namespace="tooltipster-follower-"+Math.round(1e6*Math.random()),b.__pointerPosition,b.__previousState="closed",b.__size,b.__options,b.__initialROS||b.__instance.option("repositionOnScroll",!0),b.__optionsFormat(),b.__instance._on("destroyed."+b.__namespace,function(){b.__destroy()}),b.__instance._on("options."+b.__namespace,function(){b.__optionsFormat()}),b.__instance._on("reposition."+b.__namespace,function(a){b.__reposition(a.event,a.helper)}),b.__instance._on("start."+b.__namespace,function(a){b.__instance._$origin.on("mousemove."+b.__namespace,function(a){b.__latestMousemove=a})}),b.__instance._one("startend."+b.__namespace+" startcancel."+b.__namespace,function(a){b.__instance._$origin.off("mousemove."+b.__namespace),"startcancel"==a.type&&(b.__latestMousemove=null)}),b.__instance._on("state."+b.__namespace,function(a){"closed"==a.state?b.__close():"appearing"==a.state&&"closed"==b.__previousState&&b.__create(),b.__previousState=a.state})},__close:function(){"object"==typeof this.__instance.content()&&null!==this.__instance.content()&&this.__instance.content().detach(),this.__instance._$tooltip.remove(),this.__instance._$tooltip=null,a(a.tooltipster._env.window.document).off("."+this.__namespace)},__create:function(){var b=this,c=a('
');b.__options.minWidth&&c.css("min-width",b.__options.minWidth+"px"),b.__options.maxWidth&&c.css("max-width",b.__options.maxWidth+"px"),b.__instance._$tooltip=c,a(a.tooltipster._env.window.document).on("mousemove."+b.__namespace,function(a){b.__follow(a)}),b.__instance._trigger("created")},__destroy:function(){this.__instance._off("."+this.__namespace),this.__initialROS||this.__instance.option("repositionOnScroll",!1)},__follow:function(b){this.__latestMousemove&&(b=this.__latestMousemove,this.__latestMousemove=null);var c={},d=this.__options.anchor,e=a.merge([],this.__options.offset);switch(this.__helper.geo.window.scroll={left:a.tooltipster._env.window.scrollX||a.tooltipster._env.window.document.documentElement.scrollLeft,top:a.tooltipster._env.window.scrollY||a.tooltipster._env.window.document.documentElement.scrollTop},d){case"top-left":case"left-center":case"bottom-left":c.left=b.pageX+e[0];break;case"top-center":case"bottom-center":c.left=b.pageX+e[0]-this.__size.width/2;break;case"top-right":case"right-center":case"bottom-right":c.left=b.pageX+e[0]-this.__size.width;break;default:console.log("Wrong anchor value")}switch(d){case"top-left":case"top-center":case"top-right":c.top=b.pageY-e[1];break;case"left-center":case"right-center":c.top=b.pageY-e[1]-this.__size.height/2;break;case"bottom-left":case"bottom-center":case"bottom-right":c.top=b.pageY-e[1]-this.__size.height}if("left-center"==d||"right-center"==d){if("right-center"==d)c.leftthis.__helper.geo.window.scroll.left+this.__helper.geo.window.size.width){var f=b.pageX-e[0]-this.__size.width;f>=0?(d="right-center",e[0]=-e[0],c.left=f):(d="top-left",e[1]=-e[0],c={left:b.pageX+e[0],top:b.pageY-e[1]})}c.top+this.__size.height>this.__helper.geo.window.scroll.top+this.__helper.geo.window.size.height&&(c.top=this.__helper.geo.window.scroll.top+this.__helper.geo.window.size.height-this.__size.height),c.topthis.__helper.geo.document.size.height&&(c.top=this.__helper.geo.document.size.height-this.__size.height),c.top<0&&(c.top=0)}if("left-center"!=d&&"right-center"!=d){c.left+this.__size.width>this.__helper.geo.window.scroll.left+this.__helper.geo.window.size.width&&(c.left=this.__helper.geo.window.scroll.left+this.__helper.geo.window.size.width-this.__size.width),c.left<0&&(c.left=0);var g=b.pageY-this.__helper.geo.window.scroll.top;if(0==d.indexOf("bottom"))c.topthis.__helper.geo.window.scroll.top+this.__helper.geo.window.size.height&&(g>this.__helper.geo.window.size.height-g||g-e[1]+this.__size.height<=this.__helper.geo.document.size.height)){var i=b.pageY+e[1]-this.__size.height;i>=0&&(c.top=i)}}}this.__helper.geo.origin.fixedLineage&&(c.left-=this.__helper.geo.window.scroll.left,c.top-=this.__helper.geo.window.scroll.top);var j={coord:c};this.__instance._trigger({edit:function(a){j=a},event:b,helper:this.__helper,position:a.extend(!0,{},j),type:"follow"}),this.__instance._$tooltip.css({left:j.coord.left,top:j.coord.top}).show()},__optionsFormat:function(){this.__options=this.__instance._optionsExtract(b,this.__defaults())},__reposition:function(b,c){var d=this,e=d.__instance._$tooltip.clone(),f=a.tooltipster._getRuler(e),g=f.free().measure(),h={size:g.size};c.geo.origin.fixedLineage?d.__instance._$tooltip.css("position","fixed"):d.__instance._$tooltip.css("position",""),d.__instance._trigger({edit:function(a){h=a},event:b,helper:c,position:a.extend(!0,{},h),tooltipClone:e[0],type:"position"}),f.destroy(),d.__helper=c,d.__size=h.size,d.__instance._$tooltip.css({height:h.size.height,width:h.size.width}),b?d.__follow(b):d.__instance._$tooltip.hide(),d.__instance._$tooltip.appendTo(d.__instance.option("parent")),d.__instance._trigger({type:"repositioned",event:b,position:{coord:{left:0,top:0},size:h.size}})}}}),a}); \ No newline at end of file +/* tooltipster-follower v0.1.0 */!function(a,b){"function"==typeof define&&define.amd?define(["tooltipster"],function(a){return b(a)}):"object"==typeof exports?module.exports=b(require("tooltipster")):b(jQuery)}(this,function(a){var b="laa.follower";return a.tooltipster._plugin({name:b,instance:{__defaults:function(){return{anchor:"top-left",maxWidth:null,minWidth:0,offset:[15,-15]}},__init:function(a){var b=this;return b.__helper,b.__initialROS=a.option("repositionOnScroll"),b.__instance=a,b.__latestMouseEvent,b.__namespace="tooltipster-follower-"+Math.round(1e6*Math.random()),b.__pointerPosition,b.__previousState="closed",b.__size,b.__options,b.__initialROS||b.__instance.option("repositionOnScroll",!0),b.__optionsFormat(),b.__instance._on("destroyed."+b.__namespace,function(){b.__destroy()}),b.__instance._on("options."+b.__namespace,function(){b.__optionsFormat()}),b.__instance._on("reposition."+b.__namespace,function(a){b.__reposition(a.event,a.helper)}),b.__instance._on("start."+b.__namespace,function(a){b.__instance._$origin.on("mousemove."+b.__namespace,function(a){b.__latestMouseEvent=a})}),b.__instance._one("startend."+b.__namespace+" startcancel."+b.__namespace,function(a){b.__instance._$origin.off("mousemove."+b.__namespace),"startcancel"==a.type&&(b.__latestMouseEvent=null)}),b.__instance._on("state."+b.__namespace,function(a){"closed"==a.state?b.__close():"appearing"==a.state&&"closed"==b.__previousState&&b.__create(),b.__previousState=a.state}),b},__close:function(){return"object"==typeof this.__instance.content()&&null!==this.__instance.content()&&this.__instance.content().detach(),this.__instance._$tooltip.remove(),this.__instance._$tooltip=null,a(a.tooltipster._env.window.document).off("."+this.__namespace),this.__latestMouseEvent=null,this},__create:function(){var b=this,c=a('
');return b.__options.minWidth&&c.css("min-width",b.__options.minWidth+"px"),b.__options.maxWidth&&c.css("max-width",b.__options.maxWidth+"px"),b.__instance._$tooltip=c,a(a.tooltipster._env.window.document).on("mousemove."+b.__namespace,function(a){b.__follow(a)}),b.__instance._trigger("created"),b},__destroy:function(){return this.__instance._off("."+this.__namespace),this.__initialROS||this.__instance.option("repositionOnScroll",!1),this},__follow:function(b){if(b?this.__latestMouseEvent=b:this.__latestMouseEvent&&(b=this.__latestMouseEvent),b){var c={},d=this.__options.anchor,e=a.merge([],this.__options.offset);switch(this.__helper.geo.window.scroll={left:a.tooltipster._env.window.scrollX||a.tooltipster._env.window.document.documentElement.scrollLeft,top:a.tooltipster._env.window.scrollY||a.tooltipster._env.window.document.documentElement.scrollTop},d){case"top-left":case"left-center":case"bottom-left":c.left=b.pageX+e[0];break;case"top-center":case"bottom-center":c.left=b.pageX+e[0]-this.__size.width/2;break;case"top-right":case"right-center":case"bottom-right":c.left=b.pageX+e[0]-this.__size.width;break;default:console.log("Wrong anchor value")}switch(d){case"top-left":case"top-center":case"top-right":c.top=b.pageY-e[1];break;case"left-center":case"right-center":c.top=b.pageY-e[1]-this.__size.height/2;break;case"bottom-left":case"bottom-center":case"bottom-right":c.top=b.pageY-e[1]-this.__size.height}if("left-center"==d||"right-center"==d){if("right-center"==d)c.leftthis.__helper.geo.window.scroll.left+this.__helper.geo.window.size.width){var f=b.pageX-e[0]-this.__size.width;f>=0?(d="right-center",e[0]=-e[0],c.left=f):(d="top-left",e[1]=-e[0],c={left:b.pageX+e[0],top:b.pageY-e[1]})}c.top+this.__size.height>this.__helper.geo.window.scroll.top+this.__helper.geo.window.size.height&&(c.top=this.__helper.geo.window.scroll.top+this.__helper.geo.window.size.height-this.__size.height),c.topthis.__helper.geo.document.size.height&&(c.top=this.__helper.geo.document.size.height-this.__size.height),c.top<0&&(c.top=0)}if("left-center"!=d&&"right-center"!=d){c.left+this.__size.width>this.__helper.geo.window.scroll.left+this.__helper.geo.window.size.width&&(c.left=this.__helper.geo.window.scroll.left+this.__helper.geo.window.size.width-this.__size.width),c.left<0&&(c.left=0);var g=b.pageY-this.__helper.geo.window.scroll.top;if(0==d.indexOf("bottom"))c.topthis.__helper.geo.window.scroll.top+this.__helper.geo.window.size.height&&(g>this.__helper.geo.window.size.height-g||g-e[1]+this.__size.height<=this.__helper.geo.document.size.height)){var i=b.pageY+e[1]-this.__size.height;i>=0&&(c.top=i)}}}this.__helper.geo.origin.fixedLineage&&(c.left-=this.__helper.geo.window.scroll.left,c.top-=this.__helper.geo.window.scroll.top);var j={coord:c};this.__instance._trigger({edit:function(a){j=a},event:b,helper:this.__helper,position:a.extend(!0,{},j),type:"follow"}),this.__instance._$tooltip.css({left:j.coord.left,top:j.coord.top}).show()}else this.__instance._$tooltip.hide();return this},__optionsFormat:function(){return this.__options=this.__instance._optionsExtract(b,this.__defaults()),this},__reposition:function(b,c){var d=this,e=d.__instance._$tooltip.clone(),f=a.tooltipster._getRuler(e),g=f.free().measure(),h={size:g.size};return c.geo.origin.fixedLineage?d.__instance._$tooltip.css("position","fixed"):d.__instance._$tooltip.css("position",""),d.__instance._trigger({edit:function(a){h=a},event:b,helper:c,position:a.extend(!0,{},h),tooltipClone:e[0],type:"position"}),f.destroy(),d.__helper=c,d.__size=h.size,d.__instance._$tooltip.css({height:h.size.height,width:h.size.width}),d.__follow(),d.__instance._$tooltip.appendTo(d.__instance.option("parent")),d.__instance._trigger({type:"repositioned",event:b,position:{coord:{left:0,top:0},size:h.size}}),this}}}),a}); \ No newline at end of file diff --git a/src/js/tooltipster-follower.js b/src/js/tooltipster-follower.js index c8a0325..138740f 100644 --- a/src/js/tooltipster-follower.js +++ b/src/js/tooltipster-follower.js @@ -5,6 +5,7 @@ $.tooltipster._plugin({ instance: { /** * @return {object} An object with the defaults options + * @private */ __defaults: function() { @@ -18,8 +19,10 @@ $.tooltipster._plugin({ /** * Run once at instantiation of the plugin - * + * * @param {object} instance The tooltipster object that instantiated this plugin + * @return {self} + * @private */ __init: function(instance) { @@ -31,7 +34,7 @@ $.tooltipster._plugin({ // the inition repositionOnScroll option value self.__initialROS = instance.option('repositionOnScroll'); self.__instance = instance; - self.__latestMousemove; + self.__latestMouseEvent; self.__namespace = 'tooltipster-follower-'+ Math.round(Math.random()*1000000); self.__pointerPosition; self.__previousState = 'closed'; @@ -67,7 +70,7 @@ $.tooltipster._plugin({ self.__instance._on('start.'+ self.__namespace, function(event) { self.__instance._$origin.on('mousemove.'+ self.__namespace, function(e) { - self.__latestMousemove = e; + self.__latestMouseEvent = e; }); }); @@ -78,7 +81,7 @@ $.tooltipster._plugin({ // forget the event if (event.type == 'startcancel') { - self.__latestMousemove = null; + self.__latestMouseEvent = null; } }); @@ -93,10 +96,15 @@ $.tooltipster._plugin({ self.__previousState = event.state; }); + + return self; }, /** * Called when the tooltip has closed + * + * @return {self} + * @private */ __close: function() { @@ -112,13 +120,19 @@ $.tooltipster._plugin({ // stop listening to mouse moves $($.tooltipster._env.window.document).off('.'+ this.__namespace); + + // reset the event + this.__latestMouseEvent = null; + + return this; }, /** * Contains the HTML markup of the tooltip and the bindings the should * exist as long as the tooltip is open * - * @return {object} The tooltip, as a jQuery-wrapped HTML element + * @return {self} + * @private */ __create: function() { @@ -149,10 +163,15 @@ $.tooltipster._plugin({ // tell the instance that the tooltip element has been created self.__instance._trigger('created'); + + return self; }, /** * Called upon the destruction of the tooltip or the destruction of the plugin + * + * @return {self} + * @private */ __destroy: function() { @@ -161,6 +180,8 @@ $.tooltipster._plugin({ if (!this.__initialROS) { this.__instance.option('repositionOnScroll', false); } + + return this; }, /** @@ -169,265 +190,284 @@ $.tooltipster._plugin({ * Note: this is less "smart" than sideTip, which tests scenarios before choosing one. * Here we have to be fast so the moving animation can stay fluid. So there will be no * constrained widths for example. + * + * @return {self} + * @private */ __follow: function(event) { - // use the latest mousemove event if we were recording them before the tooltip was - // opened, and then let it go (see the comment on the `start` listener). - if (this.__latestMousemove) { - event = this.__latestMousemove; - this.__latestMousemove = null; + // store the event in case it's a method call that triggers this method next time, + // or use the latest mousemove event if we have one. + if (event) { + this.__latestMouseEvent = event; } - - var coord = {}, - anchor = this.__options.anchor, - offset = $.merge([], this.__options.offset); - - // the scroll data of the helper must be updated manually on mousemove when the - // origin is fixed, because Tooltipster will not call __reposition on scroll, so - // it's out of date. Even though the tooltip will be fixed too, we need to know - // the scroll distance to determine the position of the pointer relatively to the - // viewport - this.__helper.geo.window.scroll = { - left: $.tooltipster._env.window.scrollX || $.tooltipster._env.window.document.documentElement.scrollLeft, - top: $.tooltipster._env.window.scrollY || $.tooltipster._env.window.document.documentElement.scrollTop - }; - - // coord left - switch (anchor) { - - case 'top-left': - case 'left-center': - case 'bottom-left': - coord.left = event.pageX + offset[0]; - break; - - case 'top-center': - case 'bottom-center': - coord.left = event.pageX + offset[0] - this.__size.width / 2; - break; - - case 'top-right': - case 'right-center': - case 'bottom-right': - coord.left = event.pageX + offset[0] - this.__size.width; - break; - - default: - console.log('Wrong anchor value'); - break; + else if (this.__latestMouseEvent) { + event = this.__latestMouseEvent; } - // coord top - switch (anchor) { + if (event) { - case 'top-left': - case 'top-center': - case 'top-right': - // minus because the Y axis is reversed (pos above the X axis, neg below) - coord.top = event.pageY - offset[1]; - break; + var coord = {}, + anchor = this.__options.anchor, + offset = $.merge([], this.__options.offset); - case 'left-center': - case 'right-center': - coord.top = event.pageY - offset[1] - this.__size.height / 2; - break; + // the scroll data of the helper must be updated manually on mousemove when the + // origin is fixed, because Tooltipster will not call __reposition on scroll, so + // it's out of date. Even though the tooltip will be fixed too, we need to know + // the scroll distance to determine the position of the pointer relatively to the + // viewport + this.__helper.geo.window.scroll = { + left: $.tooltipster._env.window.scrollX || $.tooltipster._env.window.document.documentElement.scrollLeft, + top: $.tooltipster._env.window.scrollY || $.tooltipster._env.window.document.documentElement.scrollTop + }; - case 'bottom-left': - case 'bottom-center': - case 'bottom-right': - coord.top = event.pageY - offset[1] - this.__size.height; - break; - } - - // if the tooltip does not fit on the given side, see if it could fit on the - // opposite one, otherwise put at the bottom (which may be moved again to the - // top by the rest of the script below) - if ( anchor == 'left-center' - || anchor == 'right-center' - ){ + // coord left + switch (anchor) { + + case 'top-left': + case 'left-center': + case 'bottom-left': + coord.left = event.pageX + offset[0]; + break; + + case 'top-center': + case 'bottom-center': + coord.left = event.pageX + offset[0] - this.__size.width / 2; + break; + + case 'top-right': + case 'right-center': + case 'bottom-right': + coord.left = event.pageX + offset[0] - this.__size.width; + break; + + default: + console.log('Wrong anchor value'); + break; + } - // if the tooltip is on the left of the cursor - if (anchor == 'right-center') { + // coord top + switch (anchor) { - // if it overflows the viewport on the left side - if (coord.left < this.__helper.geo.window.scroll.left) { + case 'top-left': + case 'top-center': + case 'top-right': + // minus because the Y axis is reversed (pos above the X axis, neg below) + coord.top = event.pageY - offset[1]; + break; + + case 'left-center': + case 'right-center': + coord.top = event.pageY - offset[1] - this.__size.height / 2; + break; + + case 'bottom-left': + case 'bottom-center': + case 'bottom-right': + coord.top = event.pageY - offset[1] - this.__size.height; + break; + } + + // if the tooltip does not fit on the given side, see if it could fit on the + // opposite one, otherwise put at the bottom (which may be moved again to the + // top by the rest of the script below) + if ( anchor == 'left-center' + || anchor == 'right-center' + ){ + + // if the tooltip is on the left of the cursor + if (anchor == 'right-center') { - // if it wouldn't overflow on the right - if (event.pageX - offset[0] + this.__size.width <= this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width) { + // if it overflows the viewport on the left side + if (coord.left < this.__helper.geo.window.scroll.left) { - // move to the right - anchor = 'left-center'; - // reverse the offset as well - offset[0] = -offset[0]; - coord.left = event.pageX + offset[0]; + // if it wouldn't overflow on the right + if (event.pageX - offset[0] + this.__size.width <= this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width) { + + // move to the right + anchor = 'left-center'; + // reverse the offset as well + offset[0] = -offset[0]; + coord.left = event.pageX + offset[0]; + } + else { + // move to the bottom left + anchor = 'top-right'; + // we'll use the X offset to move the tooltip on the Y axis. Maybe + // we'll make this configurable at some point + offset[1] = offset[0]; + coord = { + left: 0, + top: event.pageY - offset[1] + }; + } } - else { - // move to the bottom left - anchor = 'top-right'; - // we'll use the X offset to move the tooltip on the Y axis. Maybe - // we'll make this configurable at some point - offset[1] = offset[0]; - coord = { - left: 0, - top: event.pageY - offset[1] - }; + } + else { + + // if it overflows the viewport on the right side + if (coord.left + this.__size.width > this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width) { + + var coordLeft = event.pageX - offset[0] - this.__size.width; + + // if it wouldn't overflow on the left + if (coordLeft >= 0) { + + // move to the left + anchor = 'right-center'; + // reverse the offset as well + offset[0] = -offset[0]; + coord.left = coordLeft; + } + else { + // move to the bottom right + anchor = 'top-left'; + offset[1] = -offset[0]; + coord = { + left: event.pageX + offset[0], + top: event.pageY - offset[1] + }; + } } } - } - else { - // if it overflows the viewport on the right side - if (coord.left + this.__size.width > this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width) { + // if it overflows the viewport at the bottom + if (coord.top + this.__size.height > this.__helper.geo.window.scroll.top + this.__helper.geo.window.size.height) { - var coordLeft = event.pageX - offset[0] - this.__size.width; + // move up + coord.top = this.__helper.geo.window.scroll.top + this.__helper.geo.window.size.height - this.__size.height; + } + // if it overflows the viewport at the top + if (coord.top < this.__helper.geo.window.scroll.top) { - // if it wouldn't overflow on the left - if (coordLeft >= 0) { - - // move to the left - anchor = 'right-center'; - // reverse the offset as well - offset[0] = -offset[0]; - coord.left = coordLeft; - } - else { - // move to the bottom right - anchor = 'top-left'; - offset[1] = -offset[0]; - coord = { - left: event.pageX + offset[0], - top: event.pageY - offset[1] - }; - } + // move down + coord.top = this.__helper.geo.window.scroll.top; + } + // if it overflows the document at the bottom + if (coord.top + this.__size.height > this.__helper.geo.document.size.height) { + + // move up + coord.top = this.__helper.geo.document.size.height - this.__size.height; + } + // if it overflows the document at the top + if (coord.top < 0) { + + // no top document overflow + coord.top = 0; } } - // if it overflows the viewport at the bottom - if (coord.top + this.__size.height > this.__helper.geo.window.scroll.top + this.__helper.geo.window.size.height) { + // when the tooltip is not on a side, it may freely move horizontally because + // it won't go under the pointer + if ( anchor != 'left-center' + && anchor != 'right-center' + ){ - // move up - coord.top = this.__helper.geo.window.scroll.top + this.__helper.geo.window.size.height - this.__size.height; - } - // if it overflows the viewport at the top - if (coord.top < this.__helper.geo.window.scroll.top) { + // left and right overflow - // move down - coord.top = this.__helper.geo.window.scroll.top; - } - // if it overflows the document at the bottom - if (coord.top + this.__size.height > this.__helper.geo.document.size.height) { + if (coord.left + this.__size.width > this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width) { + coord.left = this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width - this.__size.width; + } - // move up - coord.top = this.__helper.geo.document.size.height - this.__size.height; - } - // if it overflows the document at the top - if (coord.top < 0) { + // don't ever let document overflow on the left, only on the right, so the user + // can scroll. Note: right overflow should not happen often because when + // measuring the natural width, text is already broken to fit into the viewport. + if (coord.left < 0) { + coord.left = 0; + } - // no top document overflow - coord.top = 0; - } - } - - // when the tooltip is not on a side, it may freely move horizontally because - // it won't go under the pointer - if ( anchor != 'left-center' - && anchor != 'right-center' - ){ - - // left and right overflow - - if (coord.left + this.__size.width > this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width) { - coord.left = this.__helper.geo.window.scroll.left + this.__helper.geo.window.size.width - this.__size.width; - } - - // don't ever let document overflow on the left, only on the right, so the user - // can scroll. Note: right overflow should not happen often because when - // measuring the natural width, text is already broken to fit into the viewport. - if (coord.left < 0) { - coord.left = 0; - } - - // top and bottom overflow - - var pointerViewportY = event.pageY - this.__helper.geo.window.scroll.top; - - // if the tooltip is above the pointer - if (anchor.indexOf('bottom') == 0) { + // top and bottom overflow - // if it overflows the viewport on top - if (coord.top < this.__helper.geo.window.scroll.top) { + var pointerViewportY = event.pageY - this.__helper.geo.window.scroll.top; + + // if the tooltip is above the pointer + if (anchor.indexOf('bottom') == 0) { - // if the tooltip overflows the document at the top - if ( coord.top < 0 - // if there is more space in the viewport below the pointer and that it won't - // overflow the document, switch to the bottom. In the latter case, it might - // seem odd not to switch to the bottom while there is more space, but the - // idea is that the user couldn't close the tooltip, scroll down and try to - // open it again, whereas he can do that at the top - || ( pointerViewportY < this.__helper.geo.window.size.height - pointerViewportY - && event.pageY + offset[1] + this.__size.height <= this.__helper.geo.document.size.height - ) - ) { - coord.top = event.pageY + offset[1]; + // if it overflows the viewport on top + if (coord.top < this.__helper.geo.window.scroll.top) { + + // if the tooltip overflows the document at the top + if ( coord.top < 0 + // if there is more space in the viewport below the pointer and that it won't + // overflow the document, switch to the bottom. In the latter case, it might + // seem odd not to switch to the bottom while there is more space, but the + // idea is that the user couldn't close the tooltip, scroll down and try to + // open it again, whereas he can do that at the top + || ( pointerViewportY < this.__helper.geo.window.size.height - pointerViewportY + && event.pageY + offset[1] + this.__size.height <= this.__helper.geo.document.size.height + ) + ) { + coord.top = event.pageY + offset[1]; + } } } - } - // similar logic - else { - - var coordBottom = coord.top + this.__size.height; - - // if it overflows at the bottom - if (coordBottom > this.__helper.geo.window.scroll.top + this.__helper.geo.window.size.height) { + // similar logic + else { - // if there is more space above the pointer or if it overflows the document - if ( pointerViewportY > this.__helper.geo.window.size.height - pointerViewportY - || pointerViewportY - offset[1] + this.__size.height <= this.__helper.geo.document.size.height - ) { - - // move it unless it would overflow the document at the top too - var coordTop = event.pageY + offset[1] - this.__size.height; + var coordBottom = coord.top + this.__size.height; + + // if it overflows at the bottom + if (coordBottom > this.__helper.geo.window.scroll.top + this.__helper.geo.window.size.height) { - if (coordTop >= 0) { - coord.top = coordTop; + // if there is more space above the pointer or if it overflows the document + if ( pointerViewportY > this.__helper.geo.window.size.height - pointerViewportY + || pointerViewportY - offset[1] + this.__size.height <= this.__helper.geo.document.size.height + ) { + + // move it unless it would overflow the document at the top too + var coordTop = event.pageY + offset[1] - this.__size.height; + + if (coordTop >= 0) { + coord.top = coordTop; + } } } } } + + // ignore the scroll distance if the origin is fixed + if (this.__helper.geo.origin.fixedLineage) { + coord.left -= this.__helper.geo.window.scroll.left; + coord.top -= this.__helper.geo.window.scroll.top; + } + + var position = { coord: coord }; + + this.__instance._trigger({ + edit: function(p) { + position = p; + }, + event: event, + helper: this.__helper, + position: $.extend(true, {}, position), + type: 'follow' + }); + + this.__instance._$tooltip + .css({ + left: position.coord.left, + top: position.coord.top + }) + .show(); } - - // ignore the scroll distance if the origin is fixed - if (this.__helper.geo.origin.fixedLineage) { - coord.left -= this.__helper.geo.window.scroll.left; - coord.top -= this.__helper.geo.window.scroll.top; + else { + // hide until a mouse event happens + this.__instance._$tooltip + .hide(); } - var position = { coord: coord }; - - this.__instance._trigger({ - edit: function(p) { - position = p; - }, - event: event, - helper: this.__helper, - position: $.extend(true, {}, position), - type: 'follow' - }); - - this.__instance._$tooltip - .css({ - left: position.coord.left, - top: position.coord.top - }) - .show(); + return this; }, /** * (Re)compute this.__options from the options declared to the instance + * + * @return {self} + * @private */ __optionsFormat: function() { this.__options = this.__instance._optionsExtract(pluginName, this.__defaults()); + return this; }, /** @@ -435,6 +475,9 @@ $.tooltipster._plugin({ * (there can be many reasons for that). Tooltipster does not take mouse moves * into account, for that we have our own listeners that will adjust the * position (see __follow()) + * + * @return {self} + * @private */ __reposition: function(event, helper) { @@ -484,16 +527,10 @@ $.tooltipster._plugin({ width: position.size.width }); - // if an event triggered this method, we can tell where the mouse is. - // Otherwise, it's a method call (which is a bit weird) - if (event) { - self.__follow(event); - } - else { - // hide until a mouse event fires __follow() - self.__instance._$tooltip - .hide(); - } + // reposition. We don't pass the event as it may be stale if it's the mouseenter + // event that initially started an opening delay. We rely on the events we + // recorded ourselves instead. + self.__follow(); // append the tooltip HTML element to its parent self.__instance._$tooltip.appendTo(self.__instance.option('parent')); @@ -514,6 +551,8 @@ $.tooltipster._plugin({ size: position.size } }); + + return this; } } });