balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

tipsy.js (7812B)


      1 // tipsy, facebook style tooltips for jquery
      2 // version 1.0.0a
      3 // (c) 2008-2010 jason frame [jason@onehackoranother.com]
      4 // released under the MIT license
      5 
      6 (function($) {
      7 
      8 	function maybeCall(thing, ctx) {
      9 		return (typeof thing == 'function') ? (thing.call(ctx)) : thing;
     10 	};
     11 
     12 	function isElementInDOM(ele) {
     13 		while (ele = ele.parentNode) {
     14 			if (ele == document) return true;
     15 		}
     16 		return false;
     17 	};
     18 
     19 	function Tipsy(element, options) {
     20 		this.$element = $(element);
     21 		this.options = options;
     22 		this.enabled = true;
     23 		this.fixTitle();
     24 	};
     25 
     26 	Tipsy.prototype = {
     27 		show: function() {
     28 			var title = this.getTitle();
     29 			if (title && this.enabled) {
     30 				var $tip = this.tip();
     31 
     32 				$tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title);
     33 				$tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity
     34 				$tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body);
     35 
     36 				var pos = $.extend({}, this.$element.offset(), {
     37 					width: this.$element[0].offsetWidth,
     38 					height: this.$element[0].offsetHeight
     39 				});
     40 
     41 				var actualWidth = $tip[0].offsetWidth,
     42 					actualHeight = $tip[0].offsetHeight,
     43 					gravity = maybeCall(this.options.gravity, this.$element[0]);
     44 
     45 				var tp;
     46 				switch (gravity.charAt(0)) {
     47 					case 'n':
     48 						tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
     49 						break;
     50 					case 's':
     51 						tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2};
     52 						break;
     53 					case 'e':
     54 						tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset};
     55 						break;
     56 					case 'w':
     57 						tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset};
     58 						break;
     59 				}
     60 
     61 				if (gravity.length == 2) {
     62 					if (gravity.charAt(1) == 'w') {
     63 						tp.left = pos.left + pos.width / 2 - 15;
     64 					} else {
     65 						tp.left = pos.left + pos.width / 2 - actualWidth + 15;
     66 					}
     67 				}
     68 
     69 				$tip.css(tp).addClass('tipsy-' + gravity);
     70 				$tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0);
     71 				if (this.options.className) {
     72 					$tip.addClass(maybeCall(this.options.className, this.$element[0]));
     73 				}
     74 
     75 				if (this.options.fade) {
     76 					$tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity});
     77 				} else {
     78 					$tip.css({visibility: 'visible', opacity: this.options.opacity});
     79 				}
     80 			}
     81 		},
     82 
     83 		hide: function() {
     84 			if (this.options.fade) {
     85 				this.tip().stop().fadeOut(function() { $(this).remove(); });
     86 			} else {
     87 				this.tip().remove();
     88 			}
     89 		},
     90 
     91 		fixTitle: function() {
     92 			var $e = this.$element;
     93 			if ($e.attr('title') || typeof($e.attr('original-title')) != 'string') {
     94 				$e.attr('original-title', $e.attr('title') || '').removeAttr('title');
     95 			}
     96 		},
     97 
     98 		getTitle: function() {
     99 			var title, $e = this.$element, o = this.options;
    100 			this.fixTitle();
    101 			var title, o = this.options;
    102 			if (typeof o.title == 'string') {
    103 				title = $e.attr(o.title == 'title' ? 'original-title' : o.title);
    104 			} else if (typeof o.title == 'function') {
    105 				title = o.title.call($e[0]);
    106 			}
    107 			title = ('' + title).replace(/(^\s*|\s*$)/, "");
    108 			return title || o.fallback;
    109 		},
    110 
    111 		tip: function() {
    112 			if (!this.$tip) {
    113 				this.$tip = $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"></div>');
    114 				this.$tip.data('tipsy-pointee', this.$element[0]);
    115 			}
    116 			return this.$tip;
    117 		},
    118 
    119 		validate: function() {
    120 			if (!this.$element[0].parentNode) {
    121 				this.hide();
    122 				this.$element = null;
    123 				this.options = null;
    124 			}
    125 		},
    126 
    127 		enable: function() { this.enabled = true; },
    128 		disable: function() { this.enabled = false; },
    129 		toggleEnabled: function() { this.enabled = !this.enabled; }
    130 	};
    131 
    132 	$.fn.tipsy = function(options) {
    133 
    134 		if (options === true) {
    135 			return this.data('tipsy');
    136 		} else if (typeof options == 'string') {
    137 			var tipsy = this.data('tipsy');
    138 			if (tipsy) tipsy[options]();
    139 			return this;
    140 		}
    141 
    142 		options = $.extend({}, $.fn.tipsy.defaults, options);
    143 
    144 		function get(ele) {
    145 			var tipsy = $.data(ele, 'tipsy');
    146 			if (!tipsy) {
    147 				tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options));
    148 				$.data(ele, 'tipsy', tipsy);
    149 			}
    150 			return tipsy;
    151 		}
    152 
    153 		function enter() {
    154 			var tipsy = get(this);
    155 			tipsy.hoverState = 'in';
    156 			if (options.delayIn == 0) {
    157 				tipsy.show();
    158 			} else {
    159 				tipsy.fixTitle();
    160 				setTimeout(function() { if (tipsy.hoverState == 'in') tipsy.show(); }, options.delayIn);
    161 			}
    162 		};
    163 
    164 		function leave() {
    165 			var tipsy = get(this);
    166 			tipsy.hoverState = 'out';
    167 			if (options.delayOut == 0) {
    168 				tipsy.hide();
    169 			} else {
    170 				setTimeout(function() { if (tipsy.hoverState == 'out') tipsy.hide(); }, options.delayOut);
    171 			}
    172 		};
    173 
    174 		if (!options.live) this.each(function() { get(this); });
    175 
    176 		if (options.trigger != 'manual') {
    177 			var binder   = options.live ? 'live' : 'bind',
    178 				eventIn  = options.trigger == 'hover' ? 'mouseenter' : 'focus',
    179 				eventOut = options.trigger == 'hover' ? 'mouseleave' : 'blur';
    180 			this[binder](eventIn, enter)[binder](eventOut, leave);
    181 		}
    182 
    183 		return this;
    184 
    185 	};
    186 
    187 	$.fn.tipsy.defaults = {
    188 		className: null,
    189 		delayIn: 0,
    190 		delayOut: 0,
    191 		fade: false,
    192 		fallback: '',
    193 		gravity: 'n',
    194 		html: false,
    195 		live: false,
    196 		offset: 0,
    197 		opacity: 0.8,
    198 		title: 'title',
    199 		trigger: 'hover'
    200 	};
    201 
    202 	$.fn.tipsy.revalidate = function() {
    203 		$('.tipsy').each(function() {
    204 			var pointee = $.data(this, 'tipsy-pointee');
    205 			if (!pointee || !isElementInDOM(pointee)) {
    206 				$(this).remove();
    207 			}
    208 		});
    209 	};
    210 
    211 	// Overwrite this method to provide options on a per-element basis.
    212 	// For example, you could store the gravity in a 'tipsy-gravity' attribute:
    213 	// return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
    214 	// (remember - do not modify 'options' in place!)
    215 	$.fn.tipsy.elementOptions = function(ele, options) {
    216 		return $.metadata ? $.extend({}, options, $(ele).metadata()) : options;
    217 	};
    218 
    219 	$.fn.tipsy.autoNS = function() {
    220 		return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n';
    221 	};
    222 
    223 	$.fn.tipsy.autoWE = function() {
    224 		return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w';
    225 	};
    226 
    227 	/**
    228 	 * yields a closure of the supplied parameters, producing a function that takes
    229 	 * no arguments and is suitable for use as an autogravity function like so:
    230 	 *
    231 	 * @param margin (int) - distance from the viewable region edge that an
    232 	 *        element should be before setting its tooltip's gravity to be away
    233 	 *        from that edge.
    234 	 * @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer
    235 	 *        if there are no viewable region edges effecting the tooltip's
    236 	 *        gravity. It will try to vary from this minimally, for example,
    237 	 *        if 'sw' is preferred and an element is near the right viewable
    238 	 *        region edge, but not the top edge, it will set the gravity for
    239 	 *        that element's tooltip to be 'se', preserving the southern
    240 	 *        component.
    241 	 */
    242 	$.fn.tipsy.autoBounds = function(margin, prefer) {
    243 		return function() {
    244 			var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)},
    245 				boundTop = $(document).scrollTop() + margin,
    246 				boundLeft = $(document).scrollLeft() + margin,
    247 				$this = $(this);
    248 
    249 			if ($this.offset().top < boundTop) dir.ns = 'n';
    250 			if ($this.offset().left < boundLeft) dir.ew = 'w';
    251 			if ($(window).width() + $(document).scrollLeft() - $this.offset().left < margin) dir.ew = 'e';
    252 			if ($(window).height() + $(document).scrollTop() - $this.offset().top < margin) dir.ns = 's';
    253 
    254 			return dir.ns + (dir.ew ? dir.ew : '');
    255 		}
    256 	};
    257 
    258 })(jQuery);