/** * Hover balloon on elements without css and images. * * Copyright (c) 2011 Hayato Takenaka * Licensed under the MIT license: * http://www.opensource.org/licenses/mit-license.php * @author: Hayato Takenaka (https://urin.github.io) * @version: 1.1.2 - 2017/04/30 **/ (function($) { //----------------------------------------------------------------------------- // Private //----------------------------------------------------------------------------- // Helper for meta programming const Meta = { pos: $.extend(['top', 'bottom', 'left', 'right'], {camel: ['Top', 'Bottom', 'Left', 'Right']}), size: $.extend(['height', 'width'], {camel: ['Height', 'Width']}), getRelativeNames: function(position) { const idx = { pos: { o: position, // origin f: (position % 2 === 0) ? position + 1 : position - 1, // faced p1: (position % 2 === 0) ? position : position - 1, p2: (position % 2 === 0) ? position + 1 : position, c1: (position < 2) ? 2 : 0, c2: (position < 2) ? 3 : 1 }, size: { p: (position < 2) ? 0 : 1, // parallel c: (position < 2) ? 1 : 0 // cross } }; const names = {}; for(var m1 in idx) { if(!names[m1]) { names[m1] = {}; } for(var m2 in idx[m1]) { names[m1][m2] = Meta[m1][idx[m1][m2]]; if(!names.camel) { names.camel = {}; } if(!names.camel[m1]) { names.camel[m1] = {}; } names.camel[m1][m2] = Meta[m1].camel[idx[m1][m2]]; } } names.isTopLeft = (names.pos.o === names.pos.p1); return names; } }; // Helper class to handle position and size as numerical pixels. function NumericalBoxElement() { this.initialize.apply(this, arguments); } (function() { // Method factories const Methods = { setBorder: function(pos, isVertical) { return function(value) { this.$.css('border-' + pos.toLowerCase() + '-width', value + 'px'); this['border' + pos] = value; return (this.isActive) ? digitalize(this, isVertical) : this; } }, setPosition: function(pos, isVertical) { return function(value) { this.$.css(pos.toLowerCase(), value + 'px'); this[pos.toLowerCase()] = value; return (this.isActive) ? digitalize(this, isVertical) : this; } } }; NumericalBoxElement.prototype = { initialize: function($element) { this.$ = $element; $.extend(true, this, this.$.offset(), {center: {}, inner: {center: {}}}); for(var i = 0; i < Meta.pos.length; i++) { this['border' + Meta.pos.camel[i]] = parseInt(this.$.css('border-' + Meta.pos[i] + '-width')) || 0; } this.active(); }, active: function() { this.isActive = true; digitalize(this); return this; }, inactive: function() { this.isActive = false; return this; } }; for(var i = 0; i < Meta.pos.length; i++) { NumericalBoxElement.prototype['setBorder' + Meta.pos.camel[i]] = Methods.setBorder(Meta.pos.camel[i], (i < 2)); if(i % 2 === 0) { NumericalBoxElement.prototype['set' + Meta.pos.camel[i]] = Methods.setPosition(Meta.pos.camel[i], (i < 2)); } } function digitalize(box, isVertical) { if(isVertical == null) { digitalize(box, true); return digitalize(box, false); } const m = Meta.getRelativeNames((isVertical) ? 0 : 2); box[m.size.p] = box.$['outer' + m.camel.size.p](); box[m.pos.f] = box[m.pos.o] + box[m.size.p]; box.center[m.pos.o] = box[m.pos.o] + box[m.size.p] / 2; box.inner[m.pos.o] = box[m.pos.o] + box['border' + m.camel.pos.o]; box.inner[m.size.p] = box.$['inner' + m.camel.size.p](); box.inner[m.pos.f] = box.inner[m.pos.o] + box.inner[m.size.p]; box.inner.center[m.pos.o] = box.inner[m.pos.f] + box.inner[m.size.p] / 2; return box; } })(); // Adjust position of balloon body function makeupBalloon($target, $balloon, options) { $balloon.stop(true, true); var outerTip, innerTip; const initTipStyle = {position: 'absolute', height: '0', width: '0', border: 'solid 0 transparent'}, target = new NumericalBoxElement($target), balloon = new NumericalBoxElement($balloon); const balloonTop = -options.offsetY + ((options.position && options.position.indexOf('top') >= 0) ? target.top - balloon.height : ((options.position && options.position.indexOf('bottom') >= 0) ? target.bottom : target.center.top - balloon.height / 2)); const balloonLeft = options.offsetX + ((options.position && options.position.indexOf('left') >= 0) ? target.left - balloon.width : ((options.position && options.position.indexOf('right') >= 0) ? target.right : target.center.left - balloon.width / 2)); balloon.setTop(balloonTop < 0 ? 0 : balloonTop); balloon.setLeft(balloonLeft < 0 ? 0 : balloonLeft); if(options.tipSize > 0) { // Add hidden balloon tips into balloon body. if($balloon.data('outerTip')) { $balloon.data('outerTip').remove(); $balloon.removeData('outerTip'); } if($balloon.data('innerTip')) { $balloon.data('innerTip').remove(); $balloon.removeData('innerTip'); } outerTip = new NumericalBoxElement($('