Subversion-Projekte lars-tiefland.cienc

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
9 lars 1
/*
2
 
3
Tooltipster 3.3.0 | 2014-11-08
4
A rockin' custom tooltip jQuery plugin
5
 
6
Developed by Caleb Jacob under the MIT license http://opensource.org/licenses/MIT
7
 
8
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9
 
10
*/
11
 
12
;(function ($, window, document) {
13
 
14
	var pluginName = "tooltipster",
15
		defaults = {
16
			animation: 'fade',
17
			arrow: true,
18
			arrowColor: '',
19
			autoClose: true,
20
			content: null,
21
			contentAsHTML: false,
22
			contentCloning: true,
23
			debug: true,
24
			delay: 200,
25
			minWidth: 0,
26
			maxWidth: null,
27
			functionInit: function(origin, content) {},
28
			functionBefore: function(origin, continueTooltip) {
29
				continueTooltip();
30
			},
31
			functionReady: function(origin, tooltip) {},
32
			functionAfter: function(origin) {},
33
			hideOnClick: false,
34
			icon: '(?)',
35
			iconCloning: true,
36
			iconDesktop: false,
37
			iconTouch: false,
38
			iconTheme: 'tooltipster-icon',
39
			interactive: false,
40
			interactiveTolerance: 350,
41
			multiple: false,
42
			offsetX: 0,
43
			offsetY: 0,
44
			onlyOne: false,
45
			position: 'top',
46
			positionTracker: false,
47
			positionTrackerCallback: function(origin){
48
				// the default tracker callback will close the tooltip when the trigger is
49
				// 'hover' (see https://github.com/iamceege/tooltipster/pull/253)
50
				if(this.option('trigger') == 'hover' && this.option('autoClose')) {
51
					this.hide();
52
				}
53
			},
54
			restoration: 'current',
55
			speed: 350,
56
			timer: 0,
57
			theme: 'tooltipster-default',
58
			touchDevices: true,
59
			trigger: 'hover',
60
			updateAnimation: true
61
		};
62
 
63
	function Plugin(element, options) {
64
 
65
		// list of instance variables
66
 
67
		this.bodyOverflowX;
68
		// stack of custom callbacks provided as parameters to API methods
69
		this.callbacks = {
70
			hide: [],
71
			show: []
72
		};
73
		this.checkInterval = null;
74
		// this will be the user content shown in the tooltip. A capital "C" is used because there is also a method called content()
75
		this.Content;
76
		// this is the original element which is being applied the tooltipster plugin
77
		this.$el = $(element);
78
		// this will be the element which triggers the appearance of the tooltip on hover/click/custom events.
79
		// it will be the same as this.$el if icons are not used (see in the options), otherwise it will correspond to the created icon
80
		this.$elProxy;
81
		this.elProxyPosition;
82
		this.enabled = true;
83
		this.options = $.extend({}, defaults, options);
84
		this.mouseIsOverProxy = false;
85
		// a unique namespace per instance, for easy selective unbinding
86
		this.namespace = 'tooltipster-'+ Math.round(Math.random()*100000);
87
		// Status (capital S) can be either : appearing, shown, disappearing, hidden
88
		this.Status = 'hidden';
89
		this.timerHide = null;
90
		this.timerShow = null;
91
		// this will be the tooltip element (jQuery wrapped HTML element)
92
		this.$tooltip;
93
 
94
		// for backward compatibility
95
		this.options.iconTheme = this.options.iconTheme.replace('.', '');
96
		this.options.theme = this.options.theme.replace('.', '');
97
 
98
		// launch
99
 
100
		this._init();
101
	}
102
 
103
	Plugin.prototype = {
104
 
105
		_init: function() {
106
 
107
			var self = this;
108
 
109
			// disable the plugin on old browsers (including IE7 and lower)
110
			if (document.querySelector) {
111
 
112
				// note : the content is null (empty) by default and can stay that way if the plugin remains initialized but not fed any content. The tooltip will just not appear.
113
 
114
				// let's save the initial value of the title attribute for later restoration if need be.
115
				var initialTitle = null;
116
				// it will already have been saved in case of multiple tooltips
117
				if (self.$el.data('tooltipster-initialTitle') === undefined) {
118
 
119
					initialTitle = self.$el.attr('title');
120
 
121
					// we do not want initialTitle to have the value "undefined" because of how jQuery's .data() method works
122
					if (initialTitle === undefined) initialTitle = null;
123
 
124
					self.$el.data('tooltipster-initialTitle', initialTitle);
125
				}
126
 
127
				// if content is provided in the options, its has precedence over the title attribute.
128
				// Note : an empty string is considered content, only 'null' represents the absence of content.
129
				// Also, an existing title="" attribute will result in an empty string content
130
				if (self.options.content !== null){
131
					self._content_set(self.options.content);
132
				}
133
				else {
134
					self._content_set(initialTitle);
135
				}
136
 
137
				var c = self.options.functionInit.call(self.$el, self.$el, self.Content);
138
				if(typeof c !== 'undefined') self._content_set(c);
139
 
140
				self.$el
141
					// strip the title off of the element to prevent the default tooltips from popping up
142
					.removeAttr('title')
143
					// to be able to find all instances on the page later (upon window events in particular)
144
					.addClass('tooltipstered');
145
 
146
				// detect if we're changing the tooltip origin to an icon
147
				// note about this condition : if the device has touch capability and self.options.iconTouch is false, you'll have no icons event though you may consider your device as a desktop if it also has a mouse. Not sure why someone would have this use case though.
148
				if ((!deviceHasTouchCapability && self.options.iconDesktop) || (deviceHasTouchCapability && self.options.iconTouch)) {
149
 
150
					// TODO : the tooltip should be automatically be given an absolute position to be near the origin. Otherwise, when the origin is floating or what, it's going to be nowhere near it and disturb the position flow of the page elements. It will imply that the icon also detects when its origin moves, to follow it : not trivial.
151
					// Until it's done, the icon feature does not really make sense since the user still has most of the work to do by himself
152
 
153
					// if the icon provided is in the form of a string
154
					if(typeof self.options.icon === 'string'){
155
						// wrap it in a span with the icon class
156
						self.$elProxy = $('<span class="'+ self.options.iconTheme +'"></span>');
157
						self.$elProxy.text(self.options.icon);
158
					}
159
					// if it is an object (sensible choice)
160
					else {
161
						// (deep) clone the object if iconCloning == true, to make sure every instance has its own proxy. We use the icon without wrapping, no need to. We do not give it a class either, as the user will undoubtedly style the object on his own and since our css properties may conflict with his own
162
						if (self.options.iconCloning) self.$elProxy = self.options.icon.clone(true);
163
						else self.$elProxy = self.options.icon;
164
					}
165
 
166
					self.$elProxy.insertAfter(self.$el);
167
				}
168
				else {
169
					self.$elProxy = self.$el;
170
				}
171
 
172
				// for 'click' and 'hover' triggers : bind on events to open the tooltip. Closing is now handled in _showNow() because of its bindings.
173
				// Notes about touch events :
174
					// - mouseenter, mouseleave and clicks happen even on pure touch devices because they are emulated. deviceIsPureTouch() is a simple attempt to detect them.
175
					// - on hybrid devices, we do not prevent touch gesture from opening tooltips. It would be too complex to differentiate real mouse events from emulated ones.
176
					// - we check deviceIsPureTouch() at each event rather than prior to binding because the situation may change during browsing
177
				if (self.options.trigger == 'hover') {
178
 
179
					// these binding are for mouse interaction only
180
					self.$elProxy
181
						.on('mouseenter.'+ self.namespace, function() {
182
							if (!deviceIsPureTouch() || self.options.touchDevices) {
183
								self.mouseIsOverProxy = true;
184
								self._show();
185
							}
186
						})
187
						.on('mouseleave.'+ self.namespace, function() {
188
							if (!deviceIsPureTouch() || self.options.touchDevices) {
189
								self.mouseIsOverProxy = false;
190
							}
191
						});
192
 
193
					// for touch interaction only
194
					if (deviceHasTouchCapability && self.options.touchDevices) {
195
 
196
						// for touch devices, we immediately display the tooltip because we cannot rely on mouseleave to handle the delay
197
						self.$elProxy.on('touchstart.'+ self.namespace, function() {
198
							self._showNow();
199
						});
200
					}
201
				}
202
				else if (self.options.trigger == 'click') {
203
 
204
					// note : for touch devices, we do not bind on touchstart, we only rely on the emulated clicks (triggered by taps)
205
					self.$elProxy.on('click.'+ self.namespace, function() {
206
						if (!deviceIsPureTouch() || self.options.touchDevices) {
207
							self._show();
208
						}
209
					});
210
				}
211
			}
212
		},
213
 
214
		// this function will schedule the opening of the tooltip after the delay, if there is one
215
		_show: function() {
216
 
217
			var self = this;
218
 
219
			if (self.Status != 'shown' && self.Status != 'appearing') {
220
 
221
				if (self.options.delay) {
222
					self.timerShow = setTimeout(function(){
223
 
224
						// for hover trigger, we check if the mouse is still over the proxy, otherwise we do not show anything
225
						if (self.options.trigger == 'click' || (self.options.trigger == 'hover' && self.mouseIsOverProxy)) {
226
							self._showNow();
227
						}
228
					}, self.options.delay);
229
				}
230
				else self._showNow();
231
			}
232
		},
233
 
234
		// this function will open the tooltip right away
235
		_showNow: function(callback) {
236
 
237
			var self = this;
238
 
239
			// call our constructor custom function before continuing
240
			self.options.functionBefore.call(self.$el, self.$el, function() {
241
 
242
				// continue only if the tooltip is enabled and has any content
243
				if (self.enabled && self.Content !== null) {
244
 
245
					// save the method callback and cancel hide method callbacks
246
					if (callback) self.callbacks.show.push(callback);
247
					self.callbacks.hide = [];
248
 
249
					//get rid of any appearance timer
250
					clearTimeout(self.timerShow);
251
					self.timerShow = null;
252
					clearTimeout(self.timerHide);
253
					self.timerHide = null;
254
 
255
					// if we only want one tooltip open at a time, close all auto-closing tooltips currently open and not already disappearing
256
					if (self.options.onlyOne) {
257
						$('.tooltipstered').not(self.$el).each(function(i,el) {
258
 
259
							var $el = $(el),
260
								nss = $el.data('tooltipster-ns');
261
 
262
							// iterate on all tooltips of the element
263
							$.each(nss, function(i, ns){
264
								var instance = $el.data(ns),
265
									// we have to use the public methods here
266
									s = instance.status(),
267
									ac = instance.option('autoClose');
268
 
269
								if (s !== 'hidden' && s !== 'disappearing' && ac) {
270
									instance.hide();
271
								}
272
							});
273
						});
274
					}
275
 
276
					var finish = function() {
277
						self.Status = 'shown';
278
 
279
						// trigger any show method custom callbacks and reset them
280
						$.each(self.callbacks.show, function(i,c) { c.call(self.$el); });
281
						self.callbacks.show = [];
282
					};
283
 
284
					// if this origin already has its tooltip open
285
					if (self.Status !== 'hidden') {
286
 
287
						// the timer (if any) will start (or restart) right now
288
						var extraTime = 0;
289
 
290
						// if it was disappearing, cancel that
291
						if (self.Status === 'disappearing') {
292
 
293
							self.Status = 'appearing';
294
 
295
							if (supportsTransitions()) {
296
 
297
								self.$tooltip
298
									.clearQueue()
299
									.removeClass('tooltipster-dying')
300
									.addClass('tooltipster-'+ self.options.animation +'-show');
301
 
302
								if (self.options.speed > 0) self.$tooltip.delay(self.options.speed);
303
 
304
								self.$tooltip.queue(finish);
305
							}
306
							else {
307
								// in case the tooltip was currently fading out, bring it back to life
308
								self.$tooltip
309
									.stop()
310
									.fadeIn(finish);
311
							}
312
						}
313
						// if the tooltip is already open, we still need to trigger the method custom callback
314
						else if(self.Status === 'shown') {
315
							finish();
316
						}
317
					}
318
					// if the tooltip isn't already open, open that sucker up!
319
					else {
320
 
321
						self.Status = 'appearing';
322
 
323
						// the timer (if any) will start when the tooltip has fully appeared after its transition
324
						var extraTime = self.options.speed;
325
 
326
						// disable horizontal scrollbar to keep overflowing tooltips from jacking with it and then restore it to its previous value
327
						self.bodyOverflowX = $('body').css('overflow-x');
328
						$('body').css('overflow-x', 'hidden');
329
 
330
						// get some other settings related to building the tooltip
331
						var animation = 'tooltipster-' + self.options.animation,
332
							animationSpeed = '-webkit-transition-duration: '+ self.options.speed +'ms; -webkit-animation-duration: '+ self.options.speed +'ms; -moz-transition-duration: '+ self.options.speed +'ms; -moz-animation-duration: '+ self.options.speed +'ms; -o-transition-duration: '+ self.options.speed +'ms; -o-animation-duration: '+ self.options.speed +'ms; -ms-transition-duration: '+ self.options.speed +'ms; -ms-animation-duration: '+ self.options.speed +'ms; transition-duration: '+ self.options.speed +'ms; animation-duration: '+ self.options.speed +'ms;',
333
							minWidth = self.options.minWidth ? 'min-width:'+ Math.round(self.options.minWidth) +'px;' : '',
334
							maxWidth = self.options.maxWidth ? 'max-width:'+ Math.round(self.options.maxWidth) +'px;' : '',
335
							pointerEvents = self.options.interactive ? 'pointer-events: auto;' : '';
336
 
337
						// build the base of our tooltip
338
						self.$tooltip = $('<div class="tooltipster-base '+ self.options.theme +'" style="'+ minWidth +' '+ maxWidth +' '+ pointerEvents +' '+ animationSpeed +'"><div class="tooltipster-content"></div></div>');
339
 
340
						// only add the animation class if the user has a browser that supports animations
341
						if (supportsTransitions()) self.$tooltip.addClass(animation);
342
 
343
						// insert the content
344
						self._content_insert();
345
 
346
						// attach
347
						self.$tooltip.appendTo('body');
348
 
349
						// do all the crazy calculations and positioning
350
						self.reposition();
351
 
352
						// call our custom callback since the content of the tooltip is now part of the DOM
353
						self.options.functionReady.call(self.$el, self.$el, self.$tooltip);
354
 
355
						// animate in the tooltip
356
						if (supportsTransitions()) {
357
 
358
							self.$tooltip.addClass(animation + '-show');
359
 
360
							if(self.options.speed > 0) self.$tooltip.delay(self.options.speed);
361
 
362
							self.$tooltip.queue(finish);
363
						}
364
						else {
365
							self.$tooltip.css('display', 'none').fadeIn(self.options.speed, finish);
366
						}
367
 
368
						// will check if our tooltip origin is removed while the tooltip is shown
369
						self._interval_set();
370
 
371
						// reposition on scroll (otherwise position:fixed element's tooltips will move away form their origin) and on resize (in case position can/has to be changed)
372
						$(window).on('scroll.'+ self.namespace +' resize.'+ self.namespace, function() {
373
							self.reposition();
374
						});
375
 
376
						// auto-close bindings
377
						if (self.options.autoClose) {
378
 
379
							// in case a listener is already bound for autoclosing (mouse or touch, hover or click), unbind it first
380
							$('body').off('.'+ self.namespace);
381
 
382
							// here we'll have to set different sets of bindings for both touch and mouse
383
							if (self.options.trigger == 'hover') {
384
 
385
								// if the user touches the body, hide
386
								if (deviceHasTouchCapability) {
387
									// timeout 0 : explanation below in click section
388
									setTimeout(function() {
389
										// we don't want to bind on click here because the initial touchstart event has not yet triggered its click event, which is thus about to happen
390
										$('body').on('touchstart.'+ self.namespace, function() {
391
											self.hide();
392
										});
393
									}, 0);
394
								}
395
 
396
								// if we have to allow interaction
397
								if (self.options.interactive) {
398
 
399
									// touch events inside the tooltip must not close it
400
									if (deviceHasTouchCapability) {
401
										self.$tooltip.on('touchstart.'+ self.namespace, function(event) {
402
											event.stopPropagation();
403
										});
404
									}
405
 
406
									// as for mouse interaction, we get rid of the tooltip only after the mouse has spent some time out of it
407
									var tolerance = null;
408
 
409
									self.$elProxy.add(self.$tooltip)
410
										// hide after some time out of the proxy and the tooltip
411
										.on('mouseleave.'+ self.namespace + '-autoClose', function() {
412
											clearTimeout(tolerance);
413
											tolerance = setTimeout(function(){
414
												self.hide();
415
											}, self.options.interactiveTolerance);
416
										})
417
										// suspend timeout when the mouse is over the proxy or the tooltip
418
										.on('mouseenter.'+ self.namespace + '-autoClose', function() {
419
											clearTimeout(tolerance);
420
										});
421
								}
422
								// if this is a non-interactive tooltip, get rid of it if the mouse leaves
423
								else {
424
									self.$elProxy.on('mouseleave.'+ self.namespace + '-autoClose', function() {
425
										self.hide();
426
									});
427
								}
428
 
429
								// close the tooltip when the proxy gets a click (common behavior of native tooltips)
430
								if (self.options.hideOnClick) {
431
 
432
									self.$elProxy.on('click.'+ self.namespace + '-autoClose', function() {
433
										self.hide();
434
									});
435
								}
436
							}
437
							// here we'll set the same bindings for both clicks and touch on the body to hide the tooltip
438
							else if(self.options.trigger == 'click'){
439
 
440
								// use a timeout to prevent immediate closing if the method was called on a click event and if options.delay == 0 (because of bubbling)
441
								setTimeout(function() {
442
									$('body').on('click.'+ self.namespace +' touchstart.'+ self.namespace, function() {
443
										self.hide();
444
									});
445
								}, 0);
446
 
447
								// if interactive, we'll stop the events that were emitted from inside the tooltip to stop autoClosing
448
								if (self.options.interactive) {
449
 
450
									// note : the touch events will just not be used if the plugin is not enabled on touch devices
451
									self.$tooltip.on('click.'+ self.namespace +' touchstart.'+ self.namespace, function(event) {
452
										event.stopPropagation();
453
									});
454
								}
455
							}
456
						}
457
					}
458
 
459
					// if we have a timer set, let the countdown begin
460
					if (self.options.timer > 0) {
461
 
462
						self.timerHide = setTimeout(function() {
463
							self.timerHide = null;
464
							self.hide();
465
						}, self.options.timer + extraTime);
466
					}
467
				}
468
			});
469
		},
470
 
471
		_interval_set: function() {
472
 
473
			var self = this;
474
 
475
			self.checkInterval = setInterval(function() {
476
 
477
				// if the tooltip and/or its interval should be stopped
478
				if (
479
						// if the origin has been removed
480
						$('body').find(self.$el).length === 0
481
						// if the elProxy has been removed
482
					||	$('body').find(self.$elProxy).length === 0
483
						// if the tooltip has been closed
484
					||	self.Status == 'hidden'
485
						// if the tooltip has somehow been removed
486
					||	$('body').find(self.$tooltip).length === 0
487
				) {
488
					// remove the tooltip if it's still here
489
					if (self.Status == 'shown' || self.Status == 'appearing') self.hide();
490
 
491
					// clear this interval as it is no longer necessary
492
					self._interval_cancel();
493
				}
494
				// if everything is alright
495
				else {
496
					// compare the former and current positions of the elProxy to reposition the tooltip if need be
497
					if(self.options.positionTracker){
498
 
499
						var p = self._repositionInfo(self.$elProxy),
500
							identical = false;
501
 
502
						// compare size first (a change requires repositioning too)
503
						if(areEqual(p.dimension, self.elProxyPosition.dimension)){
504
 
505
							// for elements with a fixed position, we track the top and left properties (relative to window)
506
							if(self.$elProxy.css('position') === 'fixed'){
507
								if(areEqual(p.position, self.elProxyPosition.position)) identical = true;
508
							}
509
							// otherwise, track total offset (relative to document)
510
							else {
511
								if(areEqual(p.offset, self.elProxyPosition.offset)) identical = true;
512
							}
513
						}
514
 
515
						if(!identical){
516
							self.reposition();
517
							self.options.positionTrackerCallback.call(self, self.$el);
518
						}
519
					}
520
				}
521
			}, 200);
522
		},
523
 
524
		_interval_cancel: function() {
525
			clearInterval(this.checkInterval);
526
			// clean delete
527
			this.checkInterval = null;
528
		},
529
 
530
		_content_set: function(content) {
531
			// clone if asked. Cloning the object makes sure that each instance has its own version of the content (in case a same object were provided for several instances)
532
			// reminder : typeof null === object
533
			if (typeof content === 'object' && content !== null && this.options.contentCloning) {
534
				content = content.clone(true);
535
			}
536
			this.Content = content;
537
		},
538
 
539
		_content_insert: function() {
540
 
541
			var self = this,
542
				$d = this.$tooltip.find('.tooltipster-content');
543
 
544
			if (typeof self.Content === 'string' && !self.options.contentAsHTML) {
545
				$d.text(self.Content);
546
			}
547
			else {
548
				$d
549
					.empty()
550
					.append(self.Content);
551
			}
552
		},
553
 
554
		_update: function(content) {
555
 
556
			var self = this;
557
 
558
			// change the content
559
			self._content_set(content);
560
 
561
			if (self.Content !== null) {
562
 
563
				// update the tooltip if it is open
564
				if (self.Status !== 'hidden') {
565
 
566
					// reset the content in the tooltip
567
					self._content_insert();
568
 
569
					// reposition and resize the tooltip
570
					self.reposition();
571
 
572
					// if we want to play a little animation showing the content changed
573
					if (self.options.updateAnimation) {
574
 
575
						if (supportsTransitions()) {
576
 
577
							self.$tooltip.css({
578
								'width': '',
579
								'-webkit-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms',
580
								'-moz-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms',
581
								'-o-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms',
582
								'-ms-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms',
583
								'transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms'
584
							}).addClass('tooltipster-content-changing');
585
 
586
							// reset the CSS transitions and finish the change animation
587
							setTimeout(function() {
588
 
589
								if(self.Status != 'hidden'){
590
 
591
									self.$tooltip.removeClass('tooltipster-content-changing');
592
 
593
									// after the changing animation has completed, reset the CSS transitions
594
									setTimeout(function() {
595
 
596
										if(self.Status !== 'hidden'){
597
											self.$tooltip.css({
598
												'-webkit-transition': self.options.speed + 'ms',
599
												'-moz-transition': self.options.speed + 'ms',
600
												'-o-transition': self.options.speed + 'ms',
601
												'-ms-transition': self.options.speed + 'ms',
602
												'transition': self.options.speed + 'ms'
603
											});
604
										}
605
									}, self.options.speed);
606
								}
607
							}, self.options.speed);
608
						}
609
						else {
610
							self.$tooltip.fadeTo(self.options.speed, 0.5, function() {
611
								if(self.Status != 'hidden'){
612
									self.$tooltip.fadeTo(self.options.speed, 1);
613
								}
614
							});
615
						}
616
					}
617
				}
618
			}
619
			else {
620
				self.hide();
621
			}
622
		},
623
 
624
		_repositionInfo: function($el) {
625
			return {
626
				dimension: {
627
					height: $el.outerHeight(false),
628
					width: $el.outerWidth(false)
629
				},
630
				offset: $el.offset(),
631
				position: {
632
					left: parseInt($el.css('left')),
633
					top: parseInt($el.css('top'))
634
				}
635
			};
636
		},
637
 
638
		hide: function(callback) {
639
 
640
			var self = this;
641
 
642
			// save the method custom callback and cancel any show method custom callbacks
643
			if (callback) self.callbacks.hide.push(callback);
644
			self.callbacks.show = [];
645
 
646
			// get rid of any appearance timeout
647
			clearTimeout(self.timerShow);
648
			self.timerShow = null;
649
			clearTimeout(self.timerHide);
650
			self.timerHide = null;
651
 
652
			var finishCallbacks = function() {
653
				// trigger any hide method custom callbacks and reset them
654
				$.each(self.callbacks.hide, function(i,c) { c.call(self.$el); });
655
				self.callbacks.hide = [];
656
			};
657
 
658
			// hide
659
			if (self.Status == 'shown' || self.Status == 'appearing') {
660
 
661
				self.Status = 'disappearing';
662
 
663
				var finish = function() {
664
 
665
					self.Status = 'hidden';
666
 
667
					// detach our content object first, so the next jQuery's remove() call does not unbind its event handlers
668
					if (typeof self.Content == 'object' && self.Content !== null) {
669
						self.Content.detach();
670
					}
671
 
672
					self.$tooltip.remove();
673
					self.$tooltip = null;
674
 
675
					// unbind orientationchange, scroll and resize listeners
676
					$(window).off('.'+ self.namespace);
677
 
678
					$('body')
679
						// unbind any auto-closing click/touch listeners
680
						.off('.'+ self.namespace)
681
						.css('overflow-x', self.bodyOverflowX);
682
 
683
					// unbind any auto-closing click/touch listeners
684
					$('body').off('.'+ self.namespace);
685
 
686
					// unbind any auto-closing hover listeners
687
					self.$elProxy.off('.'+ self.namespace + '-autoClose');
688
 
689
					// call our constructor custom callback function
690
					self.options.functionAfter.call(self.$el, self.$el);
691
 
692
					// call our method custom callbacks functions
693
					finishCallbacks();
694
				};
695
 
696
				if (supportsTransitions()) {
697
 
698
					self.$tooltip
699
						.clearQueue()
700
						.removeClass('tooltipster-' + self.options.animation + '-show')
701
						// for transitions only
702
						.addClass('tooltipster-dying');
703
 
704
					if(self.options.speed > 0) self.$tooltip.delay(self.options.speed);
705
 
706
					self.$tooltip.queue(finish);
707
				}
708
				else {
709
					self.$tooltip
710
						.stop()
711
						.fadeOut(self.options.speed, finish);
712
				}
713
			}
714
			// if the tooltip is already hidden, we still need to trigger the method custom callback
715
			else if(self.Status == 'hidden') {
716
				finishCallbacks();
717
			}
718
 
719
			return self;
720
		},
721
 
722
		// the public show() method is actually an alias for the private showNow() method
723
		show: function(callback) {
724
			this._showNow(callback);
725
			return this;
726
		},
727
 
728
		// 'update' is deprecated in favor of 'content' but is kept for backward compatibility
729
		update: function(c) {
730
			return this.content(c);
731
		},
732
		content: function(c) {
733
			// getter method
734
			if(typeof c === 'undefined'){
735
				return this.Content;
736
			}
737
			// setter method
738
			else {
739
				this._update(c);
740
				return this;
741
			}
742
		},
743
 
744
		reposition: function() {
745
 
746
			var self = this;
747
 
748
			// in case the tooltip has been removed from DOM manually
749
			if ($('body').find(self.$tooltip).length !== 0) {
750
 
751
				// reset width
752
				self.$tooltip.css('width', '');
753
 
754
				// find variables to determine placement
755
				self.elProxyPosition = self._repositionInfo(self.$elProxy);
756
				var arrowReposition = null,
757
					windowWidth = $(window).width(),
758
					// shorthand
759
					proxy = self.elProxyPosition,
760
					tooltipWidth = self.$tooltip.outerWidth(false),
761
					tooltipInnerWidth = self.$tooltip.innerWidth() + 1, // this +1 stops FireFox from sometimes forcing an additional text line
762
					tooltipHeight = self.$tooltip.outerHeight(false);
763
 
764
				// if this is an <area> tag inside a <map>, all hell breaks loose. Recalculate all the measurements based on coordinates
765
				if (self.$elProxy.is('area')) {
766
					var areaShape = self.$elProxy.attr('shape'),
767
						mapName = self.$elProxy.parent().attr('name'),
768
						map = $('img[usemap="#'+ mapName +'"]'),
769
						mapOffsetLeft = map.offset().left,
770
						mapOffsetTop = map.offset().top,
771
						areaMeasurements = self.$elProxy.attr('coords') !== undefined ? self.$elProxy.attr('coords').split(',') : undefined;
772
 
773
					if (areaShape == 'circle') {
774
						var areaLeft = parseInt(areaMeasurements[0]),
775
							areaTop = parseInt(areaMeasurements[1]),
776
							areaWidth = parseInt(areaMeasurements[2]);
777
						proxy.dimension.height = areaWidth * 2;
778
						proxy.dimension.width = areaWidth * 2;
779
						proxy.offset.top = mapOffsetTop + areaTop - areaWidth;
780
						proxy.offset.left = mapOffsetLeft + areaLeft - areaWidth;
781
					}
782
					else if (areaShape == 'rect') {
783
						var areaLeft = parseInt(areaMeasurements[0]),
784
							areaTop = parseInt(areaMeasurements[1]),
785
							areaRight = parseInt(areaMeasurements[2]),
786
							areaBottom = parseInt(areaMeasurements[3]);
787
						proxy.dimension.height = areaBottom - areaTop;
788
						proxy.dimension.width = areaRight - areaLeft;
789
						proxy.offset.top = mapOffsetTop + areaTop;
790
						proxy.offset.left = mapOffsetLeft + areaLeft;
791
					}
792
					else if (areaShape == 'poly') {
793
						var areaXs = [],
794
							areaYs = [],
795
							areaSmallestX = 0,
796
							areaSmallestY = 0,
797
							areaGreatestX = 0,
798
							areaGreatestY = 0,
799
							arrayAlternate = 'even';
800
 
801
						for (var i = 0; i < areaMeasurements.length; i++) {
802
							var areaNumber = parseInt(areaMeasurements[i]);
803
 
804
							if (arrayAlternate == 'even') {
805
								if (areaNumber > areaGreatestX) {
806
									areaGreatestX = areaNumber;
807
									if (i === 0) {
808
										areaSmallestX = areaGreatestX;
809
									}
810
								}
811
 
812
								if (areaNumber < areaSmallestX) {
813
									areaSmallestX = areaNumber;
814
								}
815
 
816
								arrayAlternate = 'odd';
817
							}
818
							else {
819
								if (areaNumber > areaGreatestY) {
820
									areaGreatestY = areaNumber;
821
									if (i == 1) {
822
										areaSmallestY = areaGreatestY;
823
									}
824
								}
825
 
826
								if (areaNumber < areaSmallestY) {
827
									areaSmallestY = areaNumber;
828
								}
829
 
830
								arrayAlternate = 'even';
831
							}
832
						}
833
 
834
						proxy.dimension.height = areaGreatestY - areaSmallestY;
835
						proxy.dimension.width = areaGreatestX - areaSmallestX;
836
						proxy.offset.top = mapOffsetTop + areaSmallestY;
837
						proxy.offset.left = mapOffsetLeft + areaSmallestX;
838
					}
839
					else {
840
						proxy.dimension.height = map.outerHeight(false);
841
						proxy.dimension.width = map.outerWidth(false);
842
						proxy.offset.top = mapOffsetTop;
843
						proxy.offset.left = mapOffsetLeft;
844
					}
845
				}
846
 
847
				// our function and global vars for positioning our tooltip
848
				var myLeft = 0,
849
					myLeftMirror = 0,
850
					myTop = 0,
851
					offsetY = parseInt(self.options.offsetY),
852
					offsetX = parseInt(self.options.offsetX),
853
					// this is the arrow position that will eventually be used. It may differ from the position option if the tooltip cannot be displayed in this position
854
					practicalPosition = self.options.position;
855
 
856
				// a function to detect if the tooltip is going off the screen horizontally. If so, reposition the crap out of it!
857
				function dontGoOffScreenX() {
858
 
859
					var windowLeft = $(window).scrollLeft();
860
 
861
					// if the tooltip goes off the left side of the screen, line it up with the left side of the window
862
					if((myLeft - windowLeft) < 0) {
863
						arrowReposition = myLeft - windowLeft;
864
						myLeft = windowLeft;
865
					}
866
 
867
					// if the tooltip goes off the right of the screen, line it up with the right side of the window
868
					if (((myLeft + tooltipWidth) - windowLeft) > windowWidth) {
869
						arrowReposition = myLeft - ((windowWidth + windowLeft) - tooltipWidth);
870
						myLeft = (windowWidth + windowLeft) - tooltipWidth;
871
					}
872
				}
873
 
874
				// a function to detect if the tooltip is going off the screen vertically. If so, switch to the opposite!
875
				function dontGoOffScreenY(switchTo, switchFrom) {
876
					// if it goes off the top off the page
877
					if(((proxy.offset.top - $(window).scrollTop() - tooltipHeight - offsetY - 12) < 0) && (switchFrom.indexOf('top') > -1)) {
878
						practicalPosition = switchTo;
879
					}
880
 
881
					// if it goes off the bottom of the page
882
					if (((proxy.offset.top + proxy.dimension.height + tooltipHeight + 12 + offsetY) > ($(window).scrollTop() + $(window).height())) && (switchFrom.indexOf('bottom') > -1)) {
883
						practicalPosition = switchTo;
884
						myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12;
885
					}
886
				}
887
 
888
				if(practicalPosition == 'top') {
889
					var leftDifference = (proxy.offset.left + tooltipWidth) - (proxy.offset.left + proxy.dimension.width);
890
					myLeft = (proxy.offset.left + offsetX) - (leftDifference / 2);
891
					myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12;
892
					dontGoOffScreenX();
893
					dontGoOffScreenY('bottom', 'top');
894
				}
895
 
896
				if(practicalPosition == 'top-left') {
897
					myLeft = proxy.offset.left + offsetX;
898
					myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12;
899
					dontGoOffScreenX();
900
					dontGoOffScreenY('bottom-left', 'top-left');
901
				}
902
 
903
				if(practicalPosition == 'top-right') {
904
					myLeft = (proxy.offset.left + proxy.dimension.width + offsetX) - tooltipWidth;
905
					myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12;
906
					dontGoOffScreenX();
907
					dontGoOffScreenY('bottom-right', 'top-right');
908
				}
909
 
910
				if(practicalPosition == 'bottom') {
911
					var leftDifference = (proxy.offset.left + tooltipWidth) - (proxy.offset.left + proxy.dimension.width);
912
					myLeft = proxy.offset.left - (leftDifference / 2) + offsetX;
913
					myTop = (proxy.offset.top + proxy.dimension.height) + offsetY + 12;
914
					dontGoOffScreenX();
915
					dontGoOffScreenY('top', 'bottom');
916
				}
917
 
918
				if(practicalPosition == 'bottom-left') {
919
					myLeft = proxy.offset.left + offsetX;
920
					myTop = (proxy.offset.top + proxy.dimension.height) + offsetY + 12;
921
					dontGoOffScreenX();
922
					dontGoOffScreenY('top-left', 'bottom-left');
923
				}
924
 
925
				if(practicalPosition == 'bottom-right') {
926
					myLeft = (proxy.offset.left + proxy.dimension.width + offsetX) - tooltipWidth;
927
					myTop = (proxy.offset.top + proxy.dimension.height) + offsetY + 12;
928
					dontGoOffScreenX();
929
					dontGoOffScreenY('top-right', 'bottom-right');
930
				}
931
 
932
				if(practicalPosition == 'left') {
933
					myLeft = proxy.offset.left - offsetX - tooltipWidth - 12;
934
					myLeftMirror = proxy.offset.left + offsetX + proxy.dimension.width + 12;
935
					var topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height);
936
					myTop = proxy.offset.top - (topDifference / 2) - offsetY;
937
 
938
					// if the tooltip goes off boths sides of the page
939
					if((myLeft < 0) && ((myLeftMirror + tooltipWidth) > windowWidth)) {
940
						var borderWidth = parseFloat(self.$tooltip.css('border-width')) * 2,
941
							newWidth = (tooltipWidth + myLeft) - borderWidth;
942
						self.$tooltip.css('width', newWidth + 'px');
943
 
944
						tooltipHeight = self.$tooltip.outerHeight(false);
945
						myLeft = proxy.offset.left - offsetX - newWidth - 12 - borderWidth;
946
						topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height);
947
						myTop = proxy.offset.top - (topDifference / 2) - offsetY;
948
					}
949
 
950
					// if it only goes off one side, flip it to the other side
951
					else if(myLeft < 0) {
952
						myLeft = proxy.offset.left + offsetX + proxy.dimension.width + 12;
953
						arrowReposition = 'left';
954
					}
955
				}
956
 
957
				if(practicalPosition == 'right') {
958
					myLeft = proxy.offset.left + offsetX + proxy.dimension.width + 12;
959
					myLeftMirror = proxy.offset.left - offsetX - tooltipWidth - 12;
960
					var topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height);
961
					myTop = proxy.offset.top - (topDifference / 2) - offsetY;
962
 
963
					// if the tooltip goes off boths sides of the page
964
					if(((myLeft + tooltipWidth) > windowWidth) && (myLeftMirror < 0)) {
965
						var borderWidth = parseFloat(self.$tooltip.css('border-width')) * 2,
966
							newWidth = (windowWidth - myLeft) - borderWidth;
967
						self.$tooltip.css('width', newWidth + 'px');
968
 
969
						tooltipHeight = self.$tooltip.outerHeight(false);
970
						topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height);
971
						myTop = proxy.offset.top - (topDifference / 2) - offsetY;
972
					}
973
 
974
					// if it only goes off one side, flip it to the other side
975
					else if((myLeft + tooltipWidth) > windowWidth) {
976
						myLeft = proxy.offset.left - offsetX - tooltipWidth - 12;
977
						arrowReposition = 'right';
978
					}
979
				}
980
 
981
				// if arrow is set true, style it and append it
982
				if (self.options.arrow) {
983
 
984
					var arrowClass = 'tooltipster-arrow-' + practicalPosition;
985
 
986
					// set color of the arrow
987
					if(self.options.arrowColor.length < 1) {
988
						var arrowColor = self.$tooltip.css('background-color');
989
					}
990
					else {
991
						var arrowColor = self.options.arrowColor;
992
					}
993
 
994
					// if the tooltip was going off the page and had to re-adjust, we need to update the arrow's position
995
					if (!arrowReposition) {
996
						arrowReposition = '';
997
					}
998
					else if (arrowReposition == 'left') {
999
						arrowClass = 'tooltipster-arrow-right';
1000
						arrowReposition = '';
1001
					}
1002
					else if (arrowReposition == 'right') {
1003
						arrowClass = 'tooltipster-arrow-left';
1004
						arrowReposition = '';
1005
					}
1006
					else {
1007
						arrowReposition = 'left:'+ Math.round(arrowReposition) +'px;';
1008
					}
1009
 
1010
					// building the logic to create the border around the arrow of the tooltip
1011
					if ((practicalPosition == 'top') || (practicalPosition == 'top-left') || (practicalPosition == 'top-right')) {
1012
						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-bottom-width')),
1013
							tooltipBorderColor = self.$tooltip.css('border-bottom-color');
1014
					}
1015
					else if ((practicalPosition == 'bottom') || (practicalPosition == 'bottom-left') || (practicalPosition == 'bottom-right')) {
1016
						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-top-width')),
1017
							tooltipBorderColor = self.$tooltip.css('border-top-color');
1018
					}
1019
					else if (practicalPosition == 'left') {
1020
						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-right-width')),
1021
							tooltipBorderColor = self.$tooltip.css('border-right-color');
1022
					}
1023
					else if (practicalPosition == 'right') {
1024
						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-left-width')),
1025
							tooltipBorderColor = self.$tooltip.css('border-left-color');
1026
					}
1027
					else {
1028
						var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-bottom-width')),
1029
							tooltipBorderColor = self.$tooltip.css('border-bottom-color');
1030
					}
1031
 
1032
					if (tooltipBorderWidth > 1) {
1033
						tooltipBorderWidth++;
1034
					}
1035
 
1036
					var arrowBorder = '';
1037
					if (tooltipBorderWidth !== 0) {
1038
						var arrowBorderSize = '',
1039
							arrowBorderColor = 'border-color: '+ tooltipBorderColor +';';
1040
						if (arrowClass.indexOf('bottom') !== -1) {
1041
							arrowBorderSize = 'margin-top: -'+ Math.round(tooltipBorderWidth) +'px;';
1042
						}
1043
						else if (arrowClass.indexOf('top') !== -1) {
1044
							arrowBorderSize = 'margin-bottom: -'+ Math.round(tooltipBorderWidth) +'px;';
1045
						}
1046
						else if (arrowClass.indexOf('left') !== -1) {
1047
							arrowBorderSize = 'margin-right: -'+ Math.round(tooltipBorderWidth) +'px;';
1048
						}
1049
						else if (arrowClass.indexOf('right') !== -1) {
1050
							arrowBorderSize = 'margin-left: -'+ Math.round(tooltipBorderWidth) +'px;';
1051
						}
1052
						arrowBorder = '<span class="tooltipster-arrow-border" style="'+ arrowBorderSize +' '+ arrowBorderColor +';"></span>';
1053
					}
1054
 
1055
					// if the arrow already exists, remove and replace it
1056
					self.$tooltip.find('.tooltipster-arrow').remove();
1057
 
1058
					// build out the arrow and append it
1059
					var arrowConstruct = '<div class="'+ arrowClass +' tooltipster-arrow" style="'+ arrowReposition +'">'+ arrowBorder +'<span style="border-color:'+ arrowColor +';"></span></div>';
1060
					self.$tooltip.append(arrowConstruct);
1061
				}
1062
 
1063
				// position the tooltip
1064
				self.$tooltip.css({'top': Math.round(myTop) + 'px', 'left': Math.round(myLeft) + 'px'});
1065
			}
1066
 
1067
			return self;
1068
		},
1069
 
1070
		enable: function() {
1071
			this.enabled = true;
1072
			return this;
1073
		},
1074
 
1075
		disable: function() {
1076
			// hide first, in case the tooltip would not disappear on its own (autoClose false)
1077
			this.hide();
1078
			this.enabled = false;
1079
			return this;
1080
		},
1081
 
1082
		destroy: function() {
1083
 
1084
			var self = this;
1085
 
1086
			self.hide();
1087
 
1088
			// remove the icon, if any
1089
			if (self.$el[0] !== self.$elProxy[0]) {
1090
				self.$elProxy.remove();
1091
			}
1092
 
1093
			self.$el
1094
				.removeData(self.namespace)
1095
				.off('.'+ self.namespace);
1096
 
1097
			var ns = self.$el.data('tooltipster-ns');
1098
 
1099
			// if there are no more tooltips on this element
1100
			if(ns.length === 1){
1101
 
1102
				// optional restoration of a title attribute
1103
				var title = null;
1104
				if (self.options.restoration === 'previous'){
1105
					title = self.$el.data('tooltipster-initialTitle');
1106
				}
1107
				else if(self.options.restoration === 'current'){
1108
 
1109
					// old school technique to stringify when outerHTML is not supported
1110
					title =
1111
						(typeof self.Content === 'string') ?
1112
						self.Content :
1113
						$('<div></div>').append(self.Content).html();
1114
				}
1115
 
1116
				if (title) {
1117
					self.$el.attr('title', title);
1118
				}
1119
 
1120
				// final cleaning
1121
				self.$el
1122
					.removeClass('tooltipstered')
1123
					.removeData('tooltipster-ns')
1124
					.removeData('tooltipster-initialTitle');
1125
			}
1126
			else {
1127
				// remove the instance namespace from the list of namespaces of tooltips present on the element
1128
				ns = $.grep(ns, function(el, i){
1129
					return el !== self.namespace;
1130
				});
1131
				self.$el.data('tooltipster-ns', ns);
1132
			}
1133
 
1134
			return self;
1135
		},
1136
 
1137
		elementIcon: function() {
1138
			return (this.$el[0] !== this.$elProxy[0]) ? this.$elProxy[0] : undefined;
1139
		},
1140
 
1141
		elementTooltip: function() {
1142
			return this.$tooltip ? this.$tooltip[0] : undefined;
1143
		},
1144
 
1145
		// public methods but for internal use only
1146
		// getter if val is ommitted, setter otherwise
1147
		option: function(o, val) {
1148
			if (typeof val == 'undefined') return this.options[o];
1149
			else {
1150
				this.options[o] = val;
1151
				return this;
1152
			}
1153
		},
1154
		status: function() {
1155
			return this.Status;
1156
		}
1157
	};
1158
 
1159
	$.fn[pluginName] = function () {
1160
 
1161
		// for using in closures
1162
		var args = arguments;
1163
 
1164
		// if we are not in the context of jQuery wrapped HTML element(s) :
1165
		// this happens when calling static methods in the form $.fn.tooltipster('methodName'), or when calling $(sel).tooltipster('methodName or options') where $(sel) does not match anything
1166
		if (this.length === 0) {
1167
 
1168
			// if the first argument is a method name
1169
			if (typeof args[0] === 'string') {
1170
 
1171
				var methodIsStatic = true;
1172
 
1173
				// list static methods here (usable by calling $.fn.tooltipster('methodName');)
1174
				switch (args[0]) {
1175
 
1176
					case 'setDefaults':
1177
						// change default options for all future instances
1178
						$.extend(defaults, args[1]);
1179
						break;
1180
 
1181
					default:
1182
						methodIsStatic = false;
1183
						break;
1184
				}
1185
 
1186
				// $.fn.tooltipster('methodName') calls will return true
1187
				if (methodIsStatic) return true;
1188
				// $(sel).tooltipster('methodName') calls will return the list of objects event though it's empty because chaining should work on empty lists
1189
				else return this;
1190
			}
1191
			// the first argument is undefined or an object of options : we are initalizing but there is no element matched by selector
1192
			else {
1193
				// still chainable : same as above
1194
				return this;
1195
			}
1196
		}
1197
		// this happens when calling $(sel).tooltipster('methodName or options') where $(sel) matches one or more elements
1198
		else {
1199
 
1200
			// method calls
1201
			if (typeof args[0] === 'string') {
1202
 
1203
				var v = '#*$~&';
1204
 
1205
				this.each(function() {
1206
 
1207
					// retrieve the namepaces of the tooltip(s) that exist on that element. We will interact with the first tooltip only.
1208
					var ns = $(this).data('tooltipster-ns'),
1209
						// self represents the instance of the first tooltipster plugin associated to the current HTML object of the loop
1210
						self = ns ? $(this).data(ns[0]) : null;
1211
 
1212
					// if the current element holds a tooltipster instance
1213
					if (self) {
1214
 
1215
						if (typeof self[args[0]] === 'function') {
1216
							// note : args[1] and args[2] may not be defined
1217
							var resp = self[args[0]](args[1], args[2]);
1218
						}
1219
						else {
1220
							throw new Error('Unknown method .tooltipster("' + args[0] + '")');
1221
						}
1222
 
1223
						// if the function returned anything other than the instance itself (which implies chaining)
1224
						if (resp !== self){
1225
							v = resp;
1226
							// return false to stop .each iteration on the first element matched by the selector
1227
							return false;
1228
						}
1229
					}
1230
					else {
1231
						throw new Error('You called Tooltipster\'s "' + args[0] + '" method on an uninitialized element');
1232
					}
1233
				});
1234
 
1235
				return (v !== '#*$~&') ? v : this;
1236
			}
1237
			// first argument is undefined or an object : the tooltip is initializing
1238
			else {
1239
 
1240
				var instances = [],
1241
					// is there a defined value for the multiple option in the options object ?
1242
					multipleIsSet = args[0] && typeof args[0].multiple !== 'undefined',
1243
					// if the multiple option is set to true, or if it's not defined but set to true in the defaults
1244
					multiple = (multipleIsSet && args[0].multiple) || (!multipleIsSet && defaults.multiple),
1245
					// same for debug
1246
					debugIsSet = args[0] && typeof args[0].debug !== 'undefined',
1247
					debug = (debugIsSet && args[0].debug) || (!debugIsSet && defaults.debug);
1248
 
1249
				// initialize a tooltipster instance for each element if it doesn't already have one or if the multiple option is set, and attach the object to it
1250
				this.each(function () {
1251
 
1252
					var go = false,
1253
						ns = $(this).data('tooltipster-ns'),
1254
						instance = null;
1255
 
1256
					if (!ns) {
1257
						go = true;
1258
					}
1259
					else if (multiple) {
1260
						go = true;
1261
					}
1262
					else if (debug) {
1263
						console.log('Tooltipster: one or more tooltips are already attached to this element: ignoring. Use the "multiple" option to attach more tooltips.');
1264
					}
1265
 
1266
					if (go) {
1267
						instance = new Plugin(this, args[0]);
1268
 
1269
						// save the reference of the new instance
1270
						if (!ns) ns = [];
1271
						ns.push(instance.namespace);
1272
						$(this).data('tooltipster-ns', ns)
1273
 
1274
						// save the instance itself
1275
						$(this).data(instance.namespace, instance);
1276
					}
1277
 
1278
					instances.push(instance);
1279
				});
1280
 
1281
				if (multiple) return instances;
1282
				else return this;
1283
			}
1284
		}
1285
	};
1286
 
1287
	// quick & dirty compare function (not bijective nor multidimensional)
1288
	function areEqual(a,b) {
1289
		var same = true;
1290
		$.each(a, function(i, el){
1291
			if(typeof b[i] === 'undefined' || a[i] !== b[i]){
1292
				same = false;
1293
				return false;
1294
			}
1295
		});
1296
		return same;
1297
	}
1298
 
1299
	// detect if this device can trigger touch events
1300
	var deviceHasTouchCapability = !!('ontouchstart' in window);
1301
 
1302
	// we'll assume the device has no mouse until we detect any mouse movement
1303
	var deviceHasMouse = false;
1304
	$('body').one('mousemove', function() {
1305
		deviceHasMouse = true;
1306
	});
1307
 
1308
	function deviceIsPureTouch() {
1309
		return (!deviceHasMouse && deviceHasTouchCapability);
1310
	}
1311
 
1312
	// detecting support for CSS transitions
1313
	function supportsTransitions() {
1314
		var b = document.body || document.documentElement,
1315
			s = b.style,
1316
			p = 'transition';
1317
 
1318
		if(typeof s[p] == 'string') {return true; }
1319
 
1320
		v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'],
1321
		p = p.charAt(0).toUpperCase() + p.substr(1);
1322
		for(var i=0; i<v.length; i++) {
1323
			if(typeof s[v[i] + p] == 'string') { return true; }
1324
		}
1325
		return false;
1326
	}
1327
})( jQuery, window, document );