forked from drewwilson/TipTip
-
Notifications
You must be signed in to change notification settings - Fork 2
/
jquery.tipTip.js
313 lines (277 loc) · 15.8 KB
/
jquery.tipTip.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
/*
* TipTip
* Copyright 2010 Drew Wilson
* www.drewwilson.com
* code.drewwilson.com/entry/tiptip-jquery-plugin
*
* Version 1.3 - Updated: Mar. 23, 2010
*
* This Plug-In will create a custom tooltip to replace the default
* browser tooltip. It is extremely lightweight and very smart in
* that it detects the edges of the browser window and will make sure
* the tooltip stays within the current window size. As a result the
* tooltip will adjust itself to be displayed above, below, to the left
* or to the right depending on what is necessary to stay within the
* browser window. It is completely customizable as well via CSS.
*
* This TipTip jQuery plug-in is dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*/
(function ($) {
$.fn.tipTip = function (options) {
var defaults = {
activation: 'hover', // How to show (and hide) the tooltip. Can be: hover, focus, click and manual.
keepAlive: false, // When true the tooltip won't disapper when the mouse moves away from the element. Instead it will be hidden when it leaves the tooltip.
maxWidth: '200px', // The max-width to set on the tooltip. You may also use the option cssClass to set this.
edgeOffset: 0, // The offset between the tooltip arrow edge and the element that has the tooltip.
defaultPosition: 'bottom', // The position of the tooltip. Can be: top, right, bottom and left.
delay: 400, // The delay in msec to show a tooltip.
fadeIn: 200, // The length in msec of the fade in.
fadeOut: 200, // The length in msec of the fade out.
attribute: 'title', // The attribute to fetch the tooltip text if the option content is false.
content: false, // HTML or String or Function (that returns HTML or String) to fill TipTIp with
enter: function () { }, // Callback function before a tooltip is shown.
afterEnter: function () { }, // Callback function after a tooltip is shown.
exit: function () { }, // Callback function before a tooltip is hidden.
afterExit: function () { }, // Callback function after a tooltip is hidden.
cssClass: '', // CSS class that will be applied on the tooltip before showing only for this instance of tooltip.
detectTextDir: true // When false auto-detection for right-to-left text will be disable (When true affects a bit the performance).
};
// Setup tip tip elements and render them to the DOM
if ($('#tiptip_holder').length <= 0) {
var tiptip_inner_arrow = $('<div>', { id: 'tiptip_arrow_inner' }),
tiptip_arrow = $('<div>', { id: 'tiptip_arrow' }).append(tiptip_inner_arrow),
tiptip_content = $('<div>', { id: 'tiptip_content' }),
tiptip_holder = $('<div>', { id: 'tiptip_holder' }).append(tiptip_arrow).append(tiptip_content);
$('body').append(tiptip_holder);
} else {
var tiptip_holder = $('#tiptip_holder'),
tiptip_content = $('#tiptip_content'),
tiptip_arrow = $('#tiptip_arrow');
}
return this.each(function () {
var org_elem = $(this),
data = org_elem.data('tipTip'),
opts = data && data.options || $.extend({}, defaults, options),
callback_data = { holder: tiptip_holder, content: tiptip_content, arrow: tiptip_arrow, options: opts };
if (data) {
switch (options) {
case 'show':
active_tiptip();
break;
case 'hide':
deactive_tiptip();
break;
case 'destroy':
org_elem.unbind('.tipTip').removeData('tipTip');
break;
case 'position':
position_tiptip();
}
} else {
var timeout = false;
org_elem.data('tipTip', { options: opts });
if (opts.activation == 'hover') {
org_elem.bind('mouseenter.tipTip', function () {
active_tiptip();
}).bind('mouseleave.tipTip', function () {
if (!opts.keepAlive) {
deactive_tiptip();
} else {
tiptip_holder.one('mouseleave.tipTip', function () {
deactive_tiptip();
});
}
});
} else if (opts.activation == 'focus') {
org_elem.bind('focus.tipTip', function () {
active_tiptip();
}).bind('blur.tipTip', function () {
deactive_tiptip();
});
} else if (opts.activation == 'click') {
org_elem.bind('click.tipTip', function (e) {
e.preventDefault();
active_tiptip();
return false;
}).bind('mouseleave.tipTip', function () {
if (!opts.keepAlive) {
deactive_tiptip();
} else {
tiptip_holder.one('mouseleave.tipTip', function () {
deactive_tiptip();
});
}
});
} else if (opts.activation == 'manual') {
// Nothing to register actually. We decide when to show or hide.
}
}
function active_tiptip() {
if (opts.enter.call(org_elem, callback_data) === false) {
return;
}
// Get the text and append it in the tiptip_content.
var org_title;
if (opts.content) {
org_title = $.isFunction(opts.content) ? opts.content.call(org_elem, callback_data) : opts.content;
} else {
org_title = opts.content = org_elem.attr(opts.attribute);
org_elem.removeAttr(opts.attribute); //remove original Attribute
}
if (!org_title) {
return; // don't show tip when no content.
}
tiptip_content.html(org_title);
tiptip_holder.hide().removeAttr('class').css({ 'max-width': opts.maxWidth });
if (opts.cssClass) {
tiptip_holder.addClass(opts.cssClass);
}
// Calculate the position of the tooltip.
position_tiptip();
// Show the tooltip.
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(function () {
tiptip_holder.stop(true, true).fadeIn(opts.fadeIn);
}, opts.delay);
$(window).bind('resize.tipTip scroll.tipTip', position_tiptip);
org_elem.addClass('tiptip_visible'); // Add marker class to easily find the target element with visible tooltip. It will be remove later on deactive_tiptip().
opts.afterEnter.call(org_elem, callback_data);
}
function deactive_tiptip() {
if (opts.exit.call(org_elem, callback_data) === false) {
return;
}
if (timeout) {
clearTimeout(timeout);
}
tiptip_holder.fadeOut(opts.fadeOut);
$(window).unbind('resize.tipTip scroll.tipTip');
org_elem.removeClass('tiptip_visible');
opts.afterExit.call(org_elem, callback_data);
}
function position_tiptip() {
var org_offset = org_elem.offset(),
org_top = org_offset.top,
org_left = org_offset.left,
org_width = org_elem.outerWidth(),
org_height = org_elem.outerHeight(),
tip_top,
tip_left,
tip_width = tiptip_holder.outerWidth(),
tip_height = tiptip_holder.outerHeight(),
tip_class,
tip_classes = { top: 'tip_top', bottom: 'tip_bottom', left: 'tip_left', right: 'tip_right' },
arrow_top,
arrow_left,
arrow_width = 12, // tiptip_arrow.outerHeight() and tiptip_arrow.outerWidth() don't work because they need the element to be visible.
arrow_height = 12,
win = $(window),
win_top = win.scrollTop(),
win_left = win.scrollLeft(),
win_width = win.width(),
win_height = win.height(),
is_rtl = opts.detectTextDir && isRtlText(tiptip_content.text());
function moveTop() {
tip_class = tip_classes.top;
tip_top = org_top - tip_height - opts.edgeOffset - (arrow_height / 2);
tip_left = org_left + ((org_width - tip_width) / 2);
}
function moveBottom() {
tip_class = tip_classes.bottom;
tip_top = org_top + org_height + opts.edgeOffset + (arrow_height / 2);
tip_left = org_left + ((org_width - tip_width) / 2);
}
function moveLeft() {
tip_class = tip_classes.left;
tip_top = org_top + ((org_height - tip_height) / 2);
tip_left = org_left - tip_width - opts.edgeOffset - (arrow_width / 2);
}
function moveRight() {
tip_class = tip_classes.right;
tip_top = org_top + ((org_height - tip_height) / 2);
tip_left = org_left + org_width + opts.edgeOffset + (arrow_width / 2);
}
// Calculate the position of the tooltip.
if (opts.defaultPosition == 'bottom') {
moveBottom();
} else if (opts.defaultPosition == 'top') {
moveTop();
} else if (opts.defaultPosition == 'left' && !is_rtl) {
moveLeft();
} else if (opts.defaultPosition == 'left' && is_rtl) {
moveRight();
} else if (opts.defaultPosition == 'right' && !is_rtl) {
moveRight();
} else if (opts.defaultPosition == 'right' && is_rtl) {
moveLeft();
} else {
moveBottom();
}
// Flip the tooltip if off the window's viewport. (left <-> right and top <-> bottom).
if (tip_class == tip_classes.left && !is_rtl && tip_left < win_left) {
moveRight();
} else if (tip_class == tip_classes.left && is_rtl && tip_left - tip_width < win_left) {
moveRight();
} else if (tip_class == tip_classes.right && !is_rtl && tip_left > win_left + win_width) {
moveLeft();
} else if (tip_class == tip_classes.right && is_rtl && tip_left + tip_width > win_left + win_width) {
moveLeft();
} else if (tip_class == tip_classes.top && tip_top < win_top) {
moveBottom();
} else if (tip_class == tip_classes.bottom && tip_top > win_top + win_height) {
moveTop();
}
// Fix the vertical position if the tooltip is off the top or bottom sides of the window's viewport.
if (tip_class == tip_classes.left || tip_class == tip_classes.right) { // If positioned left or right check if the tooltip is off the top or bottom window's viewport.
if (tip_top + tip_height > win_height + win_top) { // If the bottom edge of the tooltip is off the bottom side of the window's viewport.
tip_top = org_top + org_height > win_height + win_top ? org_top + org_height - tip_height : win_height + win_top - tip_height; // Make 'bottom edge of the tooltip' == 'bottom side of the window's viewport'.
} else if (tip_top < win_top) { // If the top edge of the tooltip if off the top side of the window's viewport.
tip_top = org_top < win_top ? org_top : win_top; // Make 'top edge of the tooltip' == 'top side of the window's viewport'.
}
}
// Fix the horizontal position if the tooltip is off the right or left sides of the window's viewport.
if (tip_class == tip_classes.top || tip_class == tip_classes.bottom) {
if (tip_left + tip_width > win_width + win_left) { // If the right edge of the tooltip is off the right side of the window's viewport.
tip_left = org_left + org_width > win_width + win_left ? org_left + org_width - tip_width : win_width + win_left - tip_width; // Make 'right edge of the tooltip' == 'right side of the window's viewport'.
} else if (tip_left < win_left) { // If the left edge of the tooltip if off the left side of the window's viewport.
tip_left = org_left < win_left ? org_left : win_left; // Make 'left edge of the tooltip' == 'left side of the window's viewport'.
}
}
// Apply the new position.
tiptip_holder
.css({ left: Math.round(tip_left), top: Math.round(tip_top) })
.removeClass(tip_classes.top)
.removeClass(tip_classes.bottom)
.removeClass(tip_classes.left)
.removeClass(tip_classes.right)
.addClass(tip_class);
// Position the arrow
if (tip_class == tip_classes.top) {
arrow_top = tip_height; // Position the arrow vertically on the top of the tooltip.
arrow_left = org_left - tip_left + ((org_width - arrow_width) / 2); // Center the arrow horizontally on the center of the target element.
} else if (tip_class == tip_classes.bottom) {
arrow_top = -arrow_height; // Position the arrow vertically on the bottom of the tooltip.
arrow_left = org_left - tip_left + ((org_width - arrow_width) / 2); // Center the arrow horizontally on the center of the target element.
} else if (tip_class == tip_classes.left) {
arrow_top = org_top - tip_top + ((org_height - arrow_height) / 2); // Center the arrow vertically on the center of the target element.
arrow_left = tip_width; // Position the arrow vertically on the left of the tooltip.
} else if (tip_class == tip_classes.right) {
arrow_top = org_top - tip_top + ((org_height - arrow_height) / 2); // Center the arrow vertically on the center of the target element.
arrow_left = -arrow_width; // Position the arrow vertically on the right of the tooltip.
}
tiptip_arrow
.css({ left: Math.round(arrow_left), top: Math.round(arrow_top) });
}
});
}
var ltrChars = 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF',
rtlChars = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC',
rtlDirCheckRe = new RegExp('^[^' + ltrChars + ']*[' + rtlChars + ']');
function isRtlText(text) {
return rtlDirCheckRe.test(text);
};
})(jQuery);