unforassets 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +41 -39
  4. data/lib/unforassets/version.rb +1 -1
  5. data/vendor/assets/images/unforassets/jcrop/jcrop.gif +0 -0
  6. data/vendor/assets/javascripts/unforassets/accounting.js +4 -0
  7. data/vendor/assets/javascripts/unforassets/autonumeric.js +4 -0
  8. data/vendor/assets/javascripts/{big.js → unforassets/big.js} +0 -0
  9. data/vendor/assets/javascripts/unforassets/bootstrap-datetimepicker.js +2 -0
  10. data/vendor/assets/javascripts/unforassets/bootstrap-multiselect/plugins/collapsible-groups.js +92 -0
  11. data/vendor/assets/javascripts/unforassets/bootstrap-multiselect.js +1416 -0
  12. data/vendor/assets/javascripts/unforassets/chart.js +14 -0
  13. data/vendor/assets/javascripts/unforassets/jcrop.js +6 -0
  14. data/vendor/assets/javascripts/{jquery.blockui.js → unforassets/jquery-blockui.js} +0 -0
  15. data/vendor/assets/javascripts/unforassets/moment.js +505 -0
  16. data/vendor/assets/javascripts/unforassets/pnotify.js +31 -0
  17. data/vendor/assets/javascripts/unforassets/select2/i18n/ar.js +3 -0
  18. data/vendor/assets/javascripts/unforassets/select2/i18n/az.js +3 -0
  19. data/vendor/assets/javascripts/unforassets/select2/i18n/bg.js +3 -0
  20. data/vendor/assets/javascripts/unforassets/select2/i18n/ca.js +3 -0
  21. data/vendor/assets/javascripts/unforassets/select2/i18n/cs.js +3 -0
  22. data/vendor/assets/javascripts/unforassets/select2/i18n/da.js +3 -0
  23. data/vendor/assets/javascripts/unforassets/select2/i18n/de.js +3 -0
  24. data/vendor/assets/javascripts/unforassets/select2/i18n/el.js +3 -0
  25. data/vendor/assets/javascripts/unforassets/select2/i18n/en.js +3 -0
  26. data/vendor/assets/javascripts/unforassets/select2/i18n/es.js +3 -0
  27. data/vendor/assets/javascripts/unforassets/select2/i18n/et.js +3 -0
  28. data/vendor/assets/javascripts/unforassets/select2/i18n/eu.js +3 -0
  29. data/vendor/assets/javascripts/unforassets/select2/i18n/fa.js +3 -0
  30. data/vendor/assets/javascripts/unforassets/select2/i18n/fi.js +3 -0
  31. data/vendor/assets/javascripts/unforassets/select2/i18n/fr.js +3 -0
  32. data/vendor/assets/javascripts/unforassets/select2/i18n/gl.js +3 -0
  33. data/vendor/assets/javascripts/unforassets/select2/i18n/he.js +3 -0
  34. data/vendor/assets/javascripts/unforassets/select2/i18n/hi.js +3 -0
  35. data/vendor/assets/javascripts/unforassets/select2/i18n/hr.js +3 -0
  36. data/vendor/assets/javascripts/unforassets/select2/i18n/hu.js +3 -0
  37. data/vendor/assets/javascripts/unforassets/select2/i18n/id.js +3 -0
  38. data/vendor/assets/javascripts/unforassets/select2/i18n/is.js +3 -0
  39. data/vendor/assets/javascripts/unforassets/select2/i18n/it.js +3 -0
  40. data/vendor/assets/javascripts/unforassets/select2/i18n/ja.js +3 -0
  41. data/vendor/assets/javascripts/unforassets/select2/i18n/km.js +3 -0
  42. data/vendor/assets/javascripts/unforassets/select2/i18n/ko.js +3 -0
  43. data/vendor/assets/javascripts/unforassets/select2/i18n/lt.js +3 -0
  44. data/vendor/assets/javascripts/unforassets/select2/i18n/lv.js +3 -0
  45. data/vendor/assets/javascripts/unforassets/select2/i18n/mk.js +3 -0
  46. data/vendor/assets/javascripts/unforassets/select2/i18n/ms.js +3 -0
  47. data/vendor/assets/javascripts/unforassets/select2/i18n/nb.js +3 -0
  48. data/vendor/assets/javascripts/unforassets/select2/i18n/nl.js +3 -0
  49. data/vendor/assets/javascripts/unforassets/select2/i18n/pl.js +3 -0
  50. data/vendor/assets/javascripts/unforassets/select2/i18n/pt-BR.js +3 -0
  51. data/vendor/assets/javascripts/unforassets/select2/i18n/pt.js +3 -0
  52. data/vendor/assets/javascripts/unforassets/select2/i18n/ro.js +3 -0
  53. data/vendor/assets/javascripts/unforassets/select2/i18n/ru.js +3 -0
  54. data/vendor/assets/javascripts/unforassets/select2/i18n/sk.js +3 -0
  55. data/vendor/assets/javascripts/unforassets/select2/i18n/sr-Cyrl.js +3 -0
  56. data/vendor/assets/javascripts/unforassets/select2/i18n/sr.js +3 -0
  57. data/vendor/assets/javascripts/unforassets/select2/i18n/sv.js +3 -0
  58. data/vendor/assets/javascripts/unforassets/select2/i18n/th.js +3 -0
  59. data/vendor/assets/javascripts/unforassets/select2/i18n/tr.js +3 -0
  60. data/vendor/assets/javascripts/unforassets/select2/i18n/uk.js +3 -0
  61. data/vendor/assets/javascripts/unforassets/select2/i18n/vi.js +3 -0
  62. data/vendor/assets/javascripts/unforassets/select2/i18n/zh-CN.js +3 -0
  63. data/vendor/assets/javascripts/unforassets/select2/i18n/zh-TW.js +3 -0
  64. data/vendor/assets/javascripts/unforassets/select2.js +3 -0
  65. data/vendor/assets/javascripts/unforassets/tooltipster.js +2 -0
  66. data/vendor/assets/javascripts/unforassets/typeahead.js +2451 -0
  67. data/vendor/assets/javascripts/unforassets/undescore.js +6 -0
  68. data/vendor/assets/javascripts/unforassets/uri.js +151 -0
  69. data/vendor/assets/javascripts/unforassets/vex.js +2 -0
  70. data/vendor/assets/stylesheets/unforassets/bootstrap-datetimepicker.css +5 -0
  71. data/vendor/assets/stylesheets/unforassets/bootstrap-multiselect.css +1 -0
  72. data/vendor/assets/stylesheets/unforassets/jcrop.css +6 -0
  73. data/vendor/assets/stylesheets/unforassets/pnotify.css +10 -0
  74. data/vendor/assets/stylesheets/unforassets/select2.css +1 -0
  75. data/vendor/assets/stylesheets/unforassets/typeahead.css +77 -0
  76. metadata +91 -24
  77. data/vendor/assets/javascripts/tooltipster.bundle.js +0 -4260
  78. data/vendor/assets/javascripts/vex.combined.js +0 -1621
  79. /data/vendor/assets/javascripts/{fancybox.js → unforassets/fancybox.js} +0 -0
  80. /data/vendor/assets/javascripts/{tooltipster-plugin-scrollable-tip.js → unforassets/tooltipster/plugins/scrollable-tip.js} +0 -0
  81. /data/vendor/assets/javascripts/{tooltipster-plugin-svg.js → unforassets/tooltipster/plugins/svg.js} +0 -0
  82. /data/vendor/assets/stylesheets/{fancybox.css → unforassets/fancybox.css} +0 -0
  83. /data/vendor/assets/stylesheets/{tooltipster-sidetip-borderless.min.css → unforassets/tooltipster/themes/tooltipster-sidetip-borderless.css} +0 -0
  84. /data/vendor/assets/stylesheets/{tooltipster-sidetip-light.min.css → unforassets/tooltipster/themes/tooltipster-sidetip-light.css} +0 -0
  85. /data/vendor/assets/stylesheets/{tooltipster-sidetip-noir.min.css → unforassets/tooltipster/themes/tooltipster-sidetip-noir.css} +0 -0
  86. /data/vendor/assets/stylesheets/{tooltipster-sidetip-punk.min.css → unforassets/tooltipster/themes/tooltipster-sidetip-punk.css} +0 -0
  87. /data/vendor/assets/stylesheets/{tooltipster-sidetip-shadow.min.css → unforassets/tooltipster/themes/tooltipster-sidetip-shadow.css} +0 -0
  88. /data/vendor/assets/stylesheets/{tooltipster.bundle.css → unforassets/tooltipster.css} +0 -0
  89. /data/vendor/assets/stylesheets/{vex-theme-bottom-right-corner.css → unforassets/vex/themes/vex-theme-bottom-right-corner.css} +0 -0
  90. /data/vendor/assets/stylesheets/{vex-theme-default.css → unforassets/vex/themes/vex-theme-default.css} +0 -0
  91. /data/vendor/assets/stylesheets/{vex-theme-flat-attack.css → unforassets/vex/themes/vex-theme-flat-attack.css} +0 -0
  92. /data/vendor/assets/stylesheets/{vex-theme-os.css → unforassets/vex/themes/vex-theme-os.css} +0 -0
  93. /data/vendor/assets/stylesheets/{vex-theme-plain.css → unforassets/vex/themes/vex-theme-plain.css} +0 -0
  94. /data/vendor/assets/stylesheets/{vex-theme-top.css → unforassets/vex/themes/vex-theme-top.css} +0 -0
  95. /data/vendor/assets/stylesheets/{vex-theme-wireframe.css → unforassets/vex/themes/vex-theme-wireframe.css} +0 -0
  96. /data/vendor/assets/stylesheets/{vex.css → unforassets/vex.css} +0 -0
@@ -1,4260 +0,0 @@
1
- /**
2
- * tooltipster http://iamceege.github.io/tooltipster/
3
- * A rockin' custom tooltip jQuery plugin
4
- * Developed by Caleb Jacob and Louis Ameline
5
- * MIT license
6
- */
7
- (function (root, factory) {
8
- if (typeof define === 'function' && define.amd) {
9
- // AMD. Register as an anonymous module unless amdModuleId is set
10
- define(["jquery"], function (a0) {
11
- return (factory(a0));
12
- });
13
- } else if (typeof exports === 'object') {
14
- // Node. Does not work with strict CommonJS, but
15
- // only CommonJS-like environments that support module.exports,
16
- // like Node.
17
- module.exports = factory(require("jquery"));
18
- } else {
19
- factory(jQuery);
20
- }
21
- }(this, function ($) {
22
-
23
- // This file will be UMDified by a build task.
24
-
25
- var defaults = {
26
- animation: 'fade',
27
- animationDuration: 350,
28
- content: null,
29
- contentAsHTML: false,
30
- contentCloning: false,
31
- debug: true,
32
- delay: 300,
33
- delayTouch: [300, 500],
34
- functionInit: null,
35
- functionBefore: null,
36
- functionReady: null,
37
- functionAfter: null,
38
- functionFormat: null,
39
- IEmin: 6,
40
- interactive: false,
41
- multiple: false,
42
- // will default to document.body, or must be an element positioned at (0, 0)
43
- // in the document, typically like the very top views of an app.
44
- parent: null,
45
- plugins: ['sideTip'],
46
- repositionOnScroll: false,
47
- restoration: 'none',
48
- selfDestruction: true,
49
- theme: [],
50
- timer: 0,
51
- trackerInterval: 500,
52
- trackOrigin: false,
53
- trackTooltip: false,
54
- trigger: 'hover',
55
- triggerClose: {
56
- click: false,
57
- mouseleave: false,
58
- originClick: false,
59
- scroll: false,
60
- tap: false,
61
- touchleave: false
62
- },
63
- triggerOpen: {
64
- click: false,
65
- mouseenter: false,
66
- tap: false,
67
- touchstart: false
68
- },
69
- updateAnimation: 'rotate',
70
- zIndex: 9999999
71
- },
72
- // we'll avoid using the 'window' global as a good practice but npm's
73
- // jquery@<2.1.0 package actually requires a 'window' global, so not sure
74
- // it's useful at all
75
- win = (typeof window != 'undefined') ? window : null,
76
- // env will be proxied by the core for plugins to have access its properties
77
- env = {
78
- // detect if this device can trigger touch events. Better have a false
79
- // positive (unused listeners, that's ok) than a false negative.
80
- // https://github.com/Modernizr/Modernizr/blob/master/feature-detects/touchevents.js
81
- // http://stackoverflow.com/questions/4817029/whats-the-best-way-to-detect-a-touch-screen-device-using-javascript
82
- hasTouchCapability: !!(
83
- win
84
- && ( 'ontouchstart' in win
85
- || (win.DocumentTouch && win.document instanceof win.DocumentTouch)
86
- || win.navigator.maxTouchPoints
87
- )
88
- ),
89
- hasTransitions: transitionSupport(),
90
- IE: false,
91
- // don't set manually, it will be updated by a build task after the manifest
92
- semVer: '4.2.3',
93
- window: win
94
- },
95
- core = function() {
96
-
97
- // core variables
98
-
99
- // the core emitters
100
- this.__$emitterPrivate = $({});
101
- this.__$emitterPublic = $({});
102
- this.__instancesLatestArr = [];
103
- // collects plugin constructors
104
- this.__plugins = {};
105
- // proxy env variables for plugins who might use them
106
- this._env = env;
107
- };
108
-
109
- // core methods
110
- core.prototype = {
111
-
112
- /**
113
- * A function to proxy the public methods of an object onto another
114
- *
115
- * @param {object} constructor The constructor to bridge
116
- * @param {object} obj The object that will get new methods (an instance or the core)
117
- * @param {string} pluginName A plugin name for the console log message
118
- * @return {core}
119
- * @private
120
- */
121
- __bridge: function(constructor, obj, pluginName) {
122
-
123
- // if it's not already bridged
124
- if (!obj[pluginName]) {
125
-
126
- var fn = function() {};
127
- fn.prototype = constructor;
128
-
129
- var pluginInstance = new fn();
130
-
131
- // the _init method has to exist in instance constructors but might be missing
132
- // in core constructors
133
- if (pluginInstance.__init) {
134
- pluginInstance.__init(obj);
135
- }
136
-
137
- $.each(constructor, function(methodName, fn) {
138
-
139
- // don't proxy "private" methods, only "protected" and public ones
140
- if (methodName.indexOf('__') != 0) {
141
-
142
- // if the method does not exist yet
143
- if (!obj[methodName]) {
144
-
145
- obj[methodName] = function() {
146
- return pluginInstance[methodName].apply(pluginInstance, Array.prototype.slice.apply(arguments));
147
- };
148
-
149
- // remember to which plugin this method corresponds (several plugins may
150
- // have methods of the same name, we need to be sure)
151
- obj[methodName].bridged = pluginInstance;
152
- }
153
- else if (defaults.debug) {
154
-
155
- console.log('The '+ methodName +' method of the '+ pluginName
156
- +' plugin conflicts with another plugin or native methods');
157
- }
158
- }
159
- });
160
-
161
- obj[pluginName] = pluginInstance;
162
- }
163
-
164
- return this;
165
- },
166
-
167
- /**
168
- * For mockup in Node env if need be, for testing purposes
169
- *
170
- * @return {core}
171
- * @private
172
- */
173
- __setWindow: function(window) {
174
- env.window = window;
175
- return this;
176
- },
177
-
178
- /**
179
- * Returns a ruler, a tool to help measure the size of a tooltip under
180
- * various settings. Meant for plugins
181
- *
182
- * @see Ruler
183
- * @return {object} A Ruler instance
184
- * @protected
185
- */
186
- _getRuler: function($tooltip) {
187
- return new Ruler($tooltip);
188
- },
189
-
190
- /**
191
- * For internal use by plugins, if needed
192
- *
193
- * @return {core}
194
- * @protected
195
- */
196
- _off: function() {
197
- this.__$emitterPrivate.off.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
198
- return this;
199
- },
200
-
201
- /**
202
- * For internal use by plugins, if needed
203
- *
204
- * @return {core}
205
- * @protected
206
- */
207
- _on: function() {
208
- this.__$emitterPrivate.on.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
209
- return this;
210
- },
211
-
212
- /**
213
- * For internal use by plugins, if needed
214
- *
215
- * @return {core}
216
- * @protected
217
- */
218
- _one: function() {
219
- this.__$emitterPrivate.one.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
220
- return this;
221
- },
222
-
223
- /**
224
- * Returns (getter) or adds (setter) a plugin
225
- *
226
- * @param {string|object} plugin Provide a string (in the full form
227
- * "namespace.name") to use as as getter, an object to use as a setter
228
- * @return {object|core}
229
- * @protected
230
- */
231
- _plugin: function(plugin) {
232
-
233
- var self = this;
234
-
235
- // getter
236
- if (typeof plugin == 'string') {
237
-
238
- var pluginName = plugin,
239
- p = null;
240
-
241
- // if the namespace is provided, it's easy to search
242
- if (pluginName.indexOf('.') > 0) {
243
- p = self.__plugins[pluginName];
244
- }
245
- // otherwise, return the first name that matches
246
- else {
247
- $.each(self.__plugins, function(i, plugin) {
248
-
249
- if (plugin.name.substring(plugin.name.length - pluginName.length - 1) == '.'+ pluginName) {
250
- p = plugin;
251
- return false;
252
- }
253
- });
254
- }
255
-
256
- return p;
257
- }
258
- // setter
259
- else {
260
-
261
- // force namespaces
262
- if (plugin.name.indexOf('.') < 0) {
263
- throw new Error('Plugins must be namespaced');
264
- }
265
-
266
- self.__plugins[plugin.name] = plugin;
267
-
268
- // if the plugin has core features
269
- if (plugin.core) {
270
-
271
- // bridge non-private methods onto the core to allow new core methods
272
- self.__bridge(plugin.core, self, plugin.name);
273
- }
274
-
275
- return this;
276
- }
277
- },
278
-
279
- /**
280
- * Trigger events on the core emitters
281
- *
282
- * @returns {core}
283
- * @protected
284
- */
285
- _trigger: function() {
286
-
287
- var args = Array.prototype.slice.apply(arguments);
288
-
289
- if (typeof args[0] == 'string') {
290
- args[0] = { type: args[0] };
291
- }
292
-
293
- // note: the order of emitters matters
294
- this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate, args);
295
- this.__$emitterPublic.trigger.apply(this.__$emitterPublic, args);
296
-
297
- return this;
298
- },
299
-
300
- /**
301
- * Returns instances of all tooltips in the page or an a given element
302
- *
303
- * @param {string|HTML object collection} selector optional Use this
304
- * parameter to restrict the set of objects that will be inspected
305
- * for the retrieval of instances. By default, all instances in the
306
- * page are returned.
307
- * @return {array} An array of instance objects
308
- * @public
309
- */
310
- instances: function(selector) {
311
-
312
- var instances = [],
313
- sel = selector || '.tooltipstered';
314
-
315
- $(sel).each(function() {
316
-
317
- var $this = $(this),
318
- ns = $this.data('tooltipster-ns');
319
-
320
- if (ns) {
321
-
322
- $.each(ns, function(i, namespace) {
323
- instances.push($this.data(namespace));
324
- });
325
- }
326
- });
327
-
328
- return instances;
329
- },
330
-
331
- /**
332
- * Returns the Tooltipster objects generated by the last initializing call
333
- *
334
- * @return {array} An array of instance objects
335
- * @public
336
- */
337
- instancesLatest: function() {
338
- return this.__instancesLatestArr;
339
- },
340
-
341
- /**
342
- * For public use only, not to be used by plugins (use ::_off() instead)
343
- *
344
- * @return {core}
345
- * @public
346
- */
347
- off: function() {
348
- this.__$emitterPublic.off.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
349
- return this;
350
- },
351
-
352
- /**
353
- * For public use only, not to be used by plugins (use ::_on() instead)
354
- *
355
- * @return {core}
356
- * @public
357
- */
358
- on: function() {
359
- this.__$emitterPublic.on.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
360
- return this;
361
- },
362
-
363
- /**
364
- * For public use only, not to be used by plugins (use ::_one() instead)
365
- *
366
- * @return {core}
367
- * @public
368
- */
369
- one: function() {
370
- this.__$emitterPublic.one.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
371
- return this;
372
- },
373
-
374
- /**
375
- * Returns all HTML elements which have one or more tooltips
376
- *
377
- * @param {string} selector optional Use this to restrict the results
378
- * to the descendants of an element
379
- * @return {array} An array of HTML elements
380
- * @public
381
- */
382
- origins: function(selector) {
383
-
384
- var sel = selector ?
385
- selector +' ' :
386
- '';
387
-
388
- return $(sel +'.tooltipstered').toArray();
389
- },
390
-
391
- /**
392
- * Change default options for all future instances
393
- *
394
- * @param {object} d The options that should be made defaults
395
- * @return {core}
396
- * @public
397
- */
398
- setDefaults: function(d) {
399
- $.extend(defaults, d);
400
- return this;
401
- },
402
-
403
- /**
404
- * For users to trigger their handlers on the public emitter
405
- *
406
- * @returns {core}
407
- * @public
408
- */
409
- triggerHandler: function() {
410
- this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
411
- return this;
412
- }
413
- };
414
-
415
- // $.tooltipster will be used to call core methods
416
- $.tooltipster = new core();
417
-
418
- // the Tooltipster instance class (mind the capital T)
419
- $.Tooltipster = function(element, options) {
420
-
421
- // list of instance variables
422
-
423
- // stack of custom callbacks provided as parameters to API methods
424
- this.__callbacks = {
425
- close: [],
426
- open: []
427
- };
428
- // the schedule time of DOM removal
429
- this.__closingTime;
430
- // this will be the user content shown in the tooltip. A capital "C" is used
431
- // because there is also a method called content()
432
- this.__Content;
433
- // for the size tracker
434
- this.__contentBcr;
435
- // to disable the tooltip after destruction
436
- this.__destroyed = false;
437
- // we can't emit directly on the instance because if a method with the same
438
- // name as the event exists, it will be called by jQuery. Se we use a plain
439
- // object as emitter. This emitter is for internal use by plugins,
440
- // if needed.
441
- this.__$emitterPrivate = $({});
442
- // this emitter is for the user to listen to events without risking to mess
443
- // with our internal listeners
444
- this.__$emitterPublic = $({});
445
- this.__enabled = true;
446
- // the reference to the gc interval
447
- this.__garbageCollector;
448
- // various position and size data recomputed before each repositioning
449
- this.__Geometry;
450
- // the tooltip position, saved after each repositioning by a plugin
451
- this.__lastPosition;
452
- // a unique namespace per instance
453
- this.__namespace = 'tooltipster-'+ Math.round(Math.random()*1000000);
454
- this.__options;
455
- // will be used to support origins in scrollable areas
456
- this.__$originParents;
457
- this.__pointerIsOverOrigin = false;
458
- // to remove themes if needed
459
- this.__previousThemes = [];
460
- // the state can be either: appearing, stable, disappearing, closed
461
- this.__state = 'closed';
462
- // timeout references
463
- this.__timeouts = {
464
- close: [],
465
- open: null
466
- };
467
- // store touch events to be able to detect emulated mouse events
468
- this.__touchEvents = [];
469
- // the reference to the tracker interval
470
- this.__tracker = null;
471
- // the element to which this tooltip is associated
472
- this._$origin;
473
- // this will be the tooltip element (jQuery wrapped HTML element).
474
- // It's the job of a plugin to create it and append it to the DOM
475
- this._$tooltip;
476
-
477
- // launch
478
- this.__init(element, options);
479
- };
480
-
481
- $.Tooltipster.prototype = {
482
-
483
- /**
484
- * @param origin
485
- * @param options
486
- * @private
487
- */
488
- __init: function(origin, options) {
489
-
490
- var self = this;
491
-
492
- self._$origin = $(origin);
493
- self.__options = $.extend(true, {}, defaults, options);
494
-
495
- // some options may need to be reformatted
496
- self.__optionsFormat();
497
-
498
- // don't run on old IE if asked no to
499
- if ( !env.IE
500
- || env.IE >= self.__options.IEmin
501
- ) {
502
-
503
- // note: the content is null (empty) by default and can stay that
504
- // way if the plugin remains initialized but not fed any content. The
505
- // tooltip will just not appear.
506
-
507
- // let's save the initial value of the title attribute for later
508
- // restoration if need be.
509
- var initialTitle = null;
510
-
511
- // it will already have been saved in case of multiple tooltips
512
- if (self._$origin.data('tooltipster-initialTitle') === undefined) {
513
-
514
- initialTitle = self._$origin.attr('title');
515
-
516
- // we do not want initialTitle to be "undefined" because
517
- // of how jQuery's .data() method works
518
- if (initialTitle === undefined) initialTitle = null;
519
-
520
- self._$origin.data('tooltipster-initialTitle', initialTitle);
521
- }
522
-
523
- // If content is provided in the options, it has precedence over the
524
- // title attribute.
525
- // Note: an empty string is considered content, only 'null' represents
526
- // the absence of content.
527
- // Also, an existing title="" attribute will result in an empty string
528
- // content
529
- if (self.__options.content !== null) {
530
- self.__contentSet(self.__options.content);
531
- }
532
- else {
533
-
534
- var selector = self._$origin.attr('data-tooltip-content'),
535
- $el;
536
-
537
- if (selector){
538
- $el = $(selector);
539
- }
540
-
541
- if ($el && $el[0]) {
542
- self.__contentSet($el.first());
543
- }
544
- else {
545
- self.__contentSet(initialTitle);
546
- }
547
- }
548
-
549
- self._$origin
550
- // strip the title off of the element to prevent the default tooltips
551
- // from popping up
552
- .removeAttr('title')
553
- // to be able to find all instances on the page later (upon window
554
- // events in particular)
555
- .addClass('tooltipstered');
556
-
557
- // set listeners on the origin
558
- self.__prepareOrigin();
559
-
560
- // set the garbage collector
561
- self.__prepareGC();
562
-
563
- // init plugins
564
- $.each(self.__options.plugins, function(i, pluginName) {
565
- self._plug(pluginName);
566
- });
567
-
568
- // to detect swiping
569
- if (env.hasTouchCapability) {
570
- $(env.window.document.body).on('touchmove.'+ self.__namespace +'-triggerOpen', function(event) {
571
- self._touchRecordEvent(event);
572
- });
573
- }
574
-
575
- self
576
- // prepare the tooltip when it gets created. This event must
577
- // be fired by a plugin
578
- ._on('created', function() {
579
- self.__prepareTooltip();
580
- })
581
- // save position information when it's sent by a plugin
582
- ._on('repositioned', function(e) {
583
- self.__lastPosition = e.position;
584
- });
585
- }
586
- else {
587
- self.__options.disabled = true;
588
- }
589
- },
590
-
591
- /**
592
- * Insert the content into the appropriate HTML element of the tooltip
593
- *
594
- * @returns {self}
595
- * @private
596
- */
597
- __contentInsert: function() {
598
-
599
- var self = this,
600
- $el = self._$tooltip.find('.tooltipster-content'),
601
- formattedContent = self.__Content,
602
- format = function(content) {
603
- formattedContent = content;
604
- };
605
-
606
- self._trigger({
607
- type: 'format',
608
- content: self.__Content,
609
- format: format
610
- });
611
-
612
- if (self.__options.functionFormat) {
613
-
614
- formattedContent = self.__options.functionFormat.call(
615
- self,
616
- self,
617
- { origin: self._$origin[0] },
618
- self.__Content
619
- );
620
- }
621
-
622
- if (typeof formattedContent === 'string' && !self.__options.contentAsHTML) {
623
- $el.text(formattedContent);
624
- }
625
- else {
626
- $el
627
- .empty()
628
- .append(formattedContent);
629
- }
630
-
631
- return self;
632
- },
633
-
634
- /**
635
- * Save the content, cloning it beforehand if need be
636
- *
637
- * @param content
638
- * @returns {self}
639
- * @private
640
- */
641
- __contentSet: function(content) {
642
-
643
- // clone if asked. Cloning the object makes sure that each instance has its
644
- // own version of the content (in case a same object were provided for several
645
- // instances)
646
- // reminder: typeof null === object
647
- if (content instanceof $ && this.__options.contentCloning) {
648
- content = content.clone(true);
649
- }
650
-
651
- this.__Content = content;
652
-
653
- this._trigger({
654
- type: 'updated',
655
- content: content
656
- });
657
-
658
- return this;
659
- },
660
-
661
- /**
662
- * Error message about a method call made after destruction
663
- *
664
- * @private
665
- */
666
- __destroyError: function() {
667
- throw new Error('This tooltip has been destroyed and cannot execute your method call.');
668
- },
669
-
670
- /**
671
- * Gather all information about dimensions and available space,
672
- * called before every repositioning
673
- *
674
- * @private
675
- * @returns {object}
676
- */
677
- __geometry: function() {
678
-
679
- var self = this,
680
- $target = self._$origin,
681
- originIsArea = self._$origin.is('area');
682
-
683
- // if this._$origin is a map area, the target we'll need
684
- // the dimensions of is actually the image using the map,
685
- // not the area itself
686
- if (originIsArea) {
687
-
688
- var mapName = self._$origin.parent().attr('name');
689
-
690
- $target = $('img[usemap="#'+ mapName +'"]');
691
- }
692
-
693
- var bcr = $target[0].getBoundingClientRect(),
694
- $document = $(env.window.document),
695
- $window = $(env.window),
696
- $parent = $target,
697
- // some useful properties of important elements
698
- geo = {
699
- // available space for the tooltip, see down below
700
- available: {
701
- document: null,
702
- window: null
703
- },
704
- document: {
705
- size: {
706
- height: $document.height(),
707
- width: $document.width()
708
- }
709
- },
710
- window: {
711
- scroll: {
712
- // the second ones are for IE compatibility
713
- left: env.window.scrollX || env.window.document.documentElement.scrollLeft,
714
- top: env.window.scrollY || env.window.document.documentElement.scrollTop
715
- },
716
- size: {
717
- height: $window.height(),
718
- width: $window.width()
719
- }
720
- },
721
- origin: {
722
- // the origin has a fixed lineage if itself or one of its
723
- // ancestors has a fixed position
724
- fixedLineage: false,
725
- // relative to the document
726
- offset: {},
727
- size: {
728
- height: bcr.bottom - bcr.top,
729
- width: bcr.right - bcr.left
730
- },
731
- usemapImage: originIsArea ? $target[0] : null,
732
- // relative to the window
733
- windowOffset: {
734
- bottom: bcr.bottom,
735
- left: bcr.left,
736
- right: bcr.right,
737
- top: bcr.top
738
- }
739
- }
740
- },
741
- geoFixed = false;
742
-
743
- // if the element is a map area, some properties may need
744
- // to be recalculated
745
- if (originIsArea) {
746
-
747
- var shape = self._$origin.attr('shape'),
748
- coords = self._$origin.attr('coords');
749
-
750
- if (coords) {
751
-
752
- coords = coords.split(',');
753
-
754
- $.map(coords, function(val, i) {
755
- coords[i] = parseInt(val);
756
- });
757
- }
758
-
759
- // if the image itself is the area, nothing more to do
760
- if (shape != 'default') {
761
-
762
- switch(shape) {
763
-
764
- case 'circle':
765
-
766
- var circleCenterLeft = coords[0],
767
- circleCenterTop = coords[1],
768
- circleRadius = coords[2],
769
- areaTopOffset = circleCenterTop - circleRadius,
770
- areaLeftOffset = circleCenterLeft - circleRadius;
771
-
772
- geo.origin.size.height = circleRadius * 2;
773
- geo.origin.size.width = geo.origin.size.height;
774
-
775
- geo.origin.windowOffset.left += areaLeftOffset;
776
- geo.origin.windowOffset.top += areaTopOffset;
777
-
778
- break;
779
-
780
- case 'rect':
781
-
782
- var areaLeft = coords[0],
783
- areaTop = coords[1],
784
- areaRight = coords[2],
785
- areaBottom = coords[3];
786
-
787
- geo.origin.size.height = areaBottom - areaTop;
788
- geo.origin.size.width = areaRight - areaLeft;
789
-
790
- geo.origin.windowOffset.left += areaLeft;
791
- geo.origin.windowOffset.top += areaTop;
792
-
793
- break;
794
-
795
- case 'poly':
796
-
797
- var areaSmallestX = 0,
798
- areaSmallestY = 0,
799
- areaGreatestX = 0,
800
- areaGreatestY = 0,
801
- arrayAlternate = 'even';
802
-
803
- for (var i = 0; i < coords.length; i++) {
804
-
805
- var areaNumber = coords[i];
806
-
807
- if (arrayAlternate == 'even') {
808
-
809
- if (areaNumber > areaGreatestX) {
810
-
811
- areaGreatestX = areaNumber;
812
-
813
- if (i === 0) {
814
- areaSmallestX = areaGreatestX;
815
- }
816
- }
817
-
818
- if (areaNumber < areaSmallestX) {
819
- areaSmallestX = areaNumber;
820
- }
821
-
822
- arrayAlternate = 'odd';
823
- }
824
- else {
825
- if (areaNumber > areaGreatestY) {
826
-
827
- areaGreatestY = areaNumber;
828
-
829
- if (i == 1) {
830
- areaSmallestY = areaGreatestY;
831
- }
832
- }
833
-
834
- if (areaNumber < areaSmallestY) {
835
- areaSmallestY = areaNumber;
836
- }
837
-
838
- arrayAlternate = 'even';
839
- }
840
- }
841
-
842
- geo.origin.size.height = areaGreatestY - areaSmallestY;
843
- geo.origin.size.width = areaGreatestX - areaSmallestX;
844
-
845
- geo.origin.windowOffset.left += areaSmallestX;
846
- geo.origin.windowOffset.top += areaSmallestY;
847
-
848
- break;
849
- }
850
- }
851
- }
852
-
853
- // user callback through an event
854
- var edit = function(r) {
855
- geo.origin.size.height = r.height,
856
- geo.origin.windowOffset.left = r.left,
857
- geo.origin.windowOffset.top = r.top,
858
- geo.origin.size.width = r.width
859
- };
860
-
861
- self._trigger({
862
- type: 'geometry',
863
- edit: edit,
864
- geometry: {
865
- height: geo.origin.size.height,
866
- left: geo.origin.windowOffset.left,
867
- top: geo.origin.windowOffset.top,
868
- width: geo.origin.size.width
869
- }
870
- });
871
-
872
- // calculate the remaining properties with what we got
873
-
874
- geo.origin.windowOffset.right = geo.origin.windowOffset.left + geo.origin.size.width;
875
- geo.origin.windowOffset.bottom = geo.origin.windowOffset.top + geo.origin.size.height;
876
-
877
- geo.origin.offset.left = geo.origin.windowOffset.left + geo.window.scroll.left;
878
- geo.origin.offset.top = geo.origin.windowOffset.top + geo.window.scroll.top;
879
- geo.origin.offset.bottom = geo.origin.offset.top + geo.origin.size.height;
880
- geo.origin.offset.right = geo.origin.offset.left + geo.origin.size.width;
881
-
882
- // the space that is available to display the tooltip relatively to the document
883
- geo.available.document = {
884
- bottom: {
885
- height: geo.document.size.height - geo.origin.offset.bottom,
886
- width: geo.document.size.width
887
- },
888
- left: {
889
- height: geo.document.size.height,
890
- width: geo.origin.offset.left
891
- },
892
- right: {
893
- height: geo.document.size.height,
894
- width: geo.document.size.width - geo.origin.offset.right
895
- },
896
- top: {
897
- height: geo.origin.offset.top,
898
- width: geo.document.size.width
899
- }
900
- };
901
-
902
- // the space that is available to display the tooltip relatively to the viewport
903
- // (the resulting values may be negative if the origin overflows the viewport)
904
- geo.available.window = {
905
- bottom: {
906
- // the inner max is here to make sure the available height is no bigger
907
- // than the viewport height (when the origin is off screen at the top).
908
- // The outer max just makes sure that the height is not negative (when
909
- // the origin overflows at the bottom).
910
- height: Math.max(geo.window.size.height - Math.max(geo.origin.windowOffset.bottom, 0), 0),
911
- width: geo.window.size.width
912
- },
913
- left: {
914
- height: geo.window.size.height,
915
- width: Math.max(geo.origin.windowOffset.left, 0)
916
- },
917
- right: {
918
- height: geo.window.size.height,
919
- width: Math.max(geo.window.size.width - Math.max(geo.origin.windowOffset.right, 0), 0)
920
- },
921
- top: {
922
- height: Math.max(geo.origin.windowOffset.top, 0),
923
- width: geo.window.size.width
924
- }
925
- };
926
-
927
- while ($parent[0].tagName.toLowerCase() != 'html') {
928
-
929
- if ($parent.css('position') == 'fixed') {
930
- geo.origin.fixedLineage = true;
931
- break;
932
- }
933
-
934
- $parent = $parent.parent();
935
- }
936
-
937
- return geo;
938
- },
939
-
940
- /**
941
- * Some options may need to be formated before being used
942
- *
943
- * @returns {self}
944
- * @private
945
- */
946
- __optionsFormat: function() {
947
-
948
- if (typeof this.__options.animationDuration == 'number') {
949
- this.__options.animationDuration = [this.__options.animationDuration, this.__options.animationDuration];
950
- }
951
-
952
- if (typeof this.__options.delay == 'number') {
953
- this.__options.delay = [this.__options.delay, this.__options.delay];
954
- }
955
-
956
- if (typeof this.__options.delayTouch == 'number') {
957
- this.__options.delayTouch = [this.__options.delayTouch, this.__options.delayTouch];
958
- }
959
-
960
- if (typeof this.__options.theme == 'string') {
961
- this.__options.theme = [this.__options.theme];
962
- }
963
-
964
- // determine the future parent
965
- if (this.__options.parent === null) {
966
- this.__options.parent = $(env.window.document.body);
967
- }
968
- else if (typeof this.__options.parent == 'string') {
969
- this.__options.parent = $(this.__options.parent);
970
- }
971
-
972
- if (this.__options.trigger == 'hover') {
973
-
974
- this.__options.triggerOpen = {
975
- mouseenter: true,
976
- touchstart: true
977
- };
978
-
979
- this.__options.triggerClose = {
980
- mouseleave: true,
981
- originClick: true,
982
- touchleave: true
983
- };
984
- }
985
- else if (this.__options.trigger == 'click') {
986
-
987
- this.__options.triggerOpen = {
988
- click: true,
989
- tap: true
990
- };
991
-
992
- this.__options.triggerClose = {
993
- click: true,
994
- tap: true
995
- };
996
- }
997
-
998
- // for the plugins
999
- this._trigger('options');
1000
-
1001
- return this;
1002
- },
1003
-
1004
- /**
1005
- * Schedules or cancels the garbage collector task
1006
- *
1007
- * @returns {self}
1008
- * @private
1009
- */
1010
- __prepareGC: function() {
1011
-
1012
- var self = this;
1013
-
1014
- // in case the selfDestruction option has been changed by a method call
1015
- if (self.__options.selfDestruction) {
1016
-
1017
- // the GC task
1018
- self.__garbageCollector = setInterval(function() {
1019
-
1020
- var now = new Date().getTime();
1021
-
1022
- // forget the old events
1023
- self.__touchEvents = $.grep(self.__touchEvents, function(event, i) {
1024
- // 1 minute
1025
- return now - event.time > 60000;
1026
- });
1027
-
1028
- // auto-destruct if the origin is gone
1029
- if (!bodyContains(self._$origin)) {
1030
-
1031
- self.close(function(){
1032
- self.destroy();
1033
- });
1034
- }
1035
- }, 20000);
1036
- }
1037
- else {
1038
- clearInterval(self.__garbageCollector);
1039
- }
1040
-
1041
- return self;
1042
- },
1043
-
1044
- /**
1045
- * Sets listeners on the origin if the open triggers require them.
1046
- * Unlike the listeners set at opening time, these ones
1047
- * remain even when the tooltip is closed. It has been made a
1048
- * separate method so it can be called when the triggers are
1049
- * changed in the options. Closing is handled in _open()
1050
- * because of the bindings that may be needed on the tooltip
1051
- * itself
1052
- *
1053
- * @returns {self}
1054
- * @private
1055
- */
1056
- __prepareOrigin: function() {
1057
-
1058
- var self = this;
1059
-
1060
- // in case we're resetting the triggers
1061
- self._$origin.off('.'+ self.__namespace +'-triggerOpen');
1062
-
1063
- // if the device is touch capable, even if only mouse triggers
1064
- // are asked, we need to listen to touch events to know if the mouse
1065
- // events are actually emulated (so we can ignore them)
1066
- if (env.hasTouchCapability) {
1067
-
1068
- self._$origin.on(
1069
- 'touchstart.'+ self.__namespace +'-triggerOpen ' +
1070
- 'touchend.'+ self.__namespace +'-triggerOpen ' +
1071
- 'touchcancel.'+ self.__namespace +'-triggerOpen',
1072
- function(event){
1073
- self._touchRecordEvent(event);
1074
- }
1075
- );
1076
- }
1077
-
1078
- // mouse click and touch tap work the same way
1079
- if ( self.__options.triggerOpen.click
1080
- || (self.__options.triggerOpen.tap && env.hasTouchCapability)
1081
- ) {
1082
-
1083
- var eventNames = '';
1084
- if (self.__options.triggerOpen.click) {
1085
- eventNames += 'click.'+ self.__namespace +'-triggerOpen ';
1086
- }
1087
- if (self.__options.triggerOpen.tap && env.hasTouchCapability) {
1088
- eventNames += 'touchend.'+ self.__namespace +'-triggerOpen';
1089
- }
1090
-
1091
- self._$origin.on(eventNames, function(event) {
1092
- if (self._touchIsMeaningfulEvent(event)) {
1093
- self._open(event);
1094
- }
1095
- });
1096
- }
1097
-
1098
- // mouseenter and touch start work the same way
1099
- if ( self.__options.triggerOpen.mouseenter
1100
- || (self.__options.triggerOpen.touchstart && env.hasTouchCapability)
1101
- ) {
1102
-
1103
- var eventNames = '';
1104
- if (self.__options.triggerOpen.mouseenter) {
1105
- eventNames += 'mouseenter.'+ self.__namespace +'-triggerOpen ';
1106
- }
1107
- if (self.__options.triggerOpen.touchstart && env.hasTouchCapability) {
1108
- eventNames += 'touchstart.'+ self.__namespace +'-triggerOpen';
1109
- }
1110
-
1111
- self._$origin.on(eventNames, function(event) {
1112
- if ( self._touchIsTouchEvent(event)
1113
- || !self._touchIsEmulatedEvent(event)
1114
- ) {
1115
- self.__pointerIsOverOrigin = true;
1116
- self._openShortly(event);
1117
- }
1118
- });
1119
- }
1120
-
1121
- // info for the mouseleave/touchleave close triggers when they use a delay
1122
- if ( self.__options.triggerClose.mouseleave
1123
- || (self.__options.triggerClose.touchleave && env.hasTouchCapability)
1124
- ) {
1125
-
1126
- var eventNames = '';
1127
- if (self.__options.triggerClose.mouseleave) {
1128
- eventNames += 'mouseleave.'+ self.__namespace +'-triggerOpen ';
1129
- }
1130
- if (self.__options.triggerClose.touchleave && env.hasTouchCapability) {
1131
- eventNames += 'touchend.'+ self.__namespace +'-triggerOpen touchcancel.'+ self.__namespace +'-triggerOpen';
1132
- }
1133
-
1134
- self._$origin.on(eventNames, function(event) {
1135
-
1136
- if (self._touchIsMeaningfulEvent(event)) {
1137
- self.__pointerIsOverOrigin = false;
1138
- }
1139
- });
1140
- }
1141
-
1142
- return self;
1143
- },
1144
-
1145
- /**
1146
- * Do the things that need to be done only once after the tooltip
1147
- * HTML element it has been created. It has been made a separate
1148
- * method so it can be called when options are changed. Remember
1149
- * that the tooltip may actually exist in the DOM before it is
1150
- * opened, and present after it has been closed: it's the display
1151
- * plugin that takes care of handling it.
1152
- *
1153
- * @returns {self}
1154
- * @private
1155
- */
1156
- __prepareTooltip: function() {
1157
-
1158
- var self = this,
1159
- p = self.__options.interactive ? 'auto' : '';
1160
-
1161
- // this will be useful to know quickly if the tooltip is in
1162
- // the DOM or not
1163
- self._$tooltip
1164
- .attr('id', self.__namespace)
1165
- .css({
1166
- // pointer events
1167
- 'pointer-events': p,
1168
- zIndex: self.__options.zIndex
1169
- });
1170
-
1171
- // themes
1172
- // remove the old ones and add the new ones
1173
- $.each(self.__previousThemes, function(i, theme) {
1174
- self._$tooltip.removeClass(theme);
1175
- });
1176
- $.each(self.__options.theme, function(i, theme) {
1177
- self._$tooltip.addClass(theme);
1178
- });
1179
-
1180
- self.__previousThemes = $.merge([], self.__options.theme);
1181
-
1182
- return self;
1183
- },
1184
-
1185
- /**
1186
- * Handles the scroll on any of the parents of the origin (when the
1187
- * tooltip is open)
1188
- *
1189
- * @param {object} event
1190
- * @returns {self}
1191
- * @private
1192
- */
1193
- __scrollHandler: function(event) {
1194
-
1195
- var self = this;
1196
-
1197
- if (self.__options.triggerClose.scroll) {
1198
- self._close(event);
1199
- }
1200
- else {
1201
-
1202
- // if the origin or tooltip have been removed: do nothing, the tracker will
1203
- // take care of it later
1204
- if (bodyContains(self._$origin) && bodyContains(self._$tooltip)) {
1205
-
1206
- // if the scroll happened on the window
1207
- if (event.target === env.window.document) {
1208
-
1209
- // if the origin has a fixed lineage, window scroll will have no
1210
- // effect on its position nor on the position of the tooltip
1211
- if (!self.__Geometry.origin.fixedLineage) {
1212
-
1213
- // we don't need to do anything unless repositionOnScroll is true
1214
- // because the tooltip will already have moved with the window
1215
- // (and of course with the origin)
1216
- if (self.__options.repositionOnScroll) {
1217
- self.reposition(event);
1218
- }
1219
- }
1220
- }
1221
- // if the scroll happened on another parent of the tooltip, it means
1222
- // that it's in a scrollable area and now needs to have its position
1223
- // adjusted or recomputed, depending ont the repositionOnScroll
1224
- // option. Also, if the origin is partly hidden due to a parent that
1225
- // hides its overflow, we'll just hide (not close) the tooltip.
1226
- else {
1227
-
1228
- var g = self.__geometry(),
1229
- overflows = false;
1230
-
1231
- // a fixed position origin is not affected by the overflow hiding
1232
- // of a parent
1233
- if (self._$origin.css('position') != 'fixed') {
1234
-
1235
- self.__$originParents.each(function(i, el) {
1236
-
1237
- var $el = $(el),
1238
- overflowX = $el.css('overflow-x'),
1239
- overflowY = $el.css('overflow-y');
1240
-
1241
- if (overflowX != 'visible' || overflowY != 'visible') {
1242
-
1243
- var bcr = el.getBoundingClientRect();
1244
-
1245
- if (overflowX != 'visible') {
1246
-
1247
- if ( g.origin.windowOffset.left < bcr.left
1248
- || g.origin.windowOffset.right > bcr.right
1249
- ) {
1250
- overflows = true;
1251
- return false;
1252
- }
1253
- }
1254
-
1255
- if (overflowY != 'visible') {
1256
-
1257
- if ( g.origin.windowOffset.top < bcr.top
1258
- || g.origin.windowOffset.bottom > bcr.bottom
1259
- ) {
1260
- overflows = true;
1261
- return false;
1262
- }
1263
- }
1264
- }
1265
-
1266
- // no need to go further if fixed, for the same reason as above
1267
- if ($el.css('position') == 'fixed') {
1268
- return false;
1269
- }
1270
- });
1271
- }
1272
-
1273
- if (overflows) {
1274
- self._$tooltip.css('visibility', 'hidden');
1275
- }
1276
- else {
1277
- self._$tooltip.css('visibility', 'visible');
1278
-
1279
- // reposition
1280
- if (self.__options.repositionOnScroll) {
1281
- self.reposition(event);
1282
- }
1283
- // or just adjust offset
1284
- else {
1285
-
1286
- // we have to use offset and not windowOffset because this way,
1287
- // only the scroll distance of the scrollable areas are taken into
1288
- // account (the scrolltop value of the main window must be
1289
- // ignored since the tooltip already moves with it)
1290
- var offsetLeft = g.origin.offset.left - self.__Geometry.origin.offset.left,
1291
- offsetTop = g.origin.offset.top - self.__Geometry.origin.offset.top;
1292
-
1293
- // add the offset to the position initially computed by the display plugin
1294
- self._$tooltip.css({
1295
- left: self.__lastPosition.coord.left + offsetLeft,
1296
- top: self.__lastPosition.coord.top + offsetTop
1297
- });
1298
- }
1299
- }
1300
- }
1301
-
1302
- self._trigger({
1303
- type: 'scroll',
1304
- event: event
1305
- });
1306
- }
1307
- }
1308
-
1309
- return self;
1310
- },
1311
-
1312
- /**
1313
- * Changes the state of the tooltip
1314
- *
1315
- * @param {string} state
1316
- * @returns {self}
1317
- * @private
1318
- */
1319
- __stateSet: function(state) {
1320
-
1321
- this.__state = state;
1322
-
1323
- this._trigger({
1324
- type: 'state',
1325
- state: state
1326
- });
1327
-
1328
- return this;
1329
- },
1330
-
1331
- /**
1332
- * Clear appearance timeouts
1333
- *
1334
- * @returns {self}
1335
- * @private
1336
- */
1337
- __timeoutsClear: function() {
1338
-
1339
- // there is only one possible open timeout: the delayed opening
1340
- // when the mouseenter/touchstart open triggers are used
1341
- clearTimeout(this.__timeouts.open);
1342
- this.__timeouts.open = null;
1343
-
1344
- // ... but several close timeouts: the delayed closing when the
1345
- // mouseleave close trigger is used and the timer option
1346
- $.each(this.__timeouts.close, function(i, timeout) {
1347
- clearTimeout(timeout);
1348
- });
1349
- this.__timeouts.close = [];
1350
-
1351
- return this;
1352
- },
1353
-
1354
- /**
1355
- * Start the tracker that will make checks at regular intervals
1356
- *
1357
- * @returns {self}
1358
- * @private
1359
- */
1360
- __trackerStart: function() {
1361
-
1362
- var self = this,
1363
- $content = self._$tooltip.find('.tooltipster-content');
1364
-
1365
- // get the initial content size
1366
- if (self.__options.trackTooltip) {
1367
- self.__contentBcr = $content[0].getBoundingClientRect();
1368
- }
1369
-
1370
- self.__tracker = setInterval(function() {
1371
-
1372
- // if the origin or tooltip elements have been removed.
1373
- // Note: we could destroy the instance now if the origin has
1374
- // been removed but we'll leave that task to our garbage collector
1375
- if (!bodyContains(self._$origin) || !bodyContains(self._$tooltip)) {
1376
- self._close();
1377
- }
1378
- // if everything is alright
1379
- else {
1380
-
1381
- // compare the former and current positions of the origin to reposition
1382
- // the tooltip if need be
1383
- if (self.__options.trackOrigin) {
1384
-
1385
- var g = self.__geometry(),
1386
- identical = false;
1387
-
1388
- // compare size first (a change requires repositioning too)
1389
- if (areEqual(g.origin.size, self.__Geometry.origin.size)) {
1390
-
1391
- // for elements that have a fixed lineage (see __geometry()), we track the
1392
- // top and left properties (relative to window)
1393
- if (self.__Geometry.origin.fixedLineage) {
1394
- if (areEqual(g.origin.windowOffset, self.__Geometry.origin.windowOffset)) {
1395
- identical = true;
1396
- }
1397
- }
1398
- // otherwise, track total offset (relative to document)
1399
- else {
1400
- if (areEqual(g.origin.offset, self.__Geometry.origin.offset)) {
1401
- identical = true;
1402
- }
1403
- }
1404
- }
1405
-
1406
- if (!identical) {
1407
-
1408
- // close the tooltip when using the mouseleave close trigger
1409
- // (see https://github.com/iamceege/tooltipster/pull/253)
1410
- if (self.__options.triggerClose.mouseleave) {
1411
- self._close();
1412
- }
1413
- else {
1414
- self.reposition();
1415
- }
1416
- }
1417
- }
1418
-
1419
- if (self.__options.trackTooltip) {
1420
-
1421
- var currentBcr = $content[0].getBoundingClientRect();
1422
-
1423
- if ( currentBcr.height !== self.__contentBcr.height
1424
- || currentBcr.width !== self.__contentBcr.width
1425
- ) {
1426
- self.reposition();
1427
- self.__contentBcr = currentBcr;
1428
- }
1429
- }
1430
- }
1431
- }, self.__options.trackerInterval);
1432
-
1433
- return self;
1434
- },
1435
-
1436
- /**
1437
- * Closes the tooltip (after the closing delay)
1438
- *
1439
- * @param event
1440
- * @param callback
1441
- * @param force Set to true to override a potential refusal of the user's function
1442
- * @returns {self}
1443
- * @protected
1444
- */
1445
- _close: function(event, callback, force) {
1446
-
1447
- var self = this,
1448
- ok = true;
1449
-
1450
- self._trigger({
1451
- type: 'close',
1452
- event: event,
1453
- stop: function() {
1454
- ok = false;
1455
- }
1456
- });
1457
-
1458
- // a destroying tooltip (force == true) may not refuse to close
1459
- if (ok || force) {
1460
-
1461
- // save the method custom callback and cancel any open method custom callbacks
1462
- if (callback) self.__callbacks.close.push(callback);
1463
- self.__callbacks.open = [];
1464
-
1465
- // clear open/close timeouts
1466
- self.__timeoutsClear();
1467
-
1468
- var finishCallbacks = function() {
1469
-
1470
- // trigger any close method custom callbacks and reset them
1471
- $.each(self.__callbacks.close, function(i,c) {
1472
- c.call(self, self, {
1473
- event: event,
1474
- origin: self._$origin[0]
1475
- });
1476
- });
1477
-
1478
- self.__callbacks.close = [];
1479
- };
1480
-
1481
- if (self.__state != 'closed') {
1482
-
1483
- var necessary = true,
1484
- d = new Date(),
1485
- now = d.getTime(),
1486
- newClosingTime = now + self.__options.animationDuration[1];
1487
-
1488
- // the tooltip may already already be disappearing, but if a new
1489
- // call to close() is made after the animationDuration was changed
1490
- // to 0 (for example), we ought to actually close it sooner than
1491
- // previously scheduled. In that case it should be noted that the
1492
- // browser will not adapt the animation duration to the new
1493
- // animationDuration that was set after the start of the closing
1494
- // animation.
1495
- // Note: the same thing could be considered at opening, but is not
1496
- // really useful since the tooltip is actually opened immediately
1497
- // upon a call to _open(). Since it would not make the opening
1498
- // animation finish sooner, its sole impact would be to trigger the
1499
- // state event and the open callbacks sooner than the actual end of
1500
- // the opening animation, which is not great.
1501
- if (self.__state == 'disappearing') {
1502
-
1503
- if (newClosingTime > self.__closingTime) {
1504
- necessary = false;
1505
- }
1506
- }
1507
-
1508
- if (necessary) {
1509
-
1510
- self.__closingTime = newClosingTime;
1511
-
1512
- if (self.__state != 'disappearing') {
1513
- self.__stateSet('disappearing');
1514
- }
1515
-
1516
- var finish = function() {
1517
-
1518
- // stop the tracker
1519
- clearInterval(self.__tracker);
1520
-
1521
- // a "beforeClose" option has been asked several times but would
1522
- // probably useless since the content element is still accessible
1523
- // via ::content(), and because people can always use listeners
1524
- // inside their content to track what's going on. For the sake of
1525
- // simplicity, this has been denied. Bur for the rare people who
1526
- // really need the option (for old browsers or for the case where
1527
- // detaching the content is actually destructive, for file or
1528
- // password inputs for example), this event will do the work.
1529
- self._trigger({
1530
- type: 'closing',
1531
- event: event
1532
- });
1533
-
1534
- // unbind listeners which are no longer needed
1535
-
1536
- self._$tooltip
1537
- .off('.'+ self.__namespace +'-triggerClose')
1538
- .removeClass('tooltipster-dying');
1539
-
1540
- // orientationchange, scroll and resize listeners
1541
- $(env.window).off('.'+ self.__namespace +'-triggerClose');
1542
-
1543
- // scroll listeners
1544
- self.__$originParents.each(function(i, el) {
1545
- $(el).off('scroll.'+ self.__namespace +'-triggerClose');
1546
- });
1547
- // clear the array to prevent memory leaks
1548
- self.__$originParents = null;
1549
-
1550
- $(env.window.document.body).off('.'+ self.__namespace +'-triggerClose');
1551
-
1552
- self._$origin.off('.'+ self.__namespace +'-triggerClose');
1553
-
1554
- self._off('dismissable');
1555
-
1556
- // a plugin that would like to remove the tooltip from the
1557
- // DOM when closed should bind on this
1558
- self.__stateSet('closed');
1559
-
1560
- // trigger event
1561
- self._trigger({
1562
- type: 'after',
1563
- event: event
1564
- });
1565
-
1566
- // call our constructor custom callback function
1567
- if (self.__options.functionAfter) {
1568
- self.__options.functionAfter.call(self, self, {
1569
- event: event,
1570
- origin: self._$origin[0]
1571
- });
1572
- }
1573
-
1574
- // call our method custom callbacks functions
1575
- finishCallbacks();
1576
- };
1577
-
1578
- if (env.hasTransitions) {
1579
-
1580
- self._$tooltip.css({
1581
- '-moz-animation-duration': self.__options.animationDuration[1] + 'ms',
1582
- '-ms-animation-duration': self.__options.animationDuration[1] + 'ms',
1583
- '-o-animation-duration': self.__options.animationDuration[1] + 'ms',
1584
- '-webkit-animation-duration': self.__options.animationDuration[1] + 'ms',
1585
- 'animation-duration': self.__options.animationDuration[1] + 'ms',
1586
- 'transition-duration': self.__options.animationDuration[1] + 'ms'
1587
- });
1588
-
1589
- self._$tooltip
1590
- // clear both potential open and close tasks
1591
- .clearQueue()
1592
- .removeClass('tooltipster-show')
1593
- // for transitions only
1594
- .addClass('tooltipster-dying');
1595
-
1596
- if (self.__options.animationDuration[1] > 0) {
1597
- self._$tooltip.delay(self.__options.animationDuration[1]);
1598
- }
1599
-
1600
- self._$tooltip.queue(finish);
1601
- }
1602
- else {
1603
-
1604
- self._$tooltip
1605
- .stop()
1606
- .fadeOut(self.__options.animationDuration[1], finish);
1607
- }
1608
- }
1609
- }
1610
- // if the tooltip is already closed, we still need to trigger
1611
- // the method custom callbacks
1612
- else {
1613
- finishCallbacks();
1614
- }
1615
- }
1616
-
1617
- return self;
1618
- },
1619
-
1620
- /**
1621
- * For internal use by plugins, if needed
1622
- *
1623
- * @returns {self}
1624
- * @protected
1625
- */
1626
- _off: function() {
1627
- this.__$emitterPrivate.off.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
1628
- return this;
1629
- },
1630
-
1631
- /**
1632
- * For internal use by plugins, if needed
1633
- *
1634
- * @returns {self}
1635
- * @protected
1636
- */
1637
- _on: function() {
1638
- this.__$emitterPrivate.on.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
1639
- return this;
1640
- },
1641
-
1642
- /**
1643
- * For internal use by plugins, if needed
1644
- *
1645
- * @returns {self}
1646
- * @protected
1647
- */
1648
- _one: function() {
1649
- this.__$emitterPrivate.one.apply(this.__$emitterPrivate, Array.prototype.slice.apply(arguments));
1650
- return this;
1651
- },
1652
-
1653
- /**
1654
- * Opens the tooltip right away.
1655
- *
1656
- * @param event
1657
- * @param callback Will be called when the opening animation is over
1658
- * @returns {self}
1659
- * @protected
1660
- */
1661
- _open: function(event, callback) {
1662
-
1663
- var self = this;
1664
-
1665
- // if the destruction process has not begun and if this was not
1666
- // triggered by an unwanted emulated click event
1667
- if (!self.__destroying) {
1668
-
1669
- // check that the origin is still in the DOM
1670
- if ( bodyContains(self._$origin)
1671
- // if the tooltip is enabled
1672
- && self.__enabled
1673
- ) {
1674
-
1675
- var ok = true;
1676
-
1677
- // if the tooltip is not open yet, we need to call functionBefore.
1678
- // otherwise we can jst go on
1679
- if (self.__state == 'closed') {
1680
-
1681
- // trigger an event. The event.stop function allows the callback
1682
- // to prevent the opening of the tooltip
1683
- self._trigger({
1684
- type: 'before',
1685
- event: event,
1686
- stop: function() {
1687
- ok = false;
1688
- }
1689
- });
1690
-
1691
- if (ok && self.__options.functionBefore) {
1692
-
1693
- // call our custom function before continuing
1694
- ok = self.__options.functionBefore.call(self, self, {
1695
- event: event,
1696
- origin: self._$origin[0]
1697
- });
1698
- }
1699
- }
1700
-
1701
- if (ok !== false) {
1702
-
1703
- // if there is some content
1704
- if (self.__Content !== null) {
1705
-
1706
- // save the method callback and cancel close method callbacks
1707
- if (callback) {
1708
- self.__callbacks.open.push(callback);
1709
- }
1710
- self.__callbacks.close = [];
1711
-
1712
- // get rid of any appearance timeouts
1713
- self.__timeoutsClear();
1714
-
1715
- var extraTime,
1716
- finish = function() {
1717
-
1718
- if (self.__state != 'stable') {
1719
- self.__stateSet('stable');
1720
- }
1721
-
1722
- // trigger any open method custom callbacks and reset them
1723
- $.each(self.__callbacks.open, function(i,c) {
1724
- c.call(self, self, {
1725
- origin: self._$origin[0],
1726
- tooltip: self._$tooltip[0]
1727
- });
1728
- });
1729
-
1730
- self.__callbacks.open = [];
1731
- };
1732
-
1733
- // if the tooltip is already open
1734
- if (self.__state !== 'closed') {
1735
-
1736
- // the timer (if any) will start (or restart) right now
1737
- extraTime = 0;
1738
-
1739
- // if it was disappearing, cancel that
1740
- if (self.__state === 'disappearing') {
1741
-
1742
- self.__stateSet('appearing');
1743
-
1744
- if (env.hasTransitions) {
1745
-
1746
- self._$tooltip
1747
- .clearQueue()
1748
- .removeClass('tooltipster-dying')
1749
- .addClass('tooltipster-show');
1750
-
1751
- if (self.__options.animationDuration[0] > 0) {
1752
- self._$tooltip.delay(self.__options.animationDuration[0]);
1753
- }
1754
-
1755
- self._$tooltip.queue(finish);
1756
- }
1757
- else {
1758
- // in case the tooltip was currently fading out, bring it back
1759
- // to life
1760
- self._$tooltip
1761
- .stop()
1762
- .fadeIn(finish);
1763
- }
1764
- }
1765
- // if the tooltip is already open, we still need to trigger the method
1766
- // custom callback
1767
- else if (self.__state == 'stable') {
1768
- finish();
1769
- }
1770
- }
1771
- // if the tooltip isn't already open, open it
1772
- else {
1773
-
1774
- // a plugin must bind on this and store the tooltip in this._$tooltip
1775
- self.__stateSet('appearing');
1776
-
1777
- // the timer (if any) will start when the tooltip has fully appeared
1778
- // after its transition
1779
- extraTime = self.__options.animationDuration[0];
1780
-
1781
- // insert the content inside the tooltip
1782
- self.__contentInsert();
1783
-
1784
- // reposition the tooltip and attach to the DOM
1785
- self.reposition(event, true);
1786
-
1787
- // animate in the tooltip. If the display plugin wants no css
1788
- // animations, it may override the animation option with a
1789
- // dummy value that will produce no effect
1790
- if (env.hasTransitions) {
1791
-
1792
- // note: there seems to be an issue with start animations which
1793
- // are randomly not played on fast devices in both Chrome and FF,
1794
- // couldn't find a way to solve it yet. It seems that applying
1795
- // the classes before appending to the DOM helps a little, but
1796
- // it messes up some CSS transitions. The issue almost never
1797
- // happens when delay[0]==0 though
1798
- self._$tooltip
1799
- .addClass('tooltipster-'+ self.__options.animation)
1800
- .addClass('tooltipster-initial')
1801
- .css({
1802
- '-moz-animation-duration': self.__options.animationDuration[0] + 'ms',
1803
- '-ms-animation-duration': self.__options.animationDuration[0] + 'ms',
1804
- '-o-animation-duration': self.__options.animationDuration[0] + 'ms',
1805
- '-webkit-animation-duration': self.__options.animationDuration[0] + 'ms',
1806
- 'animation-duration': self.__options.animationDuration[0] + 'ms',
1807
- 'transition-duration': self.__options.animationDuration[0] + 'ms'
1808
- });
1809
-
1810
- setTimeout(
1811
- function() {
1812
-
1813
- // a quick hover may have already triggered a mouseleave
1814
- if (self.__state != 'closed') {
1815
-
1816
- self._$tooltip
1817
- .addClass('tooltipster-show')
1818
- .removeClass('tooltipster-initial');
1819
-
1820
- if (self.__options.animationDuration[0] > 0) {
1821
- self._$tooltip.delay(self.__options.animationDuration[0]);
1822
- }
1823
-
1824
- self._$tooltip.queue(finish);
1825
- }
1826
- },
1827
- 0
1828
- );
1829
- }
1830
- else {
1831
-
1832
- // old browsers will have to live with this
1833
- self._$tooltip
1834
- .css('display', 'none')
1835
- .fadeIn(self.__options.animationDuration[0], finish);
1836
- }
1837
-
1838
- // checks if the origin is removed while the tooltip is open
1839
- self.__trackerStart();
1840
-
1841
- // NOTE: the listeners below have a '-triggerClose' namespace
1842
- // because we'll remove them when the tooltip closes (unlike
1843
- // the '-triggerOpen' listeners). So some of them are actually
1844
- // not about close triggers, rather about positioning.
1845
-
1846
- $(env.window)
1847
- // reposition on resize
1848
- .on('resize.'+ self.__namespace +'-triggerClose', function(e) {
1849
-
1850
- var $ae = $(document.activeElement);
1851
-
1852
- // reposition only if the resize event was not triggered upon the opening
1853
- // of a virtual keyboard due to an input field being focused within the tooltip
1854
- // (otherwise the repositioning would lose the focus)
1855
- if ( (!$ae.is('input') && !$ae.is('textarea'))
1856
- || !$.contains(self._$tooltip[0], $ae[0])
1857
- ) {
1858
- self.reposition(e);
1859
- }
1860
- })
1861
- // same as below for parents
1862
- .on('scroll.'+ self.__namespace +'-triggerClose', function(e) {
1863
- self.__scrollHandler(e);
1864
- });
1865
-
1866
- self.__$originParents = self._$origin.parents();
1867
-
1868
- // scrolling may require the tooltip to be moved or even
1869
- // repositioned in some cases
1870
- self.__$originParents.each(function(i, parent) {
1871
-
1872
- $(parent).on('scroll.'+ self.__namespace +'-triggerClose', function(e) {
1873
- self.__scrollHandler(e);
1874
- });
1875
- });
1876
-
1877
- if ( self.__options.triggerClose.mouseleave
1878
- || (self.__options.triggerClose.touchleave && env.hasTouchCapability)
1879
- ) {
1880
-
1881
- // we use an event to allow users/plugins to control when the mouseleave/touchleave
1882
- // close triggers will come to action. It allows to have more triggering elements
1883
- // than just the origin and the tooltip for example, or to cancel/delay the closing,
1884
- // or to make the tooltip interactive even if it wasn't when it was open, etc.
1885
- self._on('dismissable', function(event) {
1886
-
1887
- if (event.dismissable) {
1888
-
1889
- if (event.delay) {
1890
-
1891
- timeout = setTimeout(function() {
1892
- // event.event may be undefined
1893
- self._close(event.event);
1894
- }, event.delay);
1895
-
1896
- self.__timeouts.close.push(timeout);
1897
- }
1898
- else {
1899
- self._close(event);
1900
- }
1901
- }
1902
- else {
1903
- clearTimeout(timeout);
1904
- }
1905
- });
1906
-
1907
- // now set the listeners that will trigger 'dismissable' events
1908
- var $elements = self._$origin,
1909
- eventNamesIn = '',
1910
- eventNamesOut = '',
1911
- timeout = null;
1912
-
1913
- // if we have to allow interaction, bind on the tooltip too
1914
- if (self.__options.interactive) {
1915
- $elements = $elements.add(self._$tooltip);
1916
- }
1917
-
1918
- if (self.__options.triggerClose.mouseleave) {
1919
- eventNamesIn += 'mouseenter.'+ self.__namespace +'-triggerClose ';
1920
- eventNamesOut += 'mouseleave.'+ self.__namespace +'-triggerClose ';
1921
- }
1922
- if (self.__options.triggerClose.touchleave && env.hasTouchCapability) {
1923
- eventNamesIn += 'touchstart.'+ self.__namespace +'-triggerClose';
1924
- eventNamesOut += 'touchend.'+ self.__namespace +'-triggerClose touchcancel.'+ self.__namespace +'-triggerClose';
1925
- }
1926
-
1927
- $elements
1928
- // close after some time spent outside of the elements
1929
- .on(eventNamesOut, function(event) {
1930
-
1931
- // it's ok if the touch gesture ended up to be a swipe,
1932
- // it's still a "touch leave" situation
1933
- if ( self._touchIsTouchEvent(event)
1934
- || !self._touchIsEmulatedEvent(event)
1935
- ) {
1936
-
1937
- var delay = (event.type == 'mouseleave') ?
1938
- self.__options.delay :
1939
- self.__options.delayTouch;
1940
-
1941
- self._trigger({
1942
- delay: delay[1],
1943
- dismissable: true,
1944
- event: event,
1945
- type: 'dismissable'
1946
- });
1947
- }
1948
- })
1949
- // suspend the mouseleave timeout when the pointer comes back
1950
- // over the elements
1951
- .on(eventNamesIn, function(event) {
1952
-
1953
- // it's also ok if the touch event is a swipe gesture
1954
- if ( self._touchIsTouchEvent(event)
1955
- || !self._touchIsEmulatedEvent(event)
1956
- ) {
1957
- self._trigger({
1958
- dismissable: false,
1959
- event: event,
1960
- type: 'dismissable'
1961
- });
1962
- }
1963
- });
1964
- }
1965
-
1966
- // close the tooltip when the origin gets a mouse click (common behavior of
1967
- // native tooltips)
1968
- if (self.__options.triggerClose.originClick) {
1969
-
1970
- self._$origin.on('click.'+ self.__namespace + '-triggerClose', function(event) {
1971
-
1972
- // we could actually let a tap trigger this but this feature just
1973
- // does not make sense on touch devices
1974
- if ( !self._touchIsTouchEvent(event)
1975
- && !self._touchIsEmulatedEvent(event)
1976
- ) {
1977
- self._close(event);
1978
- }
1979
- });
1980
- }
1981
-
1982
- // set the same bindings for click and touch on the body to close the tooltip
1983
- if ( self.__options.triggerClose.click
1984
- || (self.__options.triggerClose.tap && env.hasTouchCapability)
1985
- ) {
1986
-
1987
- // don't set right away since the click/tap event which triggered this method
1988
- // (if it was a click/tap) is going to bubble up to the body, we don't want it
1989
- // to close the tooltip immediately after it opened
1990
- setTimeout(function() {
1991
-
1992
- if (self.__state != 'closed') {
1993
-
1994
- var eventNames = '',
1995
- $body = $(env.window.document.body);
1996
-
1997
- if (self.__options.triggerClose.click) {
1998
- eventNames += 'click.'+ self.__namespace +'-triggerClose ';
1999
- }
2000
- if (self.__options.triggerClose.tap && env.hasTouchCapability) {
2001
- eventNames += 'touchend.'+ self.__namespace +'-triggerClose';
2002
- }
2003
-
2004
- $body.on(eventNames, function(event) {
2005
-
2006
- if (self._touchIsMeaningfulEvent(event)) {
2007
-
2008
- self._touchRecordEvent(event);
2009
-
2010
- if (!self.__options.interactive || !$.contains(self._$tooltip[0], event.target)) {
2011
- self._close(event);
2012
- }
2013
- }
2014
- });
2015
-
2016
- // needed to detect and ignore swiping
2017
- if (self.__options.triggerClose.tap && env.hasTouchCapability) {
2018
-
2019
- $body.on('touchstart.'+ self.__namespace +'-triggerClose', function(event) {
2020
- self._touchRecordEvent(event);
2021
- });
2022
- }
2023
- }
2024
- }, 0);
2025
- }
2026
-
2027
- self._trigger('ready');
2028
-
2029
- // call our custom callback
2030
- if (self.__options.functionReady) {
2031
- self.__options.functionReady.call(self, self, {
2032
- origin: self._$origin[0],
2033
- tooltip: self._$tooltip[0]
2034
- });
2035
- }
2036
- }
2037
-
2038
- // if we have a timer set, let the countdown begin
2039
- if (self.__options.timer > 0) {
2040
-
2041
- var timeout = setTimeout(function() {
2042
- self._close();
2043
- }, self.__options.timer + extraTime);
2044
-
2045
- self.__timeouts.close.push(timeout);
2046
- }
2047
- }
2048
- }
2049
- }
2050
- }
2051
-
2052
- return self;
2053
- },
2054
-
2055
- /**
2056
- * When using the mouseenter/touchstart open triggers, this function will
2057
- * schedule the opening of the tooltip after the delay, if there is one
2058
- *
2059
- * @param event
2060
- * @returns {self}
2061
- * @protected
2062
- */
2063
- _openShortly: function(event) {
2064
-
2065
- var self = this,
2066
- ok = true;
2067
-
2068
- if (self.__state != 'stable' && self.__state != 'appearing') {
2069
-
2070
- // if a timeout is not already running
2071
- if (!self.__timeouts.open) {
2072
-
2073
- self._trigger({
2074
- type: 'start',
2075
- event: event,
2076
- stop: function() {
2077
- ok = false;
2078
- }
2079
- });
2080
-
2081
- if (ok) {
2082
-
2083
- var delay = (event.type.indexOf('touch') == 0) ?
2084
- self.__options.delayTouch :
2085
- self.__options.delay;
2086
-
2087
- if (delay[0]) {
2088
-
2089
- self.__timeouts.open = setTimeout(function() {
2090
-
2091
- self.__timeouts.open = null;
2092
-
2093
- // open only if the pointer (mouse or touch) is still over the origin.
2094
- // The check on the "meaningful event" can only be made here, after some
2095
- // time has passed (to know if the touch was a swipe or not)
2096
- if (self.__pointerIsOverOrigin && self._touchIsMeaningfulEvent(event)) {
2097
-
2098
- // signal that we go on
2099
- self._trigger('startend');
2100
-
2101
- self._open(event);
2102
- }
2103
- else {
2104
- // signal that we cancel
2105
- self._trigger('startcancel');
2106
- }
2107
- }, delay[0]);
2108
- }
2109
- else {
2110
- // signal that we go on
2111
- self._trigger('startend');
2112
-
2113
- self._open(event);
2114
- }
2115
- }
2116
- }
2117
- }
2118
-
2119
- return self;
2120
- },
2121
-
2122
- /**
2123
- * Meant for plugins to get their options
2124
- *
2125
- * @param {string} pluginName The name of the plugin that asks for its options
2126
- * @param {object} defaultOptions The default options of the plugin
2127
- * @returns {object} The options
2128
- * @protected
2129
- */
2130
- _optionsExtract: function(pluginName, defaultOptions) {
2131
-
2132
- var self = this,
2133
- options = $.extend(true, {}, defaultOptions);
2134
-
2135
- // if the plugin options were isolated in a property named after the
2136
- // plugin, use them (prevents conflicts with other plugins)
2137
- var pluginOptions = self.__options[pluginName];
2138
-
2139
- // if not, try to get them as regular options
2140
- if (!pluginOptions){
2141
-
2142
- pluginOptions = {};
2143
-
2144
- $.each(defaultOptions, function(optionName, value) {
2145
-
2146
- var o = self.__options[optionName];
2147
-
2148
- if (o !== undefined) {
2149
- pluginOptions[optionName] = o;
2150
- }
2151
- });
2152
- }
2153
-
2154
- // let's merge the default options and the ones that were provided. We'd want
2155
- // to do a deep copy but not let jQuery merge arrays, so we'll do a shallow
2156
- // extend on two levels, that will be enough if options are not more than 1
2157
- // level deep
2158
- $.each(options, function(optionName, value) {
2159
-
2160
- if (pluginOptions[optionName] !== undefined) {
2161
-
2162
- if (( typeof value == 'object'
2163
- && !(value instanceof Array)
2164
- && value != null
2165
- )
2166
- &&
2167
- ( typeof pluginOptions[optionName] == 'object'
2168
- && !(pluginOptions[optionName] instanceof Array)
2169
- && pluginOptions[optionName] != null
2170
- )
2171
- ) {
2172
- $.extend(options[optionName], pluginOptions[optionName]);
2173
- }
2174
- else {
2175
- options[optionName] = pluginOptions[optionName];
2176
- }
2177
- }
2178
- });
2179
-
2180
- return options;
2181
- },
2182
-
2183
- /**
2184
- * Used at instantiation of the plugin, or afterwards by plugins that activate themselves
2185
- * on existing instances
2186
- *
2187
- * @param {object} pluginName
2188
- * @returns {self}
2189
- * @protected
2190
- */
2191
- _plug: function(pluginName) {
2192
-
2193
- var plugin = $.tooltipster._plugin(pluginName);
2194
-
2195
- if (plugin) {
2196
-
2197
- // if there is a constructor for instances
2198
- if (plugin.instance) {
2199
-
2200
- // proxy non-private methods on the instance to allow new instance methods
2201
- $.tooltipster.__bridge(plugin.instance, this, plugin.name);
2202
- }
2203
- }
2204
- else {
2205
- throw new Error('The "'+ pluginName +'" plugin is not defined');
2206
- }
2207
-
2208
- return this;
2209
- },
2210
-
2211
- /**
2212
- * This will return true if the event is a mouse event which was
2213
- * emulated by the browser after a touch event. This allows us to
2214
- * really dissociate mouse and touch triggers.
2215
- *
2216
- * There is a margin of error if a real mouse event is fired right
2217
- * after (within the delay shown below) a touch event on the same
2218
- * element, but hopefully it should not happen often.
2219
- *
2220
- * @returns {boolean}
2221
- * @protected
2222
- */
2223
- _touchIsEmulatedEvent: function(event) {
2224
-
2225
- var isEmulated = false,
2226
- now = new Date().getTime();
2227
-
2228
- for (var i = this.__touchEvents.length - 1; i >= 0; i--) {
2229
-
2230
- var e = this.__touchEvents[i];
2231
-
2232
- // delay, in milliseconds. It's supposed to be 300ms in
2233
- // most browsers (350ms on iOS) to allow a double tap but
2234
- // can be less (check out FastClick for more info)
2235
- if (now - e.time < 500) {
2236
-
2237
- if (e.target === event.target) {
2238
- isEmulated = true;
2239
- }
2240
- }
2241
- else {
2242
- break;
2243
- }
2244
- }
2245
-
2246
- return isEmulated;
2247
- },
2248
-
2249
- /**
2250
- * Returns false if the event was an emulated mouse event or
2251
- * a touch event involved in a swipe gesture.
2252
- *
2253
- * @param {object} event
2254
- * @returns {boolean}
2255
- * @protected
2256
- */
2257
- _touchIsMeaningfulEvent: function(event) {
2258
- return (
2259
- (this._touchIsTouchEvent(event) && !this._touchSwiped(event.target))
2260
- || (!this._touchIsTouchEvent(event) && !this._touchIsEmulatedEvent(event))
2261
- );
2262
- },
2263
-
2264
- /**
2265
- * Checks if an event is a touch event
2266
- *
2267
- * @param {object} event
2268
- * @returns {boolean}
2269
- * @protected
2270
- */
2271
- _touchIsTouchEvent: function(event){
2272
- return event.type.indexOf('touch') == 0;
2273
- },
2274
-
2275
- /**
2276
- * Store touch events for a while to detect swiping and emulated mouse events
2277
- *
2278
- * @param {object} event
2279
- * @returns {self}
2280
- * @protected
2281
- */
2282
- _touchRecordEvent: function(event) {
2283
-
2284
- if (this._touchIsTouchEvent(event)) {
2285
- event.time = new Date().getTime();
2286
- this.__touchEvents.push(event);
2287
- }
2288
-
2289
- return this;
2290
- },
2291
-
2292
- /**
2293
- * Returns true if a swipe happened after the last touchstart event fired on
2294
- * event.target.
2295
- *
2296
- * We need to differentiate a swipe from a tap before we let the event open
2297
- * or close the tooltip. A swipe is when a touchmove (scroll) event happens
2298
- * on the body between the touchstart and the touchend events of an element.
2299
- *
2300
- * @param {object} target The HTML element that may have triggered the swipe
2301
- * @returns {boolean}
2302
- * @protected
2303
- */
2304
- _touchSwiped: function(target) {
2305
-
2306
- var swiped = false;
2307
-
2308
- for (var i = this.__touchEvents.length - 1; i >= 0; i--) {
2309
-
2310
- var e = this.__touchEvents[i];
2311
-
2312
- if (e.type == 'touchmove') {
2313
- swiped = true;
2314
- break;
2315
- }
2316
- else if (
2317
- e.type == 'touchstart'
2318
- && target === e.target
2319
- ) {
2320
- break;
2321
- }
2322
- }
2323
-
2324
- return swiped;
2325
- },
2326
-
2327
- /**
2328
- * Triggers an event on the instance emitters
2329
- *
2330
- * @returns {self}
2331
- * @protected
2332
- */
2333
- _trigger: function() {
2334
-
2335
- var args = Array.prototype.slice.apply(arguments);
2336
-
2337
- if (typeof args[0] == 'string') {
2338
- args[0] = { type: args[0] };
2339
- }
2340
-
2341
- // add properties to the event
2342
- args[0].instance = this;
2343
- args[0].origin = this._$origin ? this._$origin[0] : null;
2344
- args[0].tooltip = this._$tooltip ? this._$tooltip[0] : null;
2345
-
2346
- // note: the order of emitters matters
2347
- this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate, args);
2348
- $.tooltipster._trigger.apply($.tooltipster, args);
2349
- this.__$emitterPublic.trigger.apply(this.__$emitterPublic, args);
2350
-
2351
- return this;
2352
- },
2353
-
2354
- /**
2355
- * Deactivate a plugin on this instance
2356
- *
2357
- * @returns {self}
2358
- * @protected
2359
- */
2360
- _unplug: function(pluginName) {
2361
-
2362
- var self = this;
2363
-
2364
- // if the plugin has been activated on this instance
2365
- if (self[pluginName]) {
2366
-
2367
- var plugin = $.tooltipster._plugin(pluginName);
2368
-
2369
- // if there is a constructor for instances
2370
- if (plugin.instance) {
2371
-
2372
- // unbridge
2373
- $.each(plugin.instance, function(methodName, fn) {
2374
-
2375
- // if the method exists (privates methods do not) and comes indeed from
2376
- // this plugin (may be missing or come from a conflicting plugin).
2377
- if ( self[methodName]
2378
- && self[methodName].bridged === self[pluginName]
2379
- ) {
2380
- delete self[methodName];
2381
- }
2382
- });
2383
- }
2384
-
2385
- // destroy the plugin
2386
- if (self[pluginName].__destroy) {
2387
- self[pluginName].__destroy();
2388
- }
2389
-
2390
- // remove the reference to the plugin instance
2391
- delete self[pluginName];
2392
- }
2393
-
2394
- return self;
2395
- },
2396
-
2397
- /**
2398
- * @see self::_close
2399
- * @returns {self}
2400
- * @public
2401
- */
2402
- close: function(callback) {
2403
-
2404
- if (!this.__destroyed) {
2405
- this._close(null, callback);
2406
- }
2407
- else {
2408
- this.__destroyError();
2409
- }
2410
-
2411
- return this;
2412
- },
2413
-
2414
- /**
2415
- * Sets or gets the content of the tooltip
2416
- *
2417
- * @returns {mixed|self}
2418
- * @public
2419
- */
2420
- content: function(content) {
2421
-
2422
- var self = this;
2423
-
2424
- // getter method
2425
- if (content === undefined) {
2426
- return self.__Content;
2427
- }
2428
- // setter method
2429
- else {
2430
-
2431
- if (!self.__destroyed) {
2432
-
2433
- // change the content
2434
- self.__contentSet(content);
2435
-
2436
- if (self.__Content !== null) {
2437
-
2438
- // update the tooltip if it is open
2439
- if (self.__state !== 'closed') {
2440
-
2441
- // reset the content in the tooltip
2442
- self.__contentInsert();
2443
-
2444
- // reposition and resize the tooltip
2445
- self.reposition();
2446
-
2447
- // if we want to play a little animation showing the content changed
2448
- if (self.__options.updateAnimation) {
2449
-
2450
- if (env.hasTransitions) {
2451
-
2452
- // keep the reference in the local scope
2453
- var animation = self.__options.updateAnimation;
2454
-
2455
- self._$tooltip.addClass('tooltipster-update-'+ animation);
2456
-
2457
- // remove the class after a while. The actual duration of the
2458
- // update animation may be shorter, it's set in the CSS rules
2459
- setTimeout(function() {
2460
-
2461
- if (self.__state != 'closed') {
2462
-
2463
- self._$tooltip.removeClass('tooltipster-update-'+ animation);
2464
- }
2465
- }, 1000);
2466
- }
2467
- else {
2468
- self._$tooltip.fadeTo(200, 0.5, function() {
2469
- if (self.__state != 'closed') {
2470
- self._$tooltip.fadeTo(200, 1);
2471
- }
2472
- });
2473
- }
2474
- }
2475
- }
2476
- }
2477
- else {
2478
- self._close();
2479
- }
2480
- }
2481
- else {
2482
- self.__destroyError();
2483
- }
2484
-
2485
- return self;
2486
- }
2487
- },
2488
-
2489
- /**
2490
- * Destroys the tooltip
2491
- *
2492
- * @returns {self}
2493
- * @public
2494
- */
2495
- destroy: function() {
2496
-
2497
- var self = this;
2498
-
2499
- if (!self.__destroyed) {
2500
-
2501
- if(self.__state != 'closed'){
2502
-
2503
- // no closing delay
2504
- self.option('animationDuration', 0)
2505
- // force closing
2506
- ._close(null, null, true);
2507
- }
2508
-
2509
- // send event
2510
- self._trigger('destroy');
2511
-
2512
- self.__destroyed = true;
2513
-
2514
- self._$origin
2515
- .removeData(self.__namespace)
2516
- // remove the open trigger listeners
2517
- .off('.'+ self.__namespace +'-triggerOpen');
2518
-
2519
- // remove the touch listener
2520
- $(env.window.document.body).off('.' + self.__namespace +'-triggerOpen');
2521
-
2522
- var ns = self._$origin.data('tooltipster-ns');
2523
-
2524
- // if the origin has been removed from DOM, its data may
2525
- // well have been destroyed in the process and there would
2526
- // be nothing to clean up or restore
2527
- if (ns) {
2528
-
2529
- // if there are no more tooltips on this element
2530
- if (ns.length === 1) {
2531
-
2532
- // optional restoration of a title attribute
2533
- var title = null;
2534
- if (self.__options.restoration == 'previous') {
2535
- title = self._$origin.data('tooltipster-initialTitle');
2536
- }
2537
- else if (self.__options.restoration == 'current') {
2538
-
2539
- // old school technique to stringify when outerHTML is not supported
2540
- title = (typeof self.__Content == 'string') ?
2541
- self.__Content :
2542
- $('<div></div>').append(self.__Content).html();
2543
- }
2544
-
2545
- if (title) {
2546
- self._$origin.attr('title', title);
2547
- }
2548
-
2549
- // final cleaning
2550
-
2551
- self._$origin.removeClass('tooltipstered');
2552
-
2553
- self._$origin
2554
- .removeData('tooltipster-ns')
2555
- .removeData('tooltipster-initialTitle');
2556
- }
2557
- else {
2558
- // remove the instance namespace from the list of namespaces of
2559
- // tooltips present on the element
2560
- ns = $.grep(ns, function(el, i) {
2561
- return el !== self.__namespace;
2562
- });
2563
- self._$origin.data('tooltipster-ns', ns);
2564
- }
2565
- }
2566
-
2567
- // last event
2568
- self._trigger('destroyed');
2569
-
2570
- // unbind private and public event listeners
2571
- self._off();
2572
- self.off();
2573
-
2574
- // remove external references, just in case
2575
- self.__Content = null;
2576
- self.__$emitterPrivate = null;
2577
- self.__$emitterPublic = null;
2578
- self.__options.parent = null;
2579
- self._$origin = null;
2580
- self._$tooltip = null;
2581
-
2582
- // make sure the object is no longer referenced in there to prevent
2583
- // memory leaks
2584
- $.tooltipster.__instancesLatestArr = $.grep($.tooltipster.__instancesLatestArr, function(el, i) {
2585
- return self !== el;
2586
- });
2587
-
2588
- clearInterval(self.__garbageCollector);
2589
- }
2590
- else {
2591
- self.__destroyError();
2592
- }
2593
-
2594
- // we return the scope rather than true so that the call to
2595
- // .tooltipster('destroy') actually returns the matched elements
2596
- // and applies to all of them
2597
- return self;
2598
- },
2599
-
2600
- /**
2601
- * Disables the tooltip
2602
- *
2603
- * @returns {self}
2604
- * @public
2605
- */
2606
- disable: function() {
2607
-
2608
- if (!this.__destroyed) {
2609
-
2610
- // close first, in case the tooltip would not disappear on
2611
- // its own (no close trigger)
2612
- this._close();
2613
- this.__enabled = false;
2614
-
2615
- return this;
2616
- }
2617
- else {
2618
- this.__destroyError();
2619
- }
2620
-
2621
- return this;
2622
- },
2623
-
2624
- /**
2625
- * Returns the HTML element of the origin
2626
- *
2627
- * @returns {self}
2628
- * @public
2629
- */
2630
- elementOrigin: function() {
2631
-
2632
- if (!this.__destroyed) {
2633
- return this._$origin[0];
2634
- }
2635
- else {
2636
- this.__destroyError();
2637
- }
2638
- },
2639
-
2640
- /**
2641
- * Returns the HTML element of the tooltip
2642
- *
2643
- * @returns {self}
2644
- * @public
2645
- */
2646
- elementTooltip: function() {
2647
- return this._$tooltip ? this._$tooltip[0] : null;
2648
- },
2649
-
2650
- /**
2651
- * Enables the tooltip
2652
- *
2653
- * @returns {self}
2654
- * @public
2655
- */
2656
- enable: function() {
2657
- this.__enabled = true;
2658
- return this;
2659
- },
2660
-
2661
- /**
2662
- * Alias, deprecated in 4.0.0
2663
- *
2664
- * @param {function} callback
2665
- * @returns {self}
2666
- * @public
2667
- */
2668
- hide: function(callback) {
2669
- return this.close(callback);
2670
- },
2671
-
2672
- /**
2673
- * Returns the instance
2674
- *
2675
- * @returns {self}
2676
- * @public
2677
- */
2678
- instance: function() {
2679
- return this;
2680
- },
2681
-
2682
- /**
2683
- * For public use only, not to be used by plugins (use ::_off() instead)
2684
- *
2685
- * @returns {self}
2686
- * @public
2687
- */
2688
- off: function() {
2689
-
2690
- if (!this.__destroyed) {
2691
- this.__$emitterPublic.off.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2692
- }
2693
-
2694
- return this;
2695
- },
2696
-
2697
- /**
2698
- * For public use only, not to be used by plugins (use ::_on() instead)
2699
- *
2700
- * @returns {self}
2701
- * @public
2702
- */
2703
- on: function() {
2704
-
2705
- if (!this.__destroyed) {
2706
- this.__$emitterPublic.on.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2707
- }
2708
- else {
2709
- this.__destroyError();
2710
- }
2711
-
2712
- return this;
2713
- },
2714
-
2715
- /**
2716
- * For public use only, not to be used by plugins
2717
- *
2718
- * @returns {self}
2719
- * @public
2720
- */
2721
- one: function() {
2722
-
2723
- if (!this.__destroyed) {
2724
- this.__$emitterPublic.one.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2725
- }
2726
- else {
2727
- this.__destroyError();
2728
- }
2729
-
2730
- return this;
2731
- },
2732
-
2733
- /**
2734
- * @see self::_open
2735
- * @returns {self}
2736
- * @public
2737
- */
2738
- open: function(callback) {
2739
-
2740
- if (!this.__destroyed) {
2741
- this._open(null, callback);
2742
- }
2743
- else {
2744
- this.__destroyError();
2745
- }
2746
-
2747
- return this;
2748
- },
2749
-
2750
- /**
2751
- * Get or set options. For internal use and advanced users only.
2752
- *
2753
- * @param {string} o Option name
2754
- * @param {mixed} val optional A new value for the option
2755
- * @return {mixed|self} If val is omitted, the value of the option
2756
- * is returned, otherwise the instance itself is returned
2757
- * @public
2758
- */
2759
- option: function(o, val) {
2760
-
2761
- // getter
2762
- if (val === undefined) {
2763
- return this.__options[o];
2764
- }
2765
- // setter
2766
- else {
2767
-
2768
- if (!this.__destroyed) {
2769
-
2770
- // change value
2771
- this.__options[o] = val;
2772
-
2773
- // format
2774
- this.__optionsFormat();
2775
-
2776
- // re-prepare the triggers if needed
2777
- if ($.inArray(o, ['trigger', 'triggerClose', 'triggerOpen']) >= 0) {
2778
- this.__prepareOrigin();
2779
- }
2780
-
2781
- if (o === 'selfDestruction') {
2782
- this.__prepareGC();
2783
- }
2784
- }
2785
- else {
2786
- this.__destroyError();
2787
- }
2788
-
2789
- return this;
2790
- }
2791
- },
2792
-
2793
- /**
2794
- * This method is in charge of setting the position and size properties of the tooltip.
2795
- * All the hard work is delegated to the display plugin.
2796
- * Note: The tooltip may be detached from the DOM at the moment the method is called
2797
- * but must be attached by the end of the method call.
2798
- *
2799
- * @param {object} event For internal use only. Defined if an event such as
2800
- * window resizing triggered the repositioning
2801
- * @param {boolean} tooltipIsDetached For internal use only. Set this to true if you
2802
- * know that the tooltip not being in the DOM is not an issue (typically when the
2803
- * tooltip element has just been created but has not been added to the DOM yet).
2804
- * @returns {self}
2805
- * @public
2806
- */
2807
- reposition: function(event, tooltipIsDetached) {
2808
-
2809
- var self = this;
2810
-
2811
- if (!self.__destroyed) {
2812
-
2813
- // if the tooltip is still open and the origin is still in the DOM
2814
- if (self.__state != 'closed' && bodyContains(self._$origin)) {
2815
-
2816
- // if the tooltip has not been removed from DOM manually (or if it
2817
- // has been detached on purpose)
2818
- if (tooltipIsDetached || bodyContains(self._$tooltip)) {
2819
-
2820
- if (!tooltipIsDetached) {
2821
- // detach in case the tooltip overflows the window and adds
2822
- // scrollbars to it, so __geometry can be accurate
2823
- self._$tooltip.detach();
2824
- }
2825
-
2826
- // refresh the geometry object before passing it as a helper
2827
- self.__Geometry = self.__geometry();
2828
-
2829
- // let a plugin fo the rest
2830
- self._trigger({
2831
- type: 'reposition',
2832
- event: event,
2833
- helper: {
2834
- geo: self.__Geometry
2835
- }
2836
- });
2837
- }
2838
- }
2839
- }
2840
- else {
2841
- self.__destroyError();
2842
- }
2843
-
2844
- return self;
2845
- },
2846
-
2847
- /**
2848
- * Alias, deprecated in 4.0.0
2849
- *
2850
- * @param callback
2851
- * @returns {self}
2852
- * @public
2853
- */
2854
- show: function(callback) {
2855
- return this.open(callback);
2856
- },
2857
-
2858
- /**
2859
- * Returns some properties about the instance
2860
- *
2861
- * @returns {object}
2862
- * @public
2863
- */
2864
- status: function() {
2865
-
2866
- return {
2867
- destroyed: this.__destroyed,
2868
- enabled: this.__enabled,
2869
- open: this.__state !== 'closed',
2870
- state: this.__state
2871
- };
2872
- },
2873
-
2874
- /**
2875
- * For public use only, not to be used by plugins
2876
- *
2877
- * @returns {self}
2878
- * @public
2879
- */
2880
- triggerHandler: function() {
2881
-
2882
- if (!this.__destroyed) {
2883
- this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic, Array.prototype.slice.apply(arguments));
2884
- }
2885
- else {
2886
- this.__destroyError();
2887
- }
2888
-
2889
- return this;
2890
- }
2891
- };
2892
-
2893
- $.fn.tooltipster = function() {
2894
-
2895
- // for using in closures
2896
- var args = Array.prototype.slice.apply(arguments),
2897
- // common mistake: an HTML element can't be in several tooltips at the same time
2898
- contentCloningWarning = 'You are using a single HTML element as content for several tooltips. You probably want to set the contentCloning option to TRUE.';
2899
-
2900
- // this happens with $(sel).tooltipster(...) when $(sel) does not match anything
2901
- if (this.length === 0) {
2902
-
2903
- // still chainable
2904
- return this;
2905
- }
2906
- // this happens when calling $(sel).tooltipster('methodName or options')
2907
- // where $(sel) matches one or more elements
2908
- else {
2909
-
2910
- // method calls
2911
- if (typeof args[0] === 'string') {
2912
-
2913
- var v = '#*$~&';
2914
-
2915
- this.each(function() {
2916
-
2917
- // retrieve the namepaces of the tooltip(s) that exist on that element.
2918
- // We will interact with the first tooltip only.
2919
- var ns = $(this).data('tooltipster-ns'),
2920
- // self represents the instance of the first tooltipster plugin
2921
- // associated to the current HTML object of the loop
2922
- self = ns ? $(this).data(ns[0]) : null;
2923
-
2924
- // if the current element holds a tooltipster instance
2925
- if (self) {
2926
-
2927
- if (typeof self[args[0]] === 'function') {
2928
-
2929
- if ( this.length > 1
2930
- && args[0] == 'content'
2931
- && ( args[1] instanceof $
2932
- || (typeof args[1] == 'object' && args[1] != null && args[1].tagName)
2933
- )
2934
- && !self.__options.contentCloning
2935
- && self.__options.debug
2936
- ) {
2937
- console.log(contentCloningWarning);
2938
- }
2939
-
2940
- // note : args[1] and args[2] may not be defined
2941
- var resp = self[args[0]](args[1], args[2]);
2942
- }
2943
- else {
2944
- throw new Error('Unknown method "'+ args[0] +'"');
2945
- }
2946
-
2947
- // if the function returned anything other than the instance
2948
- // itself (which implies chaining, except for the `instance` method)
2949
- if (resp !== self || args[0] === 'instance') {
2950
-
2951
- v = resp;
2952
-
2953
- // return false to stop .each iteration on the first element
2954
- // matched by the selector
2955
- return false;
2956
- }
2957
- }
2958
- else {
2959
- throw new Error('You called Tooltipster\'s "'+ args[0] +'" method on an uninitialized element');
2960
- }
2961
- });
2962
-
2963
- return (v !== '#*$~&') ? v : this;
2964
- }
2965
- // first argument is undefined or an object: the tooltip is initializing
2966
- else {
2967
-
2968
- // reset the array of last initialized objects
2969
- $.tooltipster.__instancesLatestArr = [];
2970
-
2971
- // is there a defined value for the multiple option in the options object ?
2972
- var multipleIsSet = args[0] && args[0].multiple !== undefined,
2973
- // if the multiple option is set to true, or if it's not defined but
2974
- // set to true in the defaults
2975
- multiple = (multipleIsSet && args[0].multiple) || (!multipleIsSet && defaults.multiple),
2976
- // same for content
2977
- contentIsSet = args[0] && args[0].content !== undefined,
2978
- content = (contentIsSet && args[0].content) || (!contentIsSet && defaults.content),
2979
- // same for contentCloning
2980
- contentCloningIsSet = args[0] && args[0].contentCloning !== undefined,
2981
- contentCloning =
2982
- (contentCloningIsSet && args[0].contentCloning)
2983
- || (!contentCloningIsSet && defaults.contentCloning),
2984
- // same for debug
2985
- debugIsSet = args[0] && args[0].debug !== undefined,
2986
- debug = (debugIsSet && args[0].debug) || (!debugIsSet && defaults.debug);
2987
-
2988
- if ( this.length > 1
2989
- && ( content instanceof $
2990
- || (typeof content == 'object' && content != null && content.tagName)
2991
- )
2992
- && !contentCloning
2993
- && debug
2994
- ) {
2995
- console.log(contentCloningWarning);
2996
- }
2997
-
2998
- // create a tooltipster instance for each element if it doesn't
2999
- // already have one or if the multiple option is set, and attach the
3000
- // object to it
3001
- this.each(function() {
3002
-
3003
- var go = false,
3004
- $this = $(this),
3005
- ns = $this.data('tooltipster-ns'),
3006
- obj = null;
3007
-
3008
- if (!ns) {
3009
- go = true;
3010
- }
3011
- else if (multiple) {
3012
- go = true;
3013
- }
3014
- else if (debug) {
3015
- console.log('Tooltipster: one or more tooltips are already attached to the element below. Ignoring.');
3016
- console.log(this);
3017
- }
3018
-
3019
- if (go) {
3020
- obj = new $.Tooltipster(this, args[0]);
3021
-
3022
- // save the reference of the new instance
3023
- if (!ns) ns = [];
3024
- ns.push(obj.__namespace);
3025
- $this.data('tooltipster-ns', ns);
3026
-
3027
- // save the instance itself
3028
- $this.data(obj.__namespace, obj);
3029
-
3030
- // call our constructor custom function.
3031
- // we do this here and not in ::init() because we wanted
3032
- // the object to be saved in $this.data before triggering
3033
- // it
3034
- if (obj.__options.functionInit) {
3035
- obj.__options.functionInit.call(obj, obj, {
3036
- origin: this
3037
- });
3038
- }
3039
-
3040
- // and now the event, for the plugins and core emitter
3041
- obj._trigger('init');
3042
- }
3043
-
3044
- $.tooltipster.__instancesLatestArr.push(obj);
3045
- });
3046
-
3047
- return this;
3048
- }
3049
- }
3050
- };
3051
-
3052
- // Utilities
3053
-
3054
- /**
3055
- * A class to check if a tooltip can fit in given dimensions
3056
- *
3057
- * @param {object} $tooltip The jQuery wrapped tooltip element, or a clone of it
3058
- */
3059
- function Ruler($tooltip) {
3060
-
3061
- // list of instance variables
3062
-
3063
- this.$container;
3064
- this.constraints = null;
3065
- this.__$tooltip;
3066
-
3067
- this.__init($tooltip);
3068
- }
3069
-
3070
- Ruler.prototype = {
3071
-
3072
- /**
3073
- * Move the tooltip into an invisible div that does not allow overflow to make
3074
- * size tests. Note: the tooltip may or may not be attached to the DOM at the
3075
- * moment this method is called, it does not matter.
3076
- *
3077
- * @param {object} $tooltip The object to test. May be just a clone of the
3078
- * actual tooltip.
3079
- * @private
3080
- */
3081
- __init: function($tooltip) {
3082
-
3083
- this.__$tooltip = $tooltip;
3084
-
3085
- this.__$tooltip
3086
- .css({
3087
- // for some reason we have to specify top and left 0
3088
- left: 0,
3089
- // any overflow will be ignored while measuring
3090
- overflow: 'hidden',
3091
- // positions at (0,0) without the div using 100% of the available width
3092
- position: 'absolute',
3093
- top: 0
3094
- })
3095
- // overflow must be auto during the test. We re-set this in case
3096
- // it were modified by the user
3097
- .find('.tooltipster-content')
3098
- .css('overflow', 'auto');
3099
-
3100
- this.$container = $('<div class="tooltipster-ruler"></div>')
3101
- .append(this.__$tooltip)
3102
- .appendTo(env.window.document.body);
3103
- },
3104
-
3105
- /**
3106
- * Force the browser to redraw (re-render) the tooltip immediately. This is required
3107
- * when you changed some CSS properties and need to make something with it
3108
- * immediately, without waiting for the browser to redraw at the end of instructions.
3109
- *
3110
- * @see http://stackoverflow.com/questions/3485365/how-can-i-force-webkit-to-redraw-repaint-to-propagate-style-changes
3111
- * @private
3112
- */
3113
- __forceRedraw: function() {
3114
-
3115
- // note: this would work but for Webkit only
3116
- //this.__$tooltip.close();
3117
- //this.__$tooltip[0].offsetHeight;
3118
- //this.__$tooltip.open();
3119
-
3120
- // works in FF too
3121
- var $p = this.__$tooltip.parent();
3122
- this.__$tooltip.detach();
3123
- this.__$tooltip.appendTo($p);
3124
- },
3125
-
3126
- /**
3127
- * Set maximum dimensions for the tooltip. A call to ::measure afterwards
3128
- * will tell us if the content overflows or if it's ok
3129
- *
3130
- * @param {int} width
3131
- * @param {int} height
3132
- * @return {Ruler}
3133
- * @public
3134
- */
3135
- constrain: function(width, height) {
3136
-
3137
- this.constraints = {
3138
- width: width,
3139
- height: height
3140
- };
3141
-
3142
- this.__$tooltip.css({
3143
- // we disable display:flex, otherwise the content would overflow without
3144
- // creating horizontal scrolling (which we need to detect).
3145
- display: 'block',
3146
- // reset any previous height
3147
- height: '',
3148
- // we'll check if horizontal scrolling occurs
3149
- overflow: 'auto',
3150
- // we'll set the width and see what height is generated and if there
3151
- // is horizontal overflow
3152
- width: width
3153
- });
3154
-
3155
- return this;
3156
- },
3157
-
3158
- /**
3159
- * Reset the tooltip content overflow and remove the test container
3160
- *
3161
- * @returns {Ruler}
3162
- * @public
3163
- */
3164
- destroy: function() {
3165
-
3166
- // in case the element was not a clone
3167
- this.__$tooltip
3168
- .detach()
3169
- .find('.tooltipster-content')
3170
- .css({
3171
- // reset to CSS value
3172
- display: '',
3173
- overflow: ''
3174
- });
3175
-
3176
- this.$container.remove();
3177
- },
3178
-
3179
- /**
3180
- * Removes any constraints
3181
- *
3182
- * @returns {Ruler}
3183
- * @public
3184
- */
3185
- free: function() {
3186
-
3187
- this.constraints = null;
3188
-
3189
- // reset to natural size
3190
- this.__$tooltip.css({
3191
- display: '',
3192
- height: '',
3193
- overflow: 'visible',
3194
- width: ''
3195
- });
3196
-
3197
- return this;
3198
- },
3199
-
3200
- /**
3201
- * Returns the size of the tooltip. When constraints are applied, also returns
3202
- * whether the tooltip fits in the provided dimensions.
3203
- * The idea is to see if the new height is small enough and if the content does
3204
- * not overflow horizontally.
3205
- *
3206
- * @param {int} width
3207
- * @param {int} height
3208
- * @returns {object} An object with a bool `fits` property and a `size` property
3209
- * @public
3210
- */
3211
- measure: function() {
3212
-
3213
- this.__forceRedraw();
3214
-
3215
- var tooltipBcr = this.__$tooltip[0].getBoundingClientRect(),
3216
- result = { size: {
3217
- // bcr.width/height are not defined in IE8- but in this
3218
- // case, bcr.right/bottom will have the same value
3219
- // except in iOS 8+ where tooltipBcr.bottom/right are wrong
3220
- // after scrolling for reasons yet to be determined.
3221
- // tooltipBcr.top/left might not be 0, see issue #514
3222
- height: tooltipBcr.height || (tooltipBcr.bottom - tooltipBcr.top),
3223
- width: tooltipBcr.width || (tooltipBcr.right - tooltipBcr.left)
3224
- }};
3225
-
3226
- if (this.constraints) {
3227
-
3228
- // note: we used to use offsetWidth instead of boundingRectClient but
3229
- // it returned rounded values, causing issues with sub-pixel layouts.
3230
-
3231
- // note2: noticed that the bcrWidth of text content of a div was once
3232
- // greater than the bcrWidth of its container by 1px, causing the final
3233
- // tooltip box to be too small for its content. However, evaluating
3234
- // their widths one against the other (below) surprisingly returned
3235
- // equality. Happened only once in Chrome 48, was not able to reproduce
3236
- // => just having fun with float position values...
3237
-
3238
- var $content = this.__$tooltip.find('.tooltipster-content'),
3239
- height = this.__$tooltip.outerHeight(),
3240
- contentBcr = $content[0].getBoundingClientRect(),
3241
- fits = {
3242
- height: height <= this.constraints.height,
3243
- width: (
3244
- // this condition accounts for min-width property that
3245
- // may apply
3246
- tooltipBcr.width <= this.constraints.width
3247
- // the -1 is here because scrollWidth actually returns
3248
- // a rounded value, and may be greater than bcr.width if
3249
- // it was rounded up. This may cause an issue for contents
3250
- // which actually really overflow by 1px or so, but that
3251
- // should be rare. Not sure how to solve this efficiently.
3252
- // See http://blogs.msdn.com/b/ie/archive/2012/02/17/sub-pixel-rendering-and-the-css-object-model.aspx
3253
- && contentBcr.width >= $content[0].scrollWidth - 1
3254
- )
3255
- };
3256
-
3257
- result.fits = fits.height && fits.width;
3258
- }
3259
-
3260
- // old versions of IE get the width wrong for some reason and it causes
3261
- // the text to be broken to a new line, so we round it up. If the width
3262
- // is the width of the screen though, we can assume it is accurate.
3263
- if ( env.IE
3264
- && env.IE <= 11
3265
- && result.size.width !== env.window.document.documentElement.clientWidth
3266
- ) {
3267
- result.size.width = Math.ceil(result.size.width) + 1;
3268
- }
3269
-
3270
- return result;
3271
- }
3272
- };
3273
-
3274
- // quick & dirty compare function, not bijective nor multidimensional
3275
- function areEqual(a,b) {
3276
- var same = true;
3277
- $.each(a, function(i, _) {
3278
- if (b[i] === undefined || a[i] !== b[i]) {
3279
- same = false;
3280
- return false;
3281
- }
3282
- });
3283
- return same;
3284
- }
3285
-
3286
- /**
3287
- * A fast function to check if an element is still in the DOM. It
3288
- * tries to use an id as ids are indexed by the browser, or falls
3289
- * back to jQuery's `contains` method. May fail if two elements
3290
- * have the same id, but so be it
3291
- *
3292
- * @param {object} $obj A jQuery-wrapped HTML element
3293
- * @return {boolean}
3294
- */
3295
- function bodyContains($obj) {
3296
- var id = $obj.attr('id'),
3297
- el = id ? env.window.document.getElementById(id) : null;
3298
- // must also check that the element with the id is the one we want
3299
- return el ? el === $obj[0] : $.contains(env.window.document.body, $obj[0]);
3300
- }
3301
-
3302
- // detect IE versions for dirty fixes
3303
- var uA = navigator.userAgent.toLowerCase();
3304
- if (uA.indexOf('msie') != -1) env.IE = parseInt(uA.split('msie')[1]);
3305
- else if (uA.toLowerCase().indexOf('trident') !== -1 && uA.indexOf(' rv:11') !== -1) env.IE = 11;
3306
- else if (uA.toLowerCase().indexOf('edge/') != -1) env.IE = parseInt(uA.toLowerCase().split('edge/')[1]);
3307
-
3308
- // detecting support for CSS transitions
3309
- function transitionSupport() {
3310
-
3311
- // env.window is not defined yet when this is called
3312
- if (!win) return false;
3313
-
3314
- var b = win.document.body || win.document.documentElement,
3315
- s = b.style,
3316
- p = 'transition',
3317
- v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
3318
-
3319
- if (typeof s[p] == 'string') { return true; }
3320
-
3321
- p = p.charAt(0).toUpperCase() + p.substr(1);
3322
- for (var i=0; i<v.length; i++) {
3323
- if (typeof s[v[i] + p] == 'string') { return true; }
3324
- }
3325
- return false;
3326
- }
3327
-
3328
- // we'll return jQuery for plugins not to have to declare it as a dependency,
3329
- // but it's done by a build task since it should be included only once at the
3330
- // end when we concatenate the main file with a plugin
3331
- // sideTip is Tooltipster's default plugin.
3332
- // This file will be UMDified by a build task.
3333
-
3334
- var pluginName = 'tooltipster.sideTip';
3335
-
3336
- $.tooltipster._plugin({
3337
- name: pluginName,
3338
- instance: {
3339
- /**
3340
- * Defaults are provided as a function for an easy override by inheritance
3341
- *
3342
- * @return {object} An object with the defaults options
3343
- * @private
3344
- */
3345
- __defaults: function() {
3346
-
3347
- return {
3348
- // if the tooltip should display an arrow that points to the origin
3349
- arrow: true,
3350
- // the distance in pixels between the tooltip and the origin
3351
- distance: 6,
3352
- // allows to easily change the position of the tooltip
3353
- functionPosition: null,
3354
- maxWidth: null,
3355
- // used to accomodate the arrow of tooltip if there is one.
3356
- // First to make sure that the arrow target is not too close
3357
- // to the edge of the tooltip, so the arrow does not overflow
3358
- // the tooltip. Secondly when we reposition the tooltip to
3359
- // make sure that it's positioned in such a way that the arrow is
3360
- // still pointing at the target (and not a few pixels beyond it).
3361
- // It should be equal to or greater than half the width of
3362
- // the arrow (by width we mean the size of the side which touches
3363
- // the side of the tooltip).
3364
- minIntersection: 16,
3365
- minWidth: 0,
3366
- // deprecated in 4.0.0. Listed for _optionsExtract to pick it up
3367
- position: null,
3368
- side: 'top',
3369
- // set to false to position the tooltip relatively to the document rather
3370
- // than the window when we open it
3371
- viewportAware: true
3372
- };
3373
- },
3374
-
3375
- /**
3376
- * Run once: at instantiation of the plugin
3377
- *
3378
- * @param {object} instance The tooltipster object that instantiated this plugin
3379
- * @private
3380
- */
3381
- __init: function(instance) {
3382
-
3383
- var self = this;
3384
-
3385
- // list of instance variables
3386
-
3387
- self.__instance = instance;
3388
- self.__namespace = 'tooltipster-sideTip-'+ Math.round(Math.random()*1000000);
3389
- self.__previousState = 'closed';
3390
- self.__options;
3391
-
3392
- // initial formatting
3393
- self.__optionsFormat();
3394
-
3395
- self.__instance._on('state.'+ self.__namespace, function(event) {
3396
-
3397
- if (event.state == 'closed') {
3398
- self.__close();
3399
- }
3400
- else if (event.state == 'appearing' && self.__previousState == 'closed') {
3401
- self.__create();
3402
- }
3403
-
3404
- self.__previousState = event.state;
3405
- });
3406
-
3407
- // reformat every time the options are changed
3408
- self.__instance._on('options.'+ self.__namespace, function() {
3409
- self.__optionsFormat();
3410
- });
3411
-
3412
- self.__instance._on('reposition.'+ self.__namespace, function(e) {
3413
- self.__reposition(e.event, e.helper);
3414
- });
3415
- },
3416
-
3417
- /**
3418
- * Called when the tooltip has closed
3419
- *
3420
- * @private
3421
- */
3422
- __close: function() {
3423
-
3424
- // detach our content object first, so the next jQuery's remove()
3425
- // call does not unbind its event handlers
3426
- if (this.__instance.content() instanceof $) {
3427
- this.__instance.content().detach();
3428
- }
3429
-
3430
- // remove the tooltip from the DOM
3431
- this.__instance._$tooltip.remove();
3432
- this.__instance._$tooltip = null;
3433
- },
3434
-
3435
- /**
3436
- * Creates the HTML element of the tooltip.
3437
- *
3438
- * @private
3439
- */
3440
- __create: function() {
3441
-
3442
- // note: we wrap with a .tooltipster-box div to be able to set a margin on it
3443
- // (.tooltipster-base must not have one)
3444
- var $html = $(
3445
- '<div class="tooltipster-base tooltipster-sidetip">' +
3446
- '<div class="tooltipster-box">' +
3447
- '<div class="tooltipster-content"></div>' +
3448
- '</div>' +
3449
- '<div class="tooltipster-arrow">' +
3450
- '<div class="tooltipster-arrow-uncropped">' +
3451
- '<div class="tooltipster-arrow-border"></div>' +
3452
- '<div class="tooltipster-arrow-background"></div>' +
3453
- '</div>' +
3454
- '</div>' +
3455
- '</div>'
3456
- );
3457
-
3458
- // hide arrow if asked
3459
- if (!this.__options.arrow) {
3460
- $html
3461
- .find('.tooltipster-box')
3462
- .css('margin', 0)
3463
- .end()
3464
- .find('.tooltipster-arrow')
3465
- .hide();
3466
- }
3467
-
3468
- // apply min/max width if asked
3469
- if (this.__options.minWidth) {
3470
- $html.css('min-width', this.__options.minWidth + 'px');
3471
- }
3472
- if (this.__options.maxWidth) {
3473
- $html.css('max-width', this.__options.maxWidth + 'px');
3474
- }
3475
-
3476
- this.__instance._$tooltip = $html;
3477
-
3478
- // tell the instance that the tooltip element has been created
3479
- this.__instance._trigger('created');
3480
- },
3481
-
3482
- /**
3483
- * Used when the plugin is to be unplugged
3484
- *
3485
- * @private
3486
- */
3487
- __destroy: function() {
3488
- this.__instance._off('.'+ self.__namespace);
3489
- },
3490
-
3491
- /**
3492
- * (Re)compute this.__options from the options declared to the instance
3493
- *
3494
- * @private
3495
- */
3496
- __optionsFormat: function() {
3497
-
3498
- var self = this;
3499
-
3500
- // get the options
3501
- self.__options = self.__instance._optionsExtract(pluginName, self.__defaults());
3502
-
3503
- // for backward compatibility, deprecated in v4.0.0
3504
- if (self.__options.position) {
3505
- self.__options.side = self.__options.position;
3506
- }
3507
-
3508
- // options formatting
3509
-
3510
- // format distance as a four-cell array if it ain't one yet and then make
3511
- // it an object with top/bottom/left/right properties
3512
- if (typeof self.__options.distance != 'object') {
3513
- self.__options.distance = [self.__options.distance];
3514
- }
3515
- if (self.__options.distance.length < 4) {
3516
-
3517
- if (self.__options.distance[1] === undefined) self.__options.distance[1] = self.__options.distance[0];
3518
- if (self.__options.distance[2] === undefined) self.__options.distance[2] = self.__options.distance[0];
3519
- if (self.__options.distance[3] === undefined) self.__options.distance[3] = self.__options.distance[1];
3520
-
3521
- self.__options.distance = {
3522
- top: self.__options.distance[0],
3523
- right: self.__options.distance[1],
3524
- bottom: self.__options.distance[2],
3525
- left: self.__options.distance[3]
3526
- };
3527
- }
3528
-
3529
- // let's transform:
3530
- // 'top' into ['top', 'bottom', 'right', 'left']
3531
- // 'right' into ['right', 'left', 'top', 'bottom']
3532
- // 'bottom' into ['bottom', 'top', 'right', 'left']
3533
- // 'left' into ['left', 'right', 'top', 'bottom']
3534
- if (typeof self.__options.side == 'string') {
3535
-
3536
- var opposites = {
3537
- 'top': 'bottom',
3538
- 'right': 'left',
3539
- 'bottom': 'top',
3540
- 'left': 'right'
3541
- };
3542
-
3543
- self.__options.side = [self.__options.side, opposites[self.__options.side]];
3544
-
3545
- if (self.__options.side[0] == 'left' || self.__options.side[0] == 'right') {
3546
- self.__options.side.push('top', 'bottom');
3547
- }
3548
- else {
3549
- self.__options.side.push('right', 'left');
3550
- }
3551
- }
3552
-
3553
- // misc
3554
- // disable the arrow in IE6 unless the arrow option was explicitly set to true
3555
- if ( $.tooltipster._env.IE === 6
3556
- && self.__options.arrow !== true
3557
- ) {
3558
- self.__options.arrow = false;
3559
- }
3560
- },
3561
-
3562
- /**
3563
- * This method must compute and set the positioning properties of the
3564
- * tooltip (left, top, width, height, etc.). It must also make sure the
3565
- * tooltip is eventually appended to its parent (since the element may be
3566
- * detached from the DOM at the moment the method is called).
3567
- *
3568
- * We'll evaluate positioning scenarios to find which side can contain the
3569
- * tooltip in the best way. We'll consider things relatively to the window
3570
- * (unless the user asks not to), then to the document (if need be, or if the
3571
- * user explicitly requires the tests to run on the document). For each
3572
- * scenario, measures are taken, allowing us to know how well the tooltip
3573
- * is going to fit. After that, a sorting function will let us know what
3574
- * the best scenario is (we also allow the user to choose his favorite
3575
- * scenario by using an event).
3576
- *
3577
- * @param {object} helper An object that contains variables that plugin
3578
- * creators may find useful (see below)
3579
- * @param {object} helper.geo An object with many layout properties
3580
- * about objects of interest (window, document, origin). This should help
3581
- * plugin users compute the optimal position of the tooltip
3582
- * @private
3583
- */
3584
- __reposition: function(event, helper) {
3585
-
3586
- var self = this,
3587
- finalResult,
3588
- // to know where to put the tooltip, we need to know on which point
3589
- // of the x or y axis we should center it. That coordinate is the target
3590
- targets = self.__targetFind(helper),
3591
- testResults = [];
3592
-
3593
- // make sure the tooltip is detached while we make tests on a clone
3594
- self.__instance._$tooltip.detach();
3595
-
3596
- // we could actually provide the original element to the Ruler and
3597
- // not a clone, but it just feels right to keep it out of the
3598
- // machinery.
3599
- var $clone = self.__instance._$tooltip.clone(),
3600
- // start position tests session
3601
- ruler = $.tooltipster._getRuler($clone),
3602
- satisfied = false,
3603
- animation = self.__instance.option('animation');
3604
-
3605
- // an animation class could contain properties that distort the size
3606
- if (animation) {
3607
- $clone.removeClass('tooltipster-'+ animation);
3608
- }
3609
-
3610
- // start evaluating scenarios
3611
- $.each(['window', 'document'], function(i, container) {
3612
-
3613
- var takeTest = null;
3614
-
3615
- // let the user decide to keep on testing or not
3616
- self.__instance._trigger({
3617
- container: container,
3618
- helper: helper,
3619
- satisfied: satisfied,
3620
- takeTest: function(bool) {
3621
- takeTest = bool;
3622
- },
3623
- results: testResults,
3624
- type: 'positionTest'
3625
- });
3626
-
3627
- if ( takeTest == true
3628
- || ( takeTest != false
3629
- && satisfied == false
3630
- // skip the window scenarios if asked. If they are reintegrated by
3631
- // the callback of the positionTest event, they will have to be
3632
- // excluded using the callback of positionTested
3633
- && (container != 'window' || self.__options.viewportAware)
3634
- )
3635
- ) {
3636
-
3637
- // for each allowed side
3638
- for (var i=0; i < self.__options.side.length; i++) {
3639
-
3640
- var distance = {
3641
- horizontal: 0,
3642
- vertical: 0
3643
- },
3644
- side = self.__options.side[i];
3645
-
3646
- if (side == 'top' || side == 'bottom') {
3647
- distance.vertical = self.__options.distance[side];
3648
- }
3649
- else {
3650
- distance.horizontal = self.__options.distance[side];
3651
- }
3652
-
3653
- // this may have an effect on the size of the tooltip if there are css
3654
- // rules for the arrow or something else
3655
- self.__sideChange($clone, side);
3656
-
3657
- $.each(['natural', 'constrained'], function(i, mode) {
3658
-
3659
- takeTest = null;
3660
-
3661
- // emit an event on the instance
3662
- self.__instance._trigger({
3663
- container: container,
3664
- event: event,
3665
- helper: helper,
3666
- mode: mode,
3667
- results: testResults,
3668
- satisfied: satisfied,
3669
- side: side,
3670
- takeTest: function(bool) {
3671
- takeTest = bool;
3672
- },
3673
- type: 'positionTest'
3674
- });
3675
-
3676
- if ( takeTest == true
3677
- || ( takeTest != false
3678
- && satisfied == false
3679
- )
3680
- ) {
3681
-
3682
- var testResult = {
3683
- container: container,
3684
- // we let the distance as an object here, it can make things a little easier
3685
- // during the user's calculations at positionTest/positionTested
3686
- distance: distance,
3687
- // whether the tooltip can fit in the size of the viewport (does not mean
3688
- // that we'll be able to make it initially entirely visible, see 'whole')
3689
- fits: null,
3690
- mode: mode,
3691
- outerSize: null,
3692
- side: side,
3693
- size: null,
3694
- target: targets[side],
3695
- // check if the origin has enough surface on screen for the tooltip to
3696
- // aim at it without overflowing the viewport (this is due to the thickness
3697
- // of the arrow represented by the minIntersection length).
3698
- // If not, the tooltip will have to be partly or entirely off screen in
3699
- // order to stay docked to the origin. This value will stay null when the
3700
- // container is the document, as it is not relevant
3701
- whole: null
3702
- };
3703
-
3704
- // get the size of the tooltip with or without size constraints
3705
- var rulerConfigured = (mode == 'natural') ?
3706
- ruler.free() :
3707
- ruler.constrain(
3708
- helper.geo.available[container][side].width - distance.horizontal,
3709
- helper.geo.available[container][side].height - distance.vertical
3710
- ),
3711
- rulerResults = rulerConfigured.measure();
3712
-
3713
- testResult.size = rulerResults.size;
3714
- testResult.outerSize = {
3715
- height: rulerResults.size.height + distance.vertical,
3716
- width: rulerResults.size.width + distance.horizontal
3717
- };
3718
-
3719
- if (mode == 'natural') {
3720
-
3721
- if( helper.geo.available[container][side].width >= testResult.outerSize.width
3722
- && helper.geo.available[container][side].height >= testResult.outerSize.height
3723
- ) {
3724
- testResult.fits = true;
3725
- }
3726
- else {
3727
- testResult.fits = false;
3728
- }
3729
- }
3730
- else {
3731
- testResult.fits = rulerResults.fits;
3732
- }
3733
-
3734
- if (container == 'window') {
3735
-
3736
- if (!testResult.fits) {
3737
- testResult.whole = false;
3738
- }
3739
- else {
3740
- if (side == 'top' || side == 'bottom') {
3741
-
3742
- testResult.whole = (
3743
- helper.geo.origin.windowOffset.right >= self.__options.minIntersection
3744
- && helper.geo.window.size.width - helper.geo.origin.windowOffset.left >= self.__options.minIntersection
3745
- );
3746
- }
3747
- else {
3748
- testResult.whole = (
3749
- helper.geo.origin.windowOffset.bottom >= self.__options.minIntersection
3750
- && helper.geo.window.size.height - helper.geo.origin.windowOffset.top >= self.__options.minIntersection
3751
- );
3752
- }
3753
- }
3754
- }
3755
-
3756
- testResults.push(testResult);
3757
-
3758
- // we don't need to compute more positions if we have one fully on screen
3759
- if (testResult.whole) {
3760
- satisfied = true;
3761
- }
3762
- else {
3763
- // don't run the constrained test unless the natural width was greater
3764
- // than the available width, otherwise it's pointless as we know it
3765
- // wouldn't fit either
3766
- if ( testResult.mode == 'natural'
3767
- && ( testResult.fits
3768
- || testResult.size.width <= helper.geo.available[container][side].width
3769
- )
3770
- ) {
3771
- return false;
3772
- }
3773
- }
3774
- }
3775
- });
3776
- }
3777
- }
3778
- });
3779
-
3780
- // the user may eliminate the unwanted scenarios from testResults, but he's
3781
- // not supposed to alter them at this point. functionPosition and the
3782
- // position event serve that purpose.
3783
- self.__instance._trigger({
3784
- edit: function(r) {
3785
- testResults = r;
3786
- },
3787
- event: event,
3788
- helper: helper,
3789
- results: testResults,
3790
- type: 'positionTested'
3791
- });
3792
-
3793
- /**
3794
- * Sort the scenarios to find the favorite one.
3795
- *
3796
- * The favorite scenario is when we can fully display the tooltip on screen,
3797
- * even if it means that the middle of the tooltip is no longer centered on
3798
- * the middle of the origin (when the origin is near the edge of the screen
3799
- * or even partly off screen). We want the tooltip on the preferred side,
3800
- * even if it means that we have to use a constrained size rather than a
3801
- * natural one (as long as it fits). When the origin is off screen at the top
3802
- * the tooltip will be positioned at the bottom (if allowed), if the origin
3803
- * is off screen on the right, it will be positioned on the left, etc.
3804
- * If there are no scenarios where the tooltip can fit on screen, or if the
3805
- * user does not want the tooltip to fit on screen (viewportAware == false),
3806
- * we fall back to the scenarios relative to the document.
3807
- *
3808
- * When the tooltip is bigger than the viewport in either dimension, we stop
3809
- * looking at the window scenarios and consider the document scenarios only,
3810
- * with the same logic to find on which side it would fit best.
3811
- *
3812
- * If the tooltip cannot fit the document on any side, we force it at the
3813
- * bottom, so at least the user can scroll to see it.
3814
- */
3815
- testResults.sort(function(a, b) {
3816
-
3817
- // best if it's whole (the tooltip fits and adapts to the viewport)
3818
- if (a.whole && !b.whole) {
3819
- return -1;
3820
- }
3821
- else if (!a.whole && b.whole) {
3822
- return 1;
3823
- }
3824
- else if (a.whole && b.whole) {
3825
-
3826
- var ai = self.__options.side.indexOf(a.side),
3827
- bi = self.__options.side.indexOf(b.side);
3828
-
3829
- // use the user's sides fallback array
3830
- if (ai < bi) {
3831
- return -1;
3832
- }
3833
- else if (ai > bi) {
3834
- return 1;
3835
- }
3836
- else {
3837
- // will be used if the user forced the tests to continue
3838
- return a.mode == 'natural' ? -1 : 1;
3839
- }
3840
- }
3841
- else {
3842
-
3843
- // better if it fits
3844
- if (a.fits && !b.fits) {
3845
- return -1;
3846
- }
3847
- else if (!a.fits && b.fits) {
3848
- return 1;
3849
- }
3850
- else if (a.fits && b.fits) {
3851
-
3852
- var ai = self.__options.side.indexOf(a.side),
3853
- bi = self.__options.side.indexOf(b.side);
3854
-
3855
- // use the user's sides fallback array
3856
- if (ai < bi) {
3857
- return -1;
3858
- }
3859
- else if (ai > bi) {
3860
- return 1;
3861
- }
3862
- else {
3863
- // will be used if the user forced the tests to continue
3864
- return a.mode == 'natural' ? -1 : 1;
3865
- }
3866
- }
3867
- else {
3868
-
3869
- // if everything failed, this will give a preference to the case where
3870
- // the tooltip overflows the document at the bottom
3871
- if ( a.container == 'document'
3872
- && a.side == 'bottom'
3873
- && a.mode == 'natural'
3874
- ) {
3875
- return -1;
3876
- }
3877
- else {
3878
- return 1;
3879
- }
3880
- }
3881
- }
3882
- });
3883
-
3884
- finalResult = testResults[0];
3885
-
3886
-
3887
- // now let's find the coordinates of the tooltip relatively to the window
3888
- finalResult.coord = {};
3889
-
3890
- switch (finalResult.side) {
3891
-
3892
- case 'left':
3893
- case 'right':
3894
- finalResult.coord.top = Math.floor(finalResult.target - finalResult.size.height / 2);
3895
- break;
3896
-
3897
- case 'bottom':
3898
- case 'top':
3899
- finalResult.coord.left = Math.floor(finalResult.target - finalResult.size.width / 2);
3900
- break;
3901
- }
3902
-
3903
- switch (finalResult.side) {
3904
-
3905
- case 'left':
3906
- finalResult.coord.left = helper.geo.origin.windowOffset.left - finalResult.outerSize.width;
3907
- break;
3908
-
3909
- case 'right':
3910
- finalResult.coord.left = helper.geo.origin.windowOffset.right + finalResult.distance.horizontal;
3911
- break;
3912
-
3913
- case 'top':
3914
- finalResult.coord.top = helper.geo.origin.windowOffset.top - finalResult.outerSize.height;
3915
- break;
3916
-
3917
- case 'bottom':
3918
- finalResult.coord.top = helper.geo.origin.windowOffset.bottom + finalResult.distance.vertical;
3919
- break;
3920
- }
3921
-
3922
- // if the tooltip can potentially be contained within the viewport dimensions
3923
- // and that we are asked to make it fit on screen
3924
- if (finalResult.container == 'window') {
3925
-
3926
- // if the tooltip overflows the viewport, we'll move it accordingly (then it will
3927
- // not be centered on the middle of the origin anymore). We only move horizontally
3928
- // for top and bottom tooltips and vice versa.
3929
- if (finalResult.side == 'top' || finalResult.side == 'bottom') {
3930
-
3931
- // if there is an overflow on the left
3932
- if (finalResult.coord.left < 0) {
3933
-
3934
- // prevent the overflow unless the origin itself gets off screen (minus the
3935
- // margin needed to keep the arrow pointing at the target)
3936
- if (helper.geo.origin.windowOffset.right - this.__options.minIntersection >= 0) {
3937
- finalResult.coord.left = 0;
3938
- }
3939
- else {
3940
- finalResult.coord.left = helper.geo.origin.windowOffset.right - this.__options.minIntersection - 1;
3941
- }
3942
- }
3943
- // or an overflow on the right
3944
- else if (finalResult.coord.left > helper.geo.window.size.width - finalResult.size.width) {
3945
-
3946
- if (helper.geo.origin.windowOffset.left + this.__options.minIntersection <= helper.geo.window.size.width) {
3947
- finalResult.coord.left = helper.geo.window.size.width - finalResult.size.width;
3948
- }
3949
- else {
3950
- finalResult.coord.left = helper.geo.origin.windowOffset.left + this.__options.minIntersection + 1 - finalResult.size.width;
3951
- }
3952
- }
3953
- }
3954
- else {
3955
-
3956
- // overflow at the top
3957
- if (finalResult.coord.top < 0) {
3958
-
3959
- if (helper.geo.origin.windowOffset.bottom - this.__options.minIntersection >= 0) {
3960
- finalResult.coord.top = 0;
3961
- }
3962
- else {
3963
- finalResult.coord.top = helper.geo.origin.windowOffset.bottom - this.__options.minIntersection - 1;
3964
- }
3965
- }
3966
- // or at the bottom
3967
- else if (finalResult.coord.top > helper.geo.window.size.height - finalResult.size.height) {
3968
-
3969
- if (helper.geo.origin.windowOffset.top + this.__options.minIntersection <= helper.geo.window.size.height) {
3970
- finalResult.coord.top = helper.geo.window.size.height - finalResult.size.height;
3971
- }
3972
- else {
3973
- finalResult.coord.top = helper.geo.origin.windowOffset.top + this.__options.minIntersection + 1 - finalResult.size.height;
3974
- }
3975
- }
3976
- }
3977
- }
3978
- else {
3979
-
3980
- // there might be overflow here too but it's easier to handle. If there has
3981
- // to be an overflow, we'll make sure it's on the right side of the screen
3982
- // (because the browser will extend the document size if there is an overflow
3983
- // on the right, but not on the left). The sort function above has already
3984
- // made sure that a bottom document overflow is preferred to a top overflow,
3985
- // so we don't have to care about it.
3986
-
3987
- // if there is an overflow on the right
3988
- if (finalResult.coord.left > helper.geo.window.size.width - finalResult.size.width) {
3989
-
3990
- // this may actually create on overflow on the left but we'll fix it in a sec
3991
- finalResult.coord.left = helper.geo.window.size.width - finalResult.size.width;
3992
- }
3993
-
3994
- // if there is an overflow on the left
3995
- if (finalResult.coord.left < 0) {
3996
-
3997
- // don't care if it overflows the right after that, we made our best
3998
- finalResult.coord.left = 0;
3999
- }
4000
- }
4001
-
4002
-
4003
- // submit the positioning proposal to the user function which may choose to change
4004
- // the side, size and/or the coordinates
4005
-
4006
- // first, set the rules that corresponds to the proposed side: it may change
4007
- // the size of the tooltip, and the custom functionPosition may want to detect the
4008
- // size of something before making a decision. So let's make things easier for the
4009
- // implementor
4010
- self.__sideChange($clone, finalResult.side);
4011
-
4012
- // add some variables to the helper
4013
- helper.tooltipClone = $clone[0];
4014
- helper.tooltipParent = self.__instance.option('parent').parent[0];
4015
- // move informative values to the helper
4016
- helper.mode = finalResult.mode;
4017
- helper.whole = finalResult.whole;
4018
- // add some variables to the helper for the functionPosition callback (these
4019
- // will also be added to the event fired by self.__instance._trigger but that's
4020
- // ok, we're just being consistent)
4021
- helper.origin = self.__instance._$origin[0];
4022
- helper.tooltip = self.__instance._$tooltip[0];
4023
-
4024
- // leave only the actionable values in there for functionPosition
4025
- delete finalResult.container;
4026
- delete finalResult.fits;
4027
- delete finalResult.mode;
4028
- delete finalResult.outerSize;
4029
- delete finalResult.whole;
4030
-
4031
- // keep only the distance on the relevant side, for clarity
4032
- finalResult.distance = finalResult.distance.horizontal || finalResult.distance.vertical;
4033
-
4034
- // beginners may not be comfortable with the concept of editing the object
4035
- // passed by reference, so we provide an edit function and pass a clone
4036
- var finalResultClone = $.extend(true, {}, finalResult);
4037
-
4038
- // emit an event on the instance
4039
- self.__instance._trigger({
4040
- edit: function(result) {
4041
- finalResult = result;
4042
- },
4043
- event: event,
4044
- helper: helper,
4045
- position: finalResultClone,
4046
- type: 'position'
4047
- });
4048
-
4049
- if (self.__options.functionPosition) {
4050
-
4051
- var result = self.__options.functionPosition.call(self, self.__instance, helper, finalResultClone);
4052
-
4053
- if (result) finalResult = result;
4054
- }
4055
-
4056
- // end the positioning tests session (the user might have had a
4057
- // use for it during the position event, now it's over)
4058
- ruler.destroy();
4059
-
4060
- // compute the position of the target relatively to the tooltip root
4061
- // element so we can place the arrow and make the needed adjustments
4062
- var arrowCoord,
4063
- maxVal;
4064
-
4065
- if (finalResult.side == 'top' || finalResult.side == 'bottom') {
4066
-
4067
- arrowCoord = {
4068
- prop: 'left',
4069
- val: finalResult.target - finalResult.coord.left
4070
- };
4071
- maxVal = finalResult.size.width - this.__options.minIntersection;
4072
- }
4073
- else {
4074
-
4075
- arrowCoord = {
4076
- prop: 'top',
4077
- val: finalResult.target - finalResult.coord.top
4078
- };
4079
- maxVal = finalResult.size.height - this.__options.minIntersection;
4080
- }
4081
-
4082
- // cannot lie beyond the boundaries of the tooltip, minus the
4083
- // arrow margin
4084
- if (arrowCoord.val < this.__options.minIntersection) {
4085
- arrowCoord.val = this.__options.minIntersection;
4086
- }
4087
- else if (arrowCoord.val > maxVal) {
4088
- arrowCoord.val = maxVal;
4089
- }
4090
-
4091
- var originParentOffset;
4092
-
4093
- // let's convert the window-relative coordinates into coordinates relative to the
4094
- // future positioned parent that the tooltip will be appended to
4095
- if (helper.geo.origin.fixedLineage) {
4096
-
4097
- // same as windowOffset when the position is fixed
4098
- originParentOffset = helper.geo.origin.windowOffset;
4099
- }
4100
- else {
4101
-
4102
- // this assumes that the parent of the tooltip is located at
4103
- // (0, 0) in the document, typically like when the parent is
4104
- // <body>.
4105
- // If we ever allow other types of parent, .tooltipster-ruler
4106
- // will have to be appended to the parent to inherit css style
4107
- // values that affect the display of the text and such.
4108
- originParentOffset = {
4109
- left: helper.geo.origin.windowOffset.left + helper.geo.window.scroll.left,
4110
- top: helper.geo.origin.windowOffset.top + helper.geo.window.scroll.top
4111
- };
4112
- }
4113
-
4114
- finalResult.coord = {
4115
- left: originParentOffset.left + (finalResult.coord.left - helper.geo.origin.windowOffset.left),
4116
- top: originParentOffset.top + (finalResult.coord.top - helper.geo.origin.windowOffset.top)
4117
- };
4118
-
4119
- // set position values on the original tooltip element
4120
-
4121
- self.__sideChange(self.__instance._$tooltip, finalResult.side);
4122
-
4123
- if (helper.geo.origin.fixedLineage) {
4124
- self.__instance._$tooltip
4125
- .css('position', 'fixed');
4126
- }
4127
- else {
4128
- // CSS default
4129
- self.__instance._$tooltip
4130
- .css('position', '');
4131
- }
4132
-
4133
- self.__instance._$tooltip
4134
- .css({
4135
- left: finalResult.coord.left,
4136
- top: finalResult.coord.top,
4137
- // we need to set a size even if the tooltip is in its natural size
4138
- // because when the tooltip is positioned beyond the width of the body
4139
- // (which is by default the width of the window; it will happen when
4140
- // you scroll the window horizontally to get to the origin), its text
4141
- // content will otherwise break lines at each word to keep up with the
4142
- // body overflow strategy.
4143
- height: finalResult.size.height,
4144
- width: finalResult.size.width
4145
- })
4146
- .find('.tooltipster-arrow')
4147
- .css({
4148
- 'left': '',
4149
- 'top': ''
4150
- })
4151
- .css(arrowCoord.prop, arrowCoord.val);
4152
-
4153
- // append the tooltip HTML element to its parent
4154
- self.__instance._$tooltip.appendTo(self.__instance.option('parent'));
4155
-
4156
- self.__instance._trigger({
4157
- type: 'repositioned',
4158
- event: event,
4159
- position: finalResult
4160
- });
4161
- },
4162
-
4163
- /**
4164
- * Make whatever modifications are needed when the side is changed. This has
4165
- * been made an independant method for easy inheritance in custom plugins based
4166
- * on this default plugin.
4167
- *
4168
- * @param {object} $obj
4169
- * @param {string} side
4170
- * @private
4171
- */
4172
- __sideChange: function($obj, side) {
4173
-
4174
- $obj
4175
- .removeClass('tooltipster-bottom')
4176
- .removeClass('tooltipster-left')
4177
- .removeClass('tooltipster-right')
4178
- .removeClass('tooltipster-top')
4179
- .addClass('tooltipster-'+ side);
4180
- },
4181
-
4182
- /**
4183
- * Returns the target that the tooltip should aim at for a given side.
4184
- * The calculated value is a distance from the edge of the window
4185
- * (left edge for top/bottom sides, top edge for left/right side). The
4186
- * tooltip will be centered on that position and the arrow will be
4187
- * positioned there (as much as possible).
4188
- *
4189
- * @param {object} helper
4190
- * @return {integer}
4191
- * @private
4192
- */
4193
- __targetFind: function(helper) {
4194
-
4195
- var target = {},
4196
- rects = this.__instance._$origin[0].getClientRects();
4197
-
4198
- // these lines fix a Chrome bug (issue #491)
4199
- if (rects.length > 1) {
4200
- var opacity = this.__instance._$origin.css('opacity');
4201
- if(opacity == 1) {
4202
- this.__instance._$origin.css('opacity', 0.99);
4203
- rects = this.__instance._$origin[0].getClientRects();
4204
- this.__instance._$origin.css('opacity', 1);
4205
- }
4206
- }
4207
-
4208
- // by default, the target will be the middle of the origin
4209
- if (rects.length < 2) {
4210
-
4211
- target.top = Math.floor(helper.geo.origin.windowOffset.left + (helper.geo.origin.size.width / 2));
4212
- target.bottom = target.top;
4213
-
4214
- target.left = Math.floor(helper.geo.origin.windowOffset.top + (helper.geo.origin.size.height / 2));
4215
- target.right = target.left;
4216
- }
4217
- // if multiple client rects exist, the element may be text split
4218
- // up into multiple lines and the middle of the origin may not be
4219
- // best option anymore. We need to choose the best target client rect
4220
- else {
4221
-
4222
- // top: the first
4223
- var targetRect = rects[0];
4224
- target.top = Math.floor(targetRect.left + (targetRect.right - targetRect.left) / 2);
4225
-
4226
- // right: the middle line, rounded down in case there is an even
4227
- // number of lines (looks more centered => check out the
4228
- // demo with 4 split lines)
4229
- if (rects.length > 2) {
4230
- targetRect = rects[Math.ceil(rects.length / 2) - 1];
4231
- }
4232
- else {
4233
- targetRect = rects[0];
4234
- }
4235
- target.right = Math.floor(targetRect.top + (targetRect.bottom - targetRect.top) / 2);
4236
-
4237
- // bottom: the last
4238
- targetRect = rects[rects.length - 1];
4239
- target.bottom = Math.floor(targetRect.left + (targetRect.right - targetRect.left) / 2);
4240
-
4241
- // left: the middle line, rounded up
4242
- if (rects.length > 2) {
4243
- targetRect = rects[Math.ceil((rects.length + 1) / 2) - 1];
4244
- }
4245
- else {
4246
- targetRect = rects[rects.length - 1];
4247
- }
4248
-
4249
- target.left = Math.floor(targetRect.top + (targetRect.bottom - targetRect.top) / 2);
4250
- }
4251
-
4252
- return target;
4253
- }
4254
- }
4255
- });
4256
-
4257
- /* a build task will add "return $;" here */
4258
- return $;
4259
-
4260
- }));