1/** 2 * Hover balloon on elements without css and images. 3 * 4 * Copyright (c) 2011 Hayato Takenaka 5 * Dual licensed under the MIT and GPL licenses: 6 * http://www.opensource.org/licenses/mit-license.php 7 * http://www.gnu.org/licenses/gpl.html 8 * @author: Hayato Takenaka (https://urin.github.io) 9 * @version: 1.0.3 - 2016/07/02 10**/ 11(function($) { 12 //----------------------------------------------------------------------------- 13 // Private 14 //----------------------------------------------------------------------------- 15 // Helper for meta programming 16 const Meta = { 17 pos: $.extend(['top', 'bottom', 'left', 'right'], {camel: ['Top', 'Bottom', 'Left', 'Right']}), 18 size: $.extend(['height', 'width'], {camel: ['Height', 'Width']}), 19 getRelativeNames: function(position) { 20 const idx = { 21 pos: { 22 o: position, // origin 23 f: (position % 2 === 0) ? position + 1 : position - 1, // faced 24 p1: (position % 2 === 0) ? position : position - 1, 25 p2: (position % 2 === 0) ? position + 1 : position, 26 c1: (position < 2) ? 2 : 0, 27 c2: (position < 2) ? 3 : 1 28 }, 29 size: { 30 p: (position < 2) ? 0 : 1, // parallel 31 c: (position < 2) ? 1 : 0 // cross 32 } 33 }; 34 const names = {}; 35 for(var m1 in idx) { 36 if(!names[m1]) { names[m1] = {}; } 37 for(var m2 in idx[m1]) { 38 names[m1][m2] = Meta[m1][idx[m1][m2]]; 39 if(!names.camel) { names.camel = {}; } 40 if(!names.camel[m1]) { names.camel[m1] = {}; } 41 names.camel[m1][m2] = Meta[m1].camel[idx[m1][m2]]; 42 } 43 } 44 names.isTopLeft = (names.pos.o === names.pos.p1); 45 return names; 46 } 47 }; 48 49 // Helper class to handle position and size as numerical pixels. 50 function NumericalBoxElement() { this.initialize.apply(this, arguments); } 51 (function() { 52 // Method factories 53 const Methods = { 54 setBorder: function(pos, isVertical) { 55 return function(value) { 56 this.$.css('border-' + pos.toLowerCase() + '-width', value + 'px'); 57 this['border' + pos] = value; 58 return (this.isActive) ? digitalize(this, isVertical) : this; 59 } 60 }, 61 setPosition: function(pos, isVertical) { 62 return function(value) { 63 this.$.css(pos.toLowerCase(), value + 'px'); 64 this[pos.toLowerCase()] = value; 65 return (this.isActive) ? digitalize(this, isVertical) : this; 66 } 67 } 68 }; 69 70 NumericalBoxElement.prototype = { 71 initialize: function($element) { 72 this.$ = $element; 73 $.extend(true, this, this.$.offset(), {center: {}, inner: {center: {}}}); 74 for(var i = 0; i < Meta.pos.length; i++) { 75 this['border' + Meta.pos.camel[i]] = parseInt(this.$.css('border-' + Meta.pos[i] + '-width')) || 0; 76 } 77 this.active(); 78 }, 79 active: function() { this.isActive = true; digitalize(this); return this; }, 80 inactive: function() { this.isActive = false; return this; } 81 }; 82 for(var i = 0; i < Meta.pos.length; i++) { 83 NumericalBoxElement.prototype['setBorder' + Meta.pos.camel[i]] = Methods.setBorder(Meta.pos.camel[i], (i < 2)); 84 if(i % 2 === 0) { 85 NumericalBoxElement.prototype['set' + Meta.pos.camel[i]] = Methods.setPosition(Meta.pos.camel[i], (i < 2)); 86 } 87 } 88 89 function digitalize(box, isVertical) { 90 if(isVertical == null) { digitalize(box, true); return digitalize(box, false); } 91 const m = Meta.getRelativeNames((isVertical) ? 0 : 2); 92 box[m.size.p] = box.$['outer' + m.camel.size.p](); 93 box[m.pos.f] = box[m.pos.o] + box[m.size.p]; 94 box.center[m.pos.o] = box[m.pos.o] + box[m.size.p] / 2; 95 box.inner[m.pos.o] = box[m.pos.o] + box['border' + m.camel.pos.o]; 96 box.inner[m.size.p] = box.$['inner' + m.camel.size.p](); 97 box.inner[m.pos.f] = box.inner[m.pos.o] + box.inner[m.size.p]; 98 box.inner.center[m.pos.o] = box.inner[m.pos.f] + box.inner[m.size.p] / 2; 99 return box; 100 } 101 })(); 102 103 // Adjust position of balloon body 104 function makeupBalloon($target, $balloon, options) { 105 $balloon.stop(true, true); 106 var outerTip, innerTip; 107 const initTipStyle = {position: 'absolute', height: '0', width: '0', border: 'solid 0 transparent'}, 108 target = new NumericalBoxElement($target), 109 balloon = new NumericalBoxElement($balloon); 110 balloon.setTop(-options.offsetY 111 + ((options.position && options.position.indexOf('top') >= 0) ? target.top - balloon.height 112 : ((options.position && options.position.indexOf('bottom') >= 0) ? target.bottom 113 : target.center.top - balloon.height / 2))); 114 balloon.setLeft(options.offsetX 115 + ((options.position && options.position.indexOf('left') >= 0) ? target.left - balloon.width 116 : ((options.position && options.position.indexOf('right') >= 0) ? target.right 117 : target.center.left - balloon.width / 2))); 118 if(options.tipSize > 0) { 119 // Add hidden balloon tips into balloon body. 120 if($balloon.data('outerTip')) { $balloon.data('outerTip').remove(); $balloon.removeData('outerTip'); } 121 if($balloon.data('innerTip')) { $balloon.data('innerTip').remove(); $balloon.removeData('innerTip'); } 122 outerTip = new NumericalBoxElement($('<div>').css(initTipStyle).appendTo($balloon)); 123 innerTip = new NumericalBoxElement($('<div>').css(initTipStyle).appendTo($balloon)); 124 // Make tip triangle, adjust position of tips. 125 var m; 126 for(var i = 0; i < Meta.pos.length; i++) { 127 m = Meta.getRelativeNames(i); 128 if(balloon.center[m.pos.c1] >= target[m.pos.c1] && 129 balloon.center[m.pos.c1] <= target[m.pos.c2]) { 130 if(i % 2 === 0) { 131 if(balloon[m.pos.o] >= target[m.pos.o] && balloon[m.pos.f] >= target[m.pos.f]) { break; } 132 } else { 133 if(balloon[m.pos.o] <= target[m.pos.o] && balloon[m.pos.f] <= target[m.pos.f]) { break; } 134 } 135 } 136 m = null; 137 } 138 if(m) { 139 balloon['set' + m.camel.pos.p1] 140 (balloon[m.pos.p1] + ((m.isTopLeft) ? 1 : -1) * (options.tipSize - balloon['border' + m.camel.pos.o])); 141 makeTip(balloon, outerTip, m, options.tipSize, $balloon.css('border-' + m.pos.o + '-color'), options.tipPosition); 142 makeTip(balloon, innerTip, m, options.tipSize - 2 * balloon['border' + m.camel.pos.o], $balloon.css('background-color'), options.tipPosition); 143 $balloon.data('outerTip', outerTip.$).data('innerTip', innerTip.$); 144 } else { 145 $.each([outerTip.$, innerTip.$], function() { this.remove(); }); 146 } 147 } 148 // Make up balloon tip. 149 function makeTip(balloon, tip, m, tipSize, color) { 150 const len = Math.round(tipSize / 1.7320508); 151 tip.inactive() 152 ['setBorder' + m.camel.pos.f](tipSize) 153 ['setBorder' + m.camel.pos.c1](len) 154 ['setBorder' + m.camel.pos.c2](len) 155 ['set' + m.camel.pos.p1]((m.isTopLeft) ? -tipSize : balloon.inner[m.size.p]) 156 ['set' + m.camel.pos.c1](balloon.inner[m.size.c] / options.tipPosition - len) 157 .active() 158 .$.css('border-' + m.pos.f + '-color', color); 159 } 160 } 161 162 // True if the event comes from the target or balloon. 163 function isValidTargetEvent($target, e) { 164 const b = $target.data('balloon') && $target.data('balloon').get(0); 165 return !(b && (b === e.relatedTarget || $.contains(b, e.relatedTarget))); 166 } 167 168 //----------------------------------------------------------------------------- 169 // Public 170 //----------------------------------------------------------------------------- 171 $.fn.balloon = function(options) { 172 return this.one('mouseenter', function first(e) { 173 const $target = $(this), t = this 174 const $balloon = $target.on('mouseenter', function(e) { 175 isValidTargetEvent($target, e) && $target.showBalloon(); 176 }).off('mouseenter', first).showBalloon(options).data('balloon'); 177 if($balloon) { 178 $balloon.on('mouseleave', function(e) { 179 if(t === e.relatedTarget || $.contains(t, e.relatedTarget)) { return; } 180 $target.hideBalloon(); 181 }).on('mouseenter', function(e) { 182 if(t === e.relatedTarget || $.contains(t, e.relatedTarget)) { return; } 183 $balloon.stop(true, true); 184 $target.showBalloon(); 185 }); 186 } 187 }).on('mouseleave', function(e) { 188 const $target = $(this); 189 isValidTargetEvent($target, e) && $target.hideBalloon(); 190 }); 191 }; 192 193 $.fn.showBalloon = function(options) { 194 var $target, $balloon; 195 if(options || !this.data('options')) { 196 if($.balloon.defaults.css === null) { $.balloon.defaults.css = {}; } 197 this.data('options', $.extend(true, {}, $.balloon.defaults, options || {})); 198 } 199 options = this.data('options'); 200 return this.each(function() { 201 var isNew; 202 $target = $(this); 203 isNew = !$target.data('balloon'); 204 $balloon = $target.data('balloon') || $('<div>'); 205 if(!isNew && $balloon.data('active')) { return; } 206 $balloon.data('active', true); 207 clearTimeout($balloon.data('minLifetime')); 208 const contents = $.isFunction(options.contents) ? options.contents.apply(this) 209 : (options.contents || $target.attr('title') || $target.attr('alt')); 210 $target.removeAttr('title'); 211 if(!options.url && contents === '' || contents == null) { return; } 212 if(!$.isFunction(options.contents)) { options.contents = contents; } 213 if(options.url) { 214 if(!$balloon.data('ajaxDisabled')) { 215 if(contents !== '' && contents != null) { 216 if(options.html) { 217 $balloon.empty().append(contents); 218 } else { 219 $balloon.text(contents); 220 } 221 } 222 clearTimeout($balloon.data('ajaxDelay')); 223 $balloon.data('ajaxDelay', 224 setTimeout(function() { 225 $balloon.load( 226 $.isFunction(options.url) ? options.url.apply($target.get(0)) : options.url, 227 function(res, sts, xhr) { 228 if(sts !== 'success' && sts !== 'notmodified') { return; } 229 $balloon.data('ajaxDisabled', true); 230 if(options.ajaxContentsMaxAge >= 0) { 231 setTimeout(function() { $balloon.data('ajaxDisabled', false); }, options.ajaxContentsMaxAge); 232 } 233 if(options.ajaxComplete) { options.ajaxComplete(res, sts, xhr); } 234 makeupBalloon($target, $balloon, options); 235 } 236 ); 237 }, options.ajaxDelay) 238 ); 239 } 240 } else { 241 if(options.html) { 242 $balloon.empty().append(contents); 243 } else { 244 $balloon.text(contents); 245 } 246 } 247 if(isNew) { 248 $balloon 249 .addClass(options.classname) 250 .css(options.css || {}) 251 .css({ visibility: 'hidden', position: 'absolute' }) 252 .appendTo('body'); 253 $target.data('balloon', $balloon); 254 makeupBalloon($target, $balloon, options); 255 $balloon.hide().css('visibility', 'visible'); 256 } else { 257 makeupBalloon($target, $balloon, options); 258 } 259 $balloon.data('delay', setTimeout(function() { 260 if(options.showAnimation) { 261 options.showAnimation.apply( 262 $balloon.stop(true, true), [ 263 options.showDuration, function() { 264 options.showComplete && options.showComplete.apply($balloon); 265 } 266 ] 267 ); 268 } else { 269 $balloon.show(options.showDuration, function() { 270 if(this.style.removeAttribute) { this.style.removeAttribute('filter'); } 271 options.showComplete && options.showComplete.apply($balloon); 272 }); 273 } 274 if(options.maxLifetime) { 275 clearTimeout($balloon.data('maxLifetime')); 276 $balloon.data('maxLifetime', 277 setTimeout(function() { $target.hideBalloon(); }, options.maxLifetime) 278 ); 279 } 280 }, options.delay)); 281 }); 282 }; 283 284 $.fn.hideBalloon = function() { 285 const options = this.data('options'); 286 if(!this.data('balloon')) { return this; } 287 return this.each(function() { 288 const $target = $(this), $balloon = $target.data('balloon'); 289 clearTimeout($balloon.data('delay')); 290 clearTimeout($balloon.data('minLifetime')); 291 clearTimeout($balloon.data('ajaxDelay')); 292 $balloon.data('minLifetime', setTimeout(function() { 293 if(options.hideAnimation) { 294 options.hideAnimation.apply( 295 $balloon.stop(true, true), 296 [ 297 options.hideDuration, 298 function(d) { 299 $(this).data('active', false); 300 options.hideComplete && options.hideComplete(d); 301 } 302 ] 303 ); 304 } else { 305 $balloon.stop(true, true).hide( 306 options.hideDuration, 307 function(d) { 308 $(this).data('active', false); 309 options.hideComplete && options.hideComplete(d); 310 } 311 ); 312 } 313 }, 314 options.minLifetime)); 315 }); 316 }; 317 318 $.balloon = { 319 defaults: { 320 contents: null, html: false, classname: null, 321 url: null, ajaxComplete: null, ajaxDelay: 500, ajaxContentsMaxAge: -1, 322 delay: 0, minLifetime: 200, maxLifetime: 0, 323 position: 'top', offsetX: 0, offsetY: 0, tipSize: 8, tipPosition: 2, 324 showDuration: 100, showAnimation: null, 325 hideDuration: 80, hideAnimation: function(d, c) { this.fadeOut(d, c); }, 326 showComplete: null, hideComplete: null, 327 css: { 328 fontSize : '.7rem', 329 minWidth : '.7rem', 330 padding : '.2rem .5rem', 331 border : '1px solid rgba(212, 212, 212, .4)', 332 borderRadius : '3px', 333 boxShadow : '2px 2px 4px #555', 334 color : '#eee', 335 backgroundColor: '#111', 336 opacity : 0.85, 337 zIndex : '32767', 338 textAlign : 'left' 339 } 340 } 341 }; 342})(jQuery); 343 344
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.