source: main/trunk/greenstone3/web/interfaces/therin/js/poshytip-1.1/src/jquery.poshytip.js@ 28644

Last change on this file since 28644 was 28644, checked in by jlwhisler, 10 years ago

A new interface created using a CSS template by luiszuno.com. Based on the default interface.

File size: 17.1 KB
Line 
1/*
2 * Poshy Tip jQuery plugin v1.1
3 * http://vadikom.com/tools/poshy-tip-jquery-plugin-for-stylish-tooltips/
4 * Copyright 2010-2011, Vasil Dinkov, http://vadikom.com/
5 */
6
7(function($) {
8
9 var tips = [],
10 reBgImage = /^url\(["']?([^"'\)]*)["']?\);?$/i,
11 rePNG = /\.png$/i,
12 ie6 = $.browser.msie && $.browser.version == 6;
13
14 // make sure the tips' position is updated on resize
15 function handleWindowResize() {
16 $.each(tips, function() {
17 this.refresh(true);
18 });
19 }
20 $(window).resize(handleWindowResize);
21
22 $.Poshytip = function(elm, options) {
23 this.$elm = $(elm);
24 this.opts = $.extend({}, $.fn.poshytip.defaults, options);
25 this.$tip = $(['<div class="',this.opts.className,'">',
26 '<div class="tip-inner tip-bg-image"></div>',
27 '<div class="tip-arrow tip-arrow-top tip-arrow-right tip-arrow-bottom tip-arrow-left"></div>',
28 '</div>'].join('')).appendTo(document.body);
29 this.$arrow = this.$tip.find('div.tip-arrow');
30 this.$inner = this.$tip.find('div.tip-inner');
31 this.disabled = false;
32 this.content = null;
33 this.init();
34 };
35
36 $.Poshytip.prototype = {
37 init: function() {
38 tips.push(this);
39
40 // save the original title and a reference to the Poshytip object
41 var title = this.$elm.attr('title');
42 this.$elm.data('title.poshytip', title !== undefined ? title : null)
43 .data('poshytip', this);
44
45 // hook element events
46 if (this.opts.showOn != 'none') {
47 this.$elm.bind({
48 'mouseenter.poshytip': $.proxy(this.mouseenter, this),
49 'mouseleave.poshytip': $.proxy(this.mouseleave, this)
50 });
51 switch (this.opts.showOn) {
52 case 'hover':
53 if (this.opts.alignTo == 'cursor')
54 this.$elm.bind('mousemove.poshytip', $.proxy(this.mousemove, this));
55 if (this.opts.allowTipHover)
56 this.$tip.hover($.proxy(this.clearTimeouts, this), $.proxy(this.mouseleave, this));
57 break;
58 case 'focus':
59 this.$elm.bind({
60 'focus.poshytip': $.proxy(this.show, this),
61 'blur.poshytip': $.proxy(this.hide, this)
62 });
63 break;
64 }
65 }
66 },
67 mouseenter: function(e) {
68 if (this.disabled)
69 return true;
70
71 this.$elm.attr('title', '');
72 if (this.opts.showOn == 'focus')
73 return true;
74
75 this.clearTimeouts();
76 this.showTimeout = setTimeout($.proxy(this.show, this), this.opts.showTimeout);
77 },
78 mouseleave: function(e) {
79 if (this.disabled || this.asyncAnimating && (this.$tip[0] === e.relatedTarget || jQuery.contains(this.$tip[0], e.relatedTarget)))
80 return true;
81
82 var title = this.$elm.data('title.poshytip');
83 if (title !== null)
84 this.$elm.attr('title', title);
85 if (this.opts.showOn == 'focus')
86 return true;
87
88 this.clearTimeouts();
89 this.hideTimeout = setTimeout($.proxy(this.hide, this), this.opts.hideTimeout);
90 },
91 mousemove: function(e) {
92 if (this.disabled)
93 return true;
94
95 this.eventX = e.pageX;
96 this.eventY = e.pageY;
97 if (this.opts.followCursor && this.$tip.data('active')) {
98 this.calcPos();
99 this.$tip.css({left: this.pos.l, top: this.pos.t});
100 if (this.pos.arrow)
101 this.$arrow[0].className = 'tip-arrow tip-arrow-' + this.pos.arrow;
102 }
103 },
104 show: function() {
105 if (this.disabled || this.$tip.data('active'))
106 return;
107
108 this.reset();
109 this.update();
110 this.display();
111 if (this.opts.timeOnScreen)
112 setTimeout($.proxy(this.hide, this), this.opts.timeOnScreen);
113 },
114 hide: function() {
115 if (this.disabled || !this.$tip.data('active'))
116 return;
117
118 this.display(true);
119 },
120 reset: function() {
121 this.$tip.queue([]).detach().css('visibility', 'hidden').data('active', false);
122 this.$inner.find('*').poshytip('hide');
123 if (this.opts.fade)
124 this.$tip.css('opacity', this.opacity);
125 this.$arrow[0].className = 'tip-arrow tip-arrow-top tip-arrow-right tip-arrow-bottom tip-arrow-left';
126 this.asyncAnimating = false;
127 },
128 update: function(content, dontOverwriteOption) {
129 if (this.disabled)
130 return;
131
132 var async = content !== undefined;
133 if (async) {
134 if (!dontOverwriteOption)
135 this.opts.content = content;
136 if (!this.$tip.data('active'))
137 return;
138 } else {
139 content = this.opts.content;
140 }
141
142 // update content only if it has been changed since last time
143 var self = this,
144 newContent = typeof content == 'function' ?
145 content.call(this.$elm[0], function(newContent) {
146 self.update(newContent);
147 }) :
148 content == '[title]' ? this.$elm.data('title.poshytip') : content;
149 if (this.content !== newContent) {
150 this.$inner.empty().append(newContent);
151 this.content = newContent;
152 }
153
154 this.refresh(async);
155 },
156 refresh: function(async) {
157 if (this.disabled)
158 return;
159
160 if (async) {
161 if (!this.$tip.data('active'))
162 return;
163 // save current position as we will need to animate
164 var currPos = {left: this.$tip.css('left'), top: this.$tip.css('top')};
165 }
166
167 // reset position to avoid text wrapping, etc.
168 this.$tip.css({left: 0, top: 0}).appendTo(document.body);
169
170 // save default opacity
171 if (this.opacity === undefined)
172 this.opacity = this.$tip.css('opacity');
173
174 // check for images - this code is here (i.e. executed each time we show the tip and not on init) due to some browser inconsistencies
175 var bgImage = this.$tip.css('background-image').match(reBgImage),
176 arrow = this.$arrow.css('background-image').match(reBgImage);
177
178 if (bgImage) {
179 var bgImagePNG = rePNG.test(bgImage[1]);
180 // fallback to background-color/padding/border in IE6 if a PNG is used
181 if (ie6 && bgImagePNG) {
182 this.$tip.css('background-image', 'none');
183 this.$inner.css({margin: 0, border: 0, padding: 0});
184 bgImage = bgImagePNG = false;
185 } else {
186 this.$tip.prepend('<table border="0" cellpadding="0" cellspacing="0"><tr><td class="tip-top tip-bg-image" colspan="2"><span></span></td><td class="tip-right tip-bg-image" rowspan="2"><span></span></td></tr><tr><td class="tip-left tip-bg-image" rowspan="2"><span></span></td><td></td></tr><tr><td class="tip-bottom tip-bg-image" colspan="2"><span></span></td></tr></table>')
187 .css({border: 0, padding: 0, 'background-image': 'none', 'background-color': 'transparent'})
188 .find('.tip-bg-image').css('background-image', 'url("' + bgImage[1] +'")').end()
189 .find('td').eq(3).append(this.$inner);
190 }
191 // disable fade effect in IE due to Alpha filter + translucent PNG issue
192 if (bgImagePNG && !$.support.opacity)
193 this.opts.fade = false;
194 }
195 // IE arrow fixes
196 if (arrow && !$.support.opacity) {
197 // disable arrow in IE6 if using a PNG
198 if (ie6 && rePNG.test(arrow[1])) {
199 arrow = false;
200 this.$arrow.css('background-image', 'none');
201 }
202 // disable fade effect in IE due to Alpha filter + translucent PNG issue
203 this.opts.fade = false;
204 }
205
206 var $table = this.$tip.find('table');
207 if (ie6) {
208 // fix min/max-width in IE6
209 this.$tip[0].style.width = '';
210 $table.width('auto').find('td').eq(3).width('auto');
211 var tipW = this.$tip.width(),
212 minW = parseInt(this.$tip.css('min-width')),
213 maxW = parseInt(this.$tip.css('max-width'));
214 if (!isNaN(minW) && tipW < minW)
215 tipW = minW;
216 else if (!isNaN(maxW) && tipW > maxW)
217 tipW = maxW;
218 this.$tip.add($table).width(tipW).eq(0).find('td').eq(3).width('100%');
219 } else if ($table[0]) {
220 // fix the table width if we are using a background image
221 // IE9, FF4 use float numbers for width/height so use getComputedStyle for them to avoid text wrapping
222 // for details look at: http://vadikom.com/dailies/offsetwidth-offsetheight-useless-in-ie9-firefox4/
223 $table.width('auto').find('td').eq(3).width('auto').end().end().width(document.defaultView && document.defaultView.getComputedStyle && parseFloat(document.defaultView.getComputedStyle(this.$tip[0], null).width) || this.$tip.width()).find('td').eq(3).width('100%');
224 }
225 this.tipOuterW = this.$tip.outerWidth();
226 this.tipOuterH = this.$tip.outerHeight();
227
228 this.calcPos();
229
230 // position and show the arrow image
231 if (arrow && this.pos.arrow) {
232 this.$arrow[0].className = 'tip-arrow tip-arrow-' + this.pos.arrow;
233 this.$arrow.css('visibility', 'inherit');
234 }
235
236 if (async) {
237 this.asyncAnimating = true;
238 var self = this;
239 this.$tip.css(currPos).animate({left: this.pos.l, top: this.pos.t}, 200, function() { self.asyncAnimating = false; });
240 } else {
241 this.$tip.css({left: this.pos.l, top: this.pos.t});
242 }
243 },
244 display: function(hide) {
245 var active = this.$tip.data('active');
246 if (active && !hide || !active && hide)
247 return;
248
249 this.$tip.stop();
250 if ((this.opts.slide && this.pos.arrow || this.opts.fade) && (hide && this.opts.hideAniDuration || !hide && this.opts.showAniDuration)) {
251 var from = {}, to = {};
252 // this.pos.arrow is only undefined when alignX == alignY == 'center' and we don't need to slide in that rare case
253 if (this.opts.slide && this.pos.arrow) {
254 var prop, arr;
255 if (this.pos.arrow == 'bottom' || this.pos.arrow == 'top') {
256 prop = 'top';
257 arr = 'bottom';
258 } else {
259 prop = 'left';
260 arr = 'right';
261 }
262 var val = parseInt(this.$tip.css(prop));
263 from[prop] = val + (hide ? 0 : (this.pos.arrow == arr ? -this.opts.slideOffset : this.opts.slideOffset));
264 to[prop] = val + (hide ? (this.pos.arrow == arr ? this.opts.slideOffset : -this.opts.slideOffset) : 0) + 'px';
265 }
266 if (this.opts.fade) {
267 from.opacity = hide ? this.$tip.css('opacity') : 0;
268 to.opacity = hide ? 0 : this.opacity;
269 }
270 this.$tip.css(from).animate(to, this.opts[hide ? 'hideAniDuration' : 'showAniDuration']);
271 }
272 hide ? this.$tip.queue($.proxy(this.reset, this)) : this.$tip.css('visibility', 'inherit');
273 this.$tip.data('active', !active);
274 },
275 disable: function() {
276 this.reset();
277 this.disabled = true;
278 },
279 enable: function() {
280 this.disabled = false;
281 },
282 destroy: function() {
283 this.reset();
284 this.$tip.remove();
285 delete this.$tip;
286 this.content = null;
287 this.$elm.unbind('.poshytip').removeData('title.poshytip').removeData('poshytip');
288 tips.splice($.inArray(this, tips), 1);
289 },
290 clearTimeouts: function() {
291 if (this.showTimeout) {
292 clearTimeout(this.showTimeout);
293 this.showTimeout = 0;
294 }
295 if (this.hideTimeout) {
296 clearTimeout(this.hideTimeout);
297 this.hideTimeout = 0;
298 }
299 },
300 calcPos: function() {
301 var pos = {l: 0, t: 0, arrow: ''},
302 $win = $(window),
303 win = {
304 l: $win.scrollLeft(),
305 t: $win.scrollTop(),
306 w: $win.width(),
307 h: $win.height()
308 }, xL, xC, xR, yT, yC, yB;
309 if (this.opts.alignTo == 'cursor') {
310 xL = xC = xR = this.eventX;
311 yT = yC = yB = this.eventY;
312 } else { // this.opts.alignTo == 'target'
313 var elmOffset = this.$elm.offset(),
314 elm = {
315 l: elmOffset.left,
316 t: elmOffset.top,
317 w: this.$elm.outerWidth(),
318 h: this.$elm.outerHeight()
319 };
320 xL = elm.l + (this.opts.alignX != 'inner-right' ? 0 : elm.w); // left edge
321 xC = xL + Math.floor(elm.w / 2); // h center
322 xR = xL + (this.opts.alignX != 'inner-left' ? elm.w : 0); // right edge
323 yT = elm.t + (this.opts.alignY != 'inner-bottom' ? 0 : elm.h); // top edge
324 yC = yT + Math.floor(elm.h / 2); // v center
325 yB = yT + (this.opts.alignY != 'inner-top' ? elm.h : 0); // bottom edge
326 }
327
328 // keep in viewport and calc arrow position
329 switch (this.opts.alignX) {
330 case 'right':
331 case 'inner-left':
332 pos.l = xR + this.opts.offsetX;
333 if (pos.l + this.tipOuterW > win.l + win.w)
334 pos.l = win.l + win.w - this.tipOuterW;
335 if (this.opts.alignX == 'right' || this.opts.alignY == 'center')
336 pos.arrow = 'left';
337 break;
338 case 'center':
339 pos.l = xC - Math.floor(this.tipOuterW / 2);
340 if (pos.l + this.tipOuterW > win.l + win.w)
341 pos.l = win.l + win.w - this.tipOuterW;
342 else if (pos.l < win.l)
343 pos.l = win.l;
344 break;
345 default: // 'left' || 'inner-right'
346 pos.l = xL - this.tipOuterW - this.opts.offsetX;
347 if (pos.l < win.l)
348 pos.l = win.l;
349 if (this.opts.alignX == 'left' || this.opts.alignY == 'center')
350 pos.arrow = 'right';
351 }
352 switch (this.opts.alignY) {
353 case 'bottom':
354 case 'inner-top':
355 pos.t = yB + this.opts.offsetY;
356 // 'left' and 'right' need priority for 'target'
357 if (!pos.arrow || this.opts.alignTo == 'cursor')
358 pos.arrow = 'top';
359 if (pos.t + this.tipOuterH > win.t + win.h) {
360 pos.t = yT - this.tipOuterH - this.opts.offsetY;
361 if (pos.arrow == 'top')
362 pos.arrow = 'bottom';
363 }
364 break;
365 case 'center':
366 pos.t = yC - Math.floor(this.tipOuterH / 2);
367 if (pos.t + this.tipOuterH > win.t + win.h)
368 pos.t = win.t + win.h - this.tipOuterH;
369 else if (pos.t < win.t)
370 pos.t = win.t;
371 break;
372 default: // 'top' || 'inner-bottom'
373 pos.t = yT - this.tipOuterH - this.opts.offsetY;
374 // 'left' and 'right' need priority for 'target'
375 if (!pos.arrow || this.opts.alignTo == 'cursor')
376 pos.arrow = 'bottom';
377 if (pos.t < win.t) {
378 pos.t = yB + this.opts.offsetY;
379 if (pos.arrow == 'bottom')
380 pos.arrow = 'top';
381 }
382 }
383 this.pos = pos;
384 }
385 };
386
387 $.fn.poshytip = function(options) {
388 if (typeof options == 'string') {
389 var args = arguments,
390 method = options;
391 Array.prototype.shift.call(args);
392 // unhook live events if 'destroy' is called
393 if (method == 'destroy')
394 this.die('mouseenter.poshytip').die('focus.poshytip');
395 return this.each(function() {
396 var poshytip = $(this).data('poshytip');
397 if (poshytip && poshytip[method])
398 poshytip[method].apply(poshytip, args);
399 });
400 }
401
402 var opts = $.extend({}, $.fn.poshytip.defaults, options);
403
404 // generate CSS for this tip class if not already generated
405 if (!$('#poshytip-css-' + opts.className)[0])
406 $(['<style id="poshytip-css-',opts.className,'" type="text/css">',
407 'div.',opts.className,'{visibility:hidden;position:absolute;top:0;left:0;}',
408 'div.',opts.className,' table, div.',opts.className,' td{margin:0;font-family:inherit;font-size:inherit;font-weight:inherit;font-style:inherit;font-variant:inherit;}',
409 'div.',opts.className,' td.tip-bg-image span{display:block;font:1px/1px sans-serif;height:',opts.bgImageFrameSize,'px;width:',opts.bgImageFrameSize,'px;overflow:hidden;}',
410 'div.',opts.className,' td.tip-right{background-position:100% 0;}',
411 'div.',opts.className,' td.tip-bottom{background-position:100% 100%;}',
412 'div.',opts.className,' td.tip-left{background-position:0 100%;}',
413 'div.',opts.className,' div.tip-inner{background-position:-',opts.bgImageFrameSize,'px -',opts.bgImageFrameSize,'px;}',
414 'div.',opts.className,' div.tip-arrow{visibility:hidden;position:absolute;overflow:hidden;font:1px/1px sans-serif;}',
415 '</style>'].join('')).appendTo('head');
416
417 // check if we need to hook live events
418 if (opts.liveEvents && opts.showOn != 'none') {
419 var deadOpts = $.extend({}, opts, { liveEvents: false });
420 switch (opts.showOn) {
421 case 'hover':
422 this.live('mouseenter.poshytip', function() {
423 var $this = $(this);
424 if (!$this.data('poshytip'))
425 $this.poshytip(deadOpts).poshytip('mouseenter');
426 });
427 break;
428 case 'focus':
429 this.live('focus.poshytip', function() {
430 var $this = $(this);
431 if (!$this.data('poshytip'))
432 $this.poshytip(deadOpts).poshytip('show');
433 });
434 break;
435 }
436 return this;
437 }
438
439 return this.each(function() {
440 new $.Poshytip(this, opts);
441 });
442 }
443
444 // default settings
445 $.fn.poshytip.defaults = {
446 content: '[title]', // content to display ('[title]', 'string', element, function(updateCallback){...}, jQuery)
447 className: 'tip-yellow', // class for the tips
448 bgImageFrameSize: 10, // size in pixels for the background-image (if set in CSS) frame around the inner content of the tip
449 showTimeout: 500, // timeout before showing the tip (in milliseconds 1000 == 1 second)
450 hideTimeout: 100, // timeout before hiding the tip
451 timeOnScreen: 0, // timeout before automatically hiding the tip after showing it (set to > 0 in order to activate)
452 showOn: 'hover', // handler for showing the tip ('hover', 'focus', 'none') - use 'none' to trigger it manually
453 liveEvents: false, // use live events
454 alignTo: 'cursor', // align/position the tip relative to ('cursor', 'target')
455 alignX: 'right', // horizontal alignment for the tip relative to the mouse cursor or the target element
456 // ('right', 'center', 'left', 'inner-left', 'inner-right') - 'inner-*' matter if alignTo:'target'
457 alignY: 'top', // vertical alignment for the tip relative to the mouse cursor or the target element
458 // ('bottom', 'center', 'top', 'inner-bottom', 'inner-top') - 'inner-*' matter if alignTo:'target'
459 offsetX: -22, // offset X pixels from the default position - doesn't matter if alignX:'center'
460 offsetY: 18, // offset Y pixels from the default position - doesn't matter if alignY:'center'
461 allowTipHover: true, // allow hovering the tip without hiding it onmouseout of the target - matters only if showOn:'hover'
462 followCursor: false, // if the tip should follow the cursor - matters only if showOn:'hover' and alignTo:'cursor'
463 fade: true, // use fade animation
464 slide: true, // use slide animation
465 slideOffset: 8, // slide animation offset
466 showAniDuration: 300, // show animation duration - set to 0 if you don't want show animation
467 hideAniDuration: 300 // hide animation duration - set to 0 if you don't want hide animation
468 };
469
470})(jQuery);
Note: See TracBrowser for help on using the repository browser.