1 2/** 3 * Hover balloon on elements without css and images. 4 * 5 * Copyright (c) 2011 Hayato Takenaka 6 * Dual licensed under the MIT and GPL licenses: 7 * http://www.opensource.org/licenses/mit-license.php 8 * http://www.gnu.org/licenses/gpl.html 9 * @author: Hayato Takenaka (http://urin.take-uma.net) 10 * @version: 0.3.0 - 2012/02/25 11**/ 12;(function($) { 13 //----------------------------------------------------------------------------- 14 // Private 15 //----------------------------------------------------------------------------- 16 // Helper for meta programming 17 var Meta = {}; 18 Meta.pos = $.extend(["top", "bottom", "left", "right"], {camel: ["Top", "Bottom", "Left", "Right"]}); 19 Meta.size = $.extend(["height", "width"], {camel: ["Height", "Width"]}); 20 Meta.getRelativeNames = function(position) { 21 var idx = { 22 pos: { 23 o: position, // origin 24 f: (position % 2 == 0) ? position + 1 : position - 1, // faced 25 p1: (position % 2 == 0) ? position : position - 1, 26 p2: (position % 2 == 0) ? position + 1 : position, 27 c1: (position < 2) ? 2 : 0, 28 c2: (position < 2) ? 3 : 1 29 }, 30 size: { 31 p: (position < 2) ? 0 : 1, // parallel 32 c: (position < 2) ? 1 : 0 // cross 33 } 34 }; 35 var names = {}; 36 for(var m1 in idx) { 37 if(!names[m1]) names[m1] = {}; 38 for(var m2 in idx[m1]) { 39 names[m1][m2] = Meta[m1][idx[m1][m2]]; 40 if(!names.camel) names.camel = {}; 41 if(!names.camel[m1]) names.camel[m1] = {}; 42 names.camel[m1][m2] = Meta[m1].camel[idx[m1][m2]]; 43 } 44 } 45 names.isTopLeft = (names.pos.o == names.pos.p1); 46 return names; 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 var 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 function digitalize(box, isVertical) { 89 if(isVertical == undefined) { digitalize(box, true); return digitalize(box, false); } 90 var m = Meta.getRelativeNames((isVertical) ? 0 : 2); 91 box[m.size.p] = box.$["outer" + m.camel.size.p](); 92 box[m.pos.f] = box[m.pos.o] + box[m.size.p]; 93 box.center[m.pos.o] = box[m.pos.o] + box[m.size.p] / 2; 94 box.inner[m.pos.o] = box[m.pos.o] + box["border" + m.camel.pos.o]; 95 box.inner[m.size.p] = box.$["inner" + m.camel.size.p](); 96 box.inner[m.pos.f] = box.inner[m.pos.o] + box.inner[m.size.p]; 97 box.inner.center[m.pos.o] = box.inner[m.pos.f] + box.inner[m.size.p] / 2; 98 return box; 99 } 100 })(); 101 102 // Adjust position of balloon body 103 function makeupBalloon($target, $balloon, options) { 104 $balloon.stop(true, true); 105 var outerTip, innerTip, 106 initTipStyle = {position: "absolute", height: "0", width: "0", border: "solid 0 transparent"}, 107 target = new NumericalBoxElement($target), 108 balloon = new NumericalBoxElement($balloon); 109 balloon.setTop(-options.offsetY 110 + ((options.position && options.position.indexOf("top") >= 0) ? target.top - balloon.height 111 : ((options.position && options.position.indexOf("bottom") >= 0) ? target.bottom 112 : target.center.top - balloon.height / 2))); 113 balloon.setLeft(options.offsetX 114 + ((options.position && options.position.indexOf("left") >= 0) ? target.left - balloon.width 115 : ((options.position && options.position.indexOf("right") >= 0) ? target.right 116 : target.center.left - balloon.width / 2))); 117 if(options.tipSize > 0) { 118 // Add hidden balloon tips into balloon body. 119 if($balloon.data("outerTip")) { $balloon.data("outerTip").remove(); $balloon.removeData("outerTip"); } 120 if($balloon.data("innerTip")) { $balloon.data("innerTip").remove(); $balloon.removeData("innerTip"); } 121 outerTip = new NumericalBoxElement($("<div>").css(initTipStyle).appendTo($balloon)); 122 innerTip = new NumericalBoxElement($("<div>").css(initTipStyle).appendTo($balloon)); 123 // Make tip triangle, adjust position of tips. 124 var m; 125 for(var i = 0; i < Meta.pos.length; i++) { 126 m = Meta.getRelativeNames(i); 127 if(balloon.center[m.pos.c1] >= target[m.pos.c1] && 128 balloon.center[m.pos.c1] <= target[m.pos.c2]) { 129 if(i % 2 == 0) { 130 if(balloon[m.pos.o] >= target[m.pos.o] && balloon[m.pos.f] >= target[m.pos.f]) break; 131 } else { 132 if(balloon[m.pos.o] <= target[m.pos.o] && balloon[m.pos.f] <= target[m.pos.f]) break; 133 } 134 } 135 m = null; 136 } 137 if(m) { 138 balloon["set" + m.camel.pos.p1] 139 (balloon[m.pos.p1] + ((m.isTopLeft) ? 1 : -1) * (options.tipSize - balloon["border" + m.camel.pos.o])); 140 makeTip(balloon, outerTip, m, options.tipSize, $balloon.css("border-" + m.pos.o + "-color")); 141 makeTip(balloon, innerTip, m, options.tipSize - 2 * balloon["border" + m.camel.pos.o], $balloon.css("background-color")); 142 $balloon.data("outerTip", outerTip.$).data("innerTip", innerTip.$); 143 } else { 144 $.each([outerTip.$, innerTip.$], function() { this.remove(); }); 145 } 146 } 147 // Make up balloon tip. 148 function makeTip(balloon, tip, m, tipSize, color) { 149 var len = Math.round(tipSize / 1.7320508); 150 tip.inactive() 151 ["setBorder" + m.camel.pos.f](tipSize) 152 ["setBorder" + m.camel.pos.c1](len) 153 ["setBorder" + m.camel.pos.c2](len) 154 ["set" + m.camel.pos.p1]((m.isTopLeft) ? -tipSize : balloon.inner[m.size.p]) 155 ["set" + m.camel.pos.c1](balloon.inner[m.size.c] / 2 - len) 156 .active() 157 .$.css("border-" + m.pos.f + "-color", color); 158 } 159 } 160 161 // True if the event comes from the target or balloon. 162 function isValidTargetEvent($target, e) { 163 var b = $target.data("balloon") && $target.data("balloon").get(0); 164 return !(b && (b == e.relatedTarget || $.contains(b, e.relatedTarget))); 165 } 166 167 //----------------------------------------------------------------------------- 168 // Public 169 //----------------------------------------------------------------------------- 170 $.fn.balloon = function(options) { 171 options = $.extend(true, {}, $.balloon.defaults, options); 172 return this.one("mouseenter", function(e) { 173 var $target = $(this), t = this; 174 var $balloon = $target.unbind("mouseenter", arguments.callee) 175 .showBalloon(options).mouseenter(function(e) { 176 isValidTargetEvent($target, e) && $target.showBalloon(); 177 }).data("balloon"); 178 if($balloon) { 179 $balloon.mouseleave(function(e) { 180 if(t == e.relatedTarget || $.contains(t, e.relatedTarget)) return; 181 $target.hideBalloon(); 182 }).mouseenter(function(e) { $target.showBalloon(); }); 183 } 184 }).mouseleave(function(e) { 185 var $target = $(this); 186 isValidTargetEvent($target, e) && $target.hideBalloon(); 187 }); 188 }; 189 190 $.fn.showBalloon = function(options) { 191 var $target, $balloon, offTimer; 192 if(!$.balloon.defaults.css) $.balloon.defaults.css = {}; 193 if(options || !this.data("options")) 194 this.data("options", $.extend(true, {}, $.balloon.defaults, options)); 195 options = this.data("options"); 196 return this.each(function() { 197 $target = $(this); 198 (offTimer = $target.data("offTimer")) && clearTimeout(offTimer); 199 var contents = $.isFunction(options.contents) 200 ? options.contents() 201 : (options.contents || $target.attr("title")); 202 var isNew = !($balloon = $target.data("balloon")); 203 if(isNew) $balloon = $("<div>").append(contents); 204 if(!options.url && (!$balloon || $balloon.html() == "")) return; 205 if(!isNew && contents && contents != $balloon.html()) $balloon.empty().append(contents); 206 $target.removeAttr("title"); 207 if(options.url) { 208 $balloon.load($.isFunction(options.url) ? options.url(this) : options.url, function(res, sts, xhr) { 209 if(options.ajaxComplete) options.ajaxComplete(res, sts, xhr); 210 makeupBalloon($target, $balloon, options); 211 }); 212 } 213 if(isNew) { 214 $balloon 215 .addClass(options.classname) 216 .css(options.css) 217 .css({visibility: "hidden", position: "absolute"}) 218 .appendTo("body"); 219 $target.data("balloon", $balloon); 220 makeupBalloon($target, $balloon, options); 221 $balloon.hide().css("visibility", "visible"); 222 } else { 223 makeupBalloon($target, $balloon, options); 224 } 225 $target.data("onTimer", setTimeout(function() { 226 if(options.showAnimation) { 227 options.showAnimation.apply($balloon.stop(true, true), [options.showDuration]); 228 } else { 229 $balloon.show(options.showDuration, function() { 230 if(this.style.removeAttribute) { this.style.removeAttribute("filter"); } 231 });
232 } 233 }, options.delay)); 234 }); 235 }; 236 237 $.fn.hideBalloon = function() { 238 var options = this.data("options"), onTimer, offTimer; 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); 277 278 279
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.