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 (http://urin.github.com) 9 * @version: 0.3.5 - 2013/06/26 10**/ 11(function($) { 12 //----------------------------------------------------------------------------- 13 // Private 14 //----------------------------------------------------------------------------- 15 // Helper for meta programming 16 var Meta = {}; 17 Meta.pos = $.extend(["top", "bottom", "left", "right"], {camel: ["Top", "Bottom", "Left", "Right"]}); 18 Meta.size = $.extend(["height", "width"], {camel: ["Height", "Width"]}); 19 Meta.getRelativeNames = function(position) { 20 var 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 var 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 // Helper class to handle position and size as numerical pixels. 49 function NumericalBoxElement() { this.initialize.apply(this, arguments); } 50 (function() { 51 // Method factories 52 var Methods = { 53 setBorder: function(pos, isVertical) { 54 return function(value) { 55 this.$.css("border-" + pos.toLowerCase() + "-width", value + "px"); 56 this["border" + pos] = value; 57 return (this.isActive) ? digitalize(this, isVertical) : this; 58 } 59 }, 60 setPosition: function(pos, isVertical) { 61 return function(value) { 62 this.$.css(pos.toLowerCase(), value + "px"); 63 this[pos.toLowerCase()] = value; 64 return (this.isActive) ? digitalize(this, isVertical) : this; 65 } 66 } 67 }; 68 69 NumericalBoxElement.prototype = { 70 initialize: function($element) { 71 this.$ = $element; 72 $.extend(true, this, this.$.offset(), {center: {}, inner: {center: {}}}); 73 for(var i = 0; i < Meta.pos.length; i++) { 74 this["border" + Meta.pos.camel[i]] = parseInt(this.$.css("border-" + Meta.pos[i] + "-width")) || 0; 75 } 76 this.active(); 77 }, 78 active: function() { this.isActive = true; digitalize(this); return this; }, 79 inactive: function() { this.isActive = false; return this; } 80 }; 81 for(var i = 0; i < Meta.pos.length; i++) { 82 NumericalBoxElement.prototype["setBorder" + Meta.pos.camel[i]] = Methods.setBorder(Meta.pos.camel[i], (i < 2)); 83 if(i % 2 == 0) 84 NumericalBoxElement.prototype["set" + Meta.pos.camel[i]] = Methods.setPosition(Meta.pos.camel[i], (i < 2)); 85 } 86 87 function digitalize(box, isVertical) { 88 if(isVertical == undefined) { digitalize(box, true); return digitalize(box, false); } 89 var m = Meta.getRelativeNames((isVertical) ? 0 : 2); 90 box[m.size.p] = box.$["outer" + m.camel.size.p](); 91 box[m.pos.f] = box[m.pos.o] + box[m.size.p]; 92 box.center[m.pos.o] = box[m.pos.o] + box[m.size.p] / 2; 93 box.inner[m.pos.o] = box[m.pos.o] + box["border" + m.camel.pos.o]; 94 box.inner[m.size.p] = box.$["inner" + m.camel.size.p](); 95 box.inner[m.pos.f] = box.inner[m.pos.o] + box.inner[m.size.p]; 96 box.inner.center[m.pos.o] = box.inner[m.pos.f] + box.inner[m.size.p] / 2; 97 return box; 98 } 99 })(); 100 101 // Adjust position of balloon body 102 function makeupBalloon($target, $balloon, options) { 103 $balloon.stop(true, true); 104 var outerTip, innerTip, 105 initTipStyle = {position: "absolute", height: "0", width: "0", border: "solid 0 transparent"}, 106 target = new NumericalBoxElement($target), 107 balloon = new NumericalBoxElement($balloon); 108 balloon.setTop(-options.offsetY 109 + ((options.position && options.position.indexOf("top") >= 0) ? target.top - balloon.height 110 : ((options.position && options.position.indexOf("bottom") >= 0) ? target.bottom 111 : target.center.top - balloon.height / 2))); 112 balloon.setLeft(options.offsetX 113 + ((options.position && options.position.indexOf("left") >= 0) ? target.left - balloon.width 114 : ((options.position && options.position.indexOf("right") >= 0) ? target.right 115 : target.center.left - balloon.width / 2))); 116 if(options.tipSize > 0) { 117 // Add hidden balloon tips into balloon body. 118 if($balloon.data("outerTip")) { $balloon.data("outerTip").remove(); $balloon.removeData("outerTip"); } 119 if($balloon.data("innerTip")) { $balloon.data("innerTip").remove(); $balloon.removeData("innerTip"); } 120 outerTip = new NumericalBoxElement($("<div>").css(initTipStyle).appendTo($balloon)); 121 innerTip = new NumericalBoxElement($("<div>").css(initTipStyle).appendTo($balloon)); 122 // Make tip triangle, adjust position of tips. 123 var m; 124 for(var i = 0; i < Meta.pos.length; i++) { 125 m = Meta.getRelativeNames(i); 126 if(balloon.center[m.pos.c1] >= target[m.pos.c1] && 127 balloon.center[m.pos.c1] <= target[m.pos.c2]) { 128 if(i % 2 == 0) { 129 if(balloon[m.pos.o] >= target[m.pos.o] && balloon[m.pos.f] >= target[m.pos.f]) break; 130 } else { 131 if(balloon[m.pos.o] <= target[m.pos.o] && balloon[m.pos.f] <= target[m.pos.f]) break; 132 } 133 } 134 m = null; 135 } 136 if(m) { 137 balloon["set" + m.camel.pos.p1] 138 (balloon[m.pos.p1] + ((m.isTopLeft) ? 1 : -1) * (options.tipSize - balloon["border" + m.camel.pos.o])); 139 makeTip(balloon, outerTip, m, options.tipSize, $balloon.css("border-" + m.pos.o + "-color")); 140 makeTip(balloon, innerTip, m, options.tipSize - 2 * balloon["border" + m.camel.pos.o], $balloon.css("background-color")); 141 $balloon.data("outerTip", outerTip.$).data("innerTip", innerTip.$); 142 } else { 143 $.each([outerTip.$, innerTip.$], function() { this.remove(); }); 144 } 145 } 146 // Make up balloon tip. 147 function makeTip(balloon, tip, m, tipSize, color) { 148 var len = Math.round(tipSize / 1.7320508); 149 tip.inactive() 150 ["setBorder" + m.camel.pos.f](tipSize) 151 ["setBorder" + m.camel.pos.c1](len) 152 ["setBorder" + m.camel.pos.c2](len) 153 ["set" + m.camel.pos.p1]((m.isTopLeft) ? -tipSize : balloon.inner[m.size.p]) 154 ["set" + m.camel.pos.c1](balloon.inner[m.size.c] / 2 - len) 155 .active() 156 .$.css("border-" + m.pos.f + "-color", color); 157 } 158 } 159 160 // True if the event comes from the target or balloon. 161 function isValidTargetEvent($target, e) { 162 var b = $target.data("balloon") && $target.data("balloon").get(0); 163 return !(b && (b == e.relatedTarget || $.contains(b, e.relatedTarget))); 164 } 165 166 //----------------------------------------------------------------------------- 167 // Public 168 //----------------------------------------------------------------------------- 169 $.fn.balloon = function(options) { 170 options = $.extend(true, {}, $.balloon.defaults, options); 171 return this.one("mouseenter", function(e) { 172 var $target = $(this), t = this; 173 var $balloon = $target.unbind("mouseenter", arguments.callee) 174 .showBalloon(options).mouseenter(function(e) { 175 isValidTargetEvent($target, e) && $target.showBalloon(); 176 }).data("balloon"); 177 if($balloon) { 178 $balloon.mouseleave(function(e) { 179 if(t == e.relatedTarget || $.contains(t, e.relatedTarget)) return; 180 $target.hideBalloon(); 181 }).mouseenter(function(e) { $target.showBalloon(); }); 182 } 183 }).mouseleave(function(e) { 184 var $target = $(this); 185 isValidTargetEvent($target, e) && $target.hideBalloon(); 186 }); 187 }; 188 189 $.fn.showBalloon = function(options) { 190 var $target, $balloon, offTimer; 191 if(!$.balloon.defaults.css) $.balloon.defaults.css = {}; 192 if(options || !this.data("options")) 193 this.data("options", $.extend(true, {}, $.balloon.defaults, options || {})); 194 options = this.data("options"); 195 return this.each(function() { 196 $target = $(this); 197 (offTimer = $target.data("offTimer")) && clearTimeout(offTimer); 198 var contents = $.isFunction(options.contents) 199 ? options.contents() 200 : (options.contents || $target.attr("title")); 201 var isNew = !($balloon = $target.data("balloon")); 202 if(isNew) $balloon = $("<div>").append(contents); 203 if(!options.url && (!$balloon || $balloon.html() == "")) return; 204 if(!isNew && contents && contents != $balloon.html()) $balloon.empty().append(contents); 205 $target.removeAttr("title"); 206 if(options.url) { 207 $balloon.load($.isFunction(options.url) ? options.url(this) : options.url, function(res, sts, xhr) { 208 if(options.ajaxComplete) options.ajaxComplete(res, sts, xhr); 209 makeupBalloon($target, $balloon, options); 210 }); 211 } 212 if(isNew) { 213 $balloon 214 .addClass(options.classname) 215 .css(options.css) 216 .css({visibility: "hidden", position: "absolute"}) 217 .appendTo("body"); 218 $target.data("balloon", $balloon); 219 makeupBalloon($target, $balloon, options); 220 $balloon.hide().css("visibility", "visible"); 221 } else { 222 makeupBalloon($target, $balloon, options); 223 } 224 $target.data("onTimer", setTimeout(function() { 225 if(options.showAnimation) { 226 options.showAnimation.apply($balloon.stop(true, true), [options.showDuration]); 227 } else { 228 $balloon.show(options.showDuration, function() { 229 if(this.style.removeAttribute) { this.style.removeAttribute("filter"); } 230 });
231 } 232 }, options.delay)); 233 }); 234 }; 235 236 $.fn.hideBalloon = function() { 237 var options = this.data("options"), onTimer, offTimer; 238 if(!this.data("balloon")) return this; 239 return this.each(function() { 240 var $target = $(this); 241 (onTimer = $target.data("onTimer")) && clearTimeout(onTimer); 242 (offTimer = $target.data("offTimer")) && clearTimeout(offTimer); 243 $target.data("offTimer", setTimeout(function() { 244 var $balloon = $target.data("balloon"); 245 if(options.hideAnimation) { 246 $balloon && options.hideAnimation.apply($balloon.stop(true, true), [options.hideDuration]); 247 } else { 248 $balloon && $balloon.stop(true, true).hide(options.hideDuration); 249 } 250 }, 251 options.minLifetime)); 252 }); 253 }; 254 255 $.balloon = { 256 defaults: { 257 contents: null, url: null, ajaxComplete: null, classname: null, 258 position: "top", offsetX: 0, offsetY: 0, tipSize: 12, 259 delay: 0, minLifetime: 200, 260 showDuration: 100, showAnimation: null, 261 hideDuration: 80, hideAnimation: function(d) { this.fadeOut(d); }, 262 css: { 263 minWidth : "20px", 264 padding : "5px", 265 borderRadius : "6px", 266 border : "solid 1px #777", 267 boxShadow : "4px 4px 4px #555", 268 color : "#666", 269 backgroundColor: "#efefef", 270 opacity : ($.support.opacity) ? "0.85" : null, 271 zIndex : "32767", 272 textAlign : "left" 273 } 274 } 275 }; 276})(jQuery);
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.