1 // tipsy, facebook style tooltips for jquery
3 // (c) 2008-2010 jason frame [jason@onehackoranother.com]
4 // released under the MIT license
6 // * This installation of tipsy includes several local modifications to both Javascript and CSS.
7 // Please be careful when upgrading.
11 function maybeCall(thing
, ctx
) {
12 return (typeof thing
== 'function') ? (thing
.call(ctx
)) : thing
;
15 function fixTitle($ele
) {
16 if ($ele
.attr('title') || typeof($ele
.attr('original-title')) != 'string') {
17 $ele
.attr('original-title', $ele
.attr('title') || '').removeAttr('title');
21 function Tipsy(element
, options
) {
22 this.$element
= $(element
);
23 this.options
= options
;
25 fixTitle(this.$element
);
30 var title
= this.getTitle();
31 if (title
&& this.enabled
) {
32 var $tip
= this.tip();
34 $tip
.find('.tipsy-inner')[this.options
.html
? 'html' : 'text'](title
);
35 $tip
[0].className
= 'tipsy'; // reset classname in case of dynamic gravity
36 if (this.options
.className
) {
37 $tip
.addClass(maybeCall(this.options
.className
, this.$element
[0]));
39 $tip
.remove().css({top
: 0, left
: 0, visibility
: 'hidden', display
: 'block'}).appendTo(document
.body
);
41 var pos
= $.extend({}, this.$element
.offset(), {
42 width
: this.$element
[0].offsetWidth
,
43 height
: this.$element
[0].offsetHeight
46 var gravity
= (typeof this.options
.gravity
== 'function')
47 ? this.options
.gravity
.call(this.$element
[0])
48 : this.options
.gravity
;
50 // Attach css classes before checking height/width so they
52 $tip
.addClass('tipsy-' + gravity
);
53 if (this.options
.className
) {
54 $tip
.addClass(maybeCall(this.options
.className
, this.$element
[0]));
57 var actualWidth
= $tip
[0].offsetWidth
, actualHeight
= $tip
[0].offsetHeight
;
59 switch (gravity
.charAt(0)) {
61 tp
= {top
: pos
.top
+ pos
.height
+ this.options
.offset
, left
: pos
.left
+ pos
.width
/ 2 - actualWidth
/ 2};
64 tp
= {top
: pos
.top
- actualHeight
- this.options
.offset
, left
: pos
.left
+ pos
.width
/ 2 - actualWidth
/ 2};
67 tp
= {top
: pos
.top
+ pos
.height
/ 2 - actualHeight
/ 2, left
: pos
.left
- actualWidth
- this.options
.offset
};
70 tp
= {top
: pos
.top
+ pos
.height
/ 2 - actualHeight
/ 2, left
: pos
.left
+ pos
.width
+ this.options
.offset
};
74 if (gravity
.length
== 2) {
75 if (gravity
.charAt(1) == 'w') {
76 if (this.options
.center
) {
77 tp
.left
= pos
.left
+ pos
.width
/ 2 - 15;
82 if (this.options
.center
) {
83 tp
.left
= pos
.left
+ pos
.width
/ 2 - actualWidth
+ 15;
85 tp
.left
= pos
.left
+ pos
.width
;
91 if (this.options
.fade
) {
92 $tip
.stop().css({opacity
: 0, display
: 'block', visibility
: 'visible'}).animate({opacity
: this.options
.opacity
}, 100);
94 $tip
.css({visibility
: 'visible', opacity
: this.options
.opacity
});
100 if (this.options
.fade
) {
101 this.tip().stop().fadeOut(100, function() { $(this).remove(); });
107 getTitle: function() {
108 var title
, $e
= this.$element
, o
= this.options
;
110 if (typeof o
.title
== 'string') {
111 title
= $e
.attr(o
.title
== 'title' ? 'original-title' : o
.title
);
112 } else if (typeof o
.title
== 'function') {
113 title
= o
.title
.call($e
[0]);
115 title
= ('' + title
).replace(/(^\s*|\s*$)/, "");
116 return title
|| o
.fallback
;
121 this.$tip
= $('<div class="tipsy"></div>').html('<div class="tipsy-arrow"></div><div class="tipsy-inner"/></div>');
126 validate: function() {
127 if (!this.$element
[0].parentNode
) {
129 this.$element
= null;
134 enable: function() { this.enabled
= true; },
135 disable: function() { this.enabled
= false; },
136 toggleEnabled: function() { this.enabled
= !this.enabled
; }
139 $.fn
.tipsy = function(options
) {
141 if (options
=== true) {
142 return this.data('tipsy');
143 } else if (typeof options
== 'string') {
144 return this.data('tipsy')[options
]();
147 options
= $.extend({}, $.fn
.tipsy
.defaults
, options
);
150 var tipsy
= $.data(ele
, 'tipsy');
152 tipsy
= new Tipsy(ele
, $.fn
.tipsy
.elementOptions(ele
, options
));
153 $.data(ele
, 'tipsy', tipsy
);
159 var tipsy
= get(this);
160 tipsy
.hoverState
= 'in';
161 if (options
.delayIn
== 0) {
164 setTimeout(function() { if (tipsy
.hoverState
== 'in') tipsy
.show(); }, options
.delayIn
);
169 var tipsy
= get(this);
170 tipsy
.hoverState
= 'out';
171 if (options
.delayOut
== 0) {
174 setTimeout(function() { if (tipsy
.hoverState
== 'out') tipsy
.hide(); }, options
.delayOut
);
178 if (!options
.live
) this.each(function() { get(this); });
180 if (options
.trigger
!= 'manual') {
181 var binder
= options
.live
? 'live' : 'bind',
182 eventIn
= options
.trigger
== 'hover' ? 'mouseenter' : 'focus',
183 eventOut
= options
.trigger
== 'hover' ? 'mouseleave' : 'blur';
184 this[binder
](eventIn
, enter
)[binder
](eventOut
, leave
);
191 $.fn
.tipsy
.defaults
= {
207 // Overwrite this method to provide options on a per-element basis.
208 // For example, you could store the gravity in a 'tipsy-gravity' attribute:
209 // return $.extend({}, options, {gravity: $(ele).attr('tipsy-gravity') || 'n' });
210 // (remember - do not modify 'options' in place!)
211 $.fn
.tipsy
.elementOptions = function(ele
, options
) {
212 return $.metadata
? $.extend({}, options
, $(ele
).metadata()) : options
;
215 $.fn
.tipsy
.autoNS = function() {
216 return $(this).offset().top
> ($(document
).scrollTop() + $(window
).height() / 2) ? 's' : 'n';
219 $.fn
.tipsy
.autoWE = function() {
220 return $(this).offset().left
> ($(document
).scrollLeft() + $(window
).width() / 2) ? 'e' : 'w';