jquery-qtip2-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jquery-qtip2-rails.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Tanguy Krotoff
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # jQuery qTip2 for Rails
2
+
3
+ qTip2 packaged for the Rails 3.1+ asset pipeline.
4
+ Check [qTip2 project home page](http://craigsworks.com/projects/qtip2/).
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'jquery-qtip2-rails'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install jquery-qtip2-rails
19
+
20
+ ## Usage
21
+
22
+ The use of qTip2 can be [customized](http://craigsworks.com/projects/qtip2/download/) to fit your own needs.
23
+ This gem works in a similar manner.
24
+
25
+ ### "Full" version of qTip2
26
+
27
+ The full version of qTip2 includes all its plugins. This is the default and conservative choice and
28
+ I recommend you to use this version.
29
+
30
+ Add the following JavaScript file to `app/assets/javascripts/application.js`:
31
+
32
+ //= require jquery.qtip.js
33
+
34
+ Add the following stylesheet file to `app/assets/stylesheets/application.css`:
35
+
36
+ *= require jquery.qtip.css
37
+
38
+ ### Basic version of qTip2
39
+
40
+ The basic version of qTip2 does not include any plugin.
41
+
42
+ File `app/assets/javascripts/application.js`:
43
+
44
+ //= require jquery.qtip.basic.js
45
+
46
+ ### Customized version of qTip2
47
+
48
+ You can also create your own `app/assets/javascripts/jquery.qtip.js` file:
49
+
50
+ //= include ./jquery-qtip/header.txt
51
+ //= include ./jquery-qtip/intro.js
52
+ //= include ./jquery-qtip/core.js
53
+ //= include ./jquery-qtip/ajax/ajax.js
54
+ //= include ./jquery-qtip/bgiframe/bgiframe.js
55
+ //= include ./jquery-qtip/imagemap/imagemap.js
56
+ //= include ./jquery-qtip/modal/modal.js
57
+ //= include ./jquery-qtip/svg/svg.js
58
+ //= include ./jquery-qtip/tips/tips.js
59
+ //= include ./jquery-qtip/outro.js
60
+
61
+ And `app/assets/stylesheets/jquery.qtip.css` file:
62
+
63
+ /*
64
+ *= include ./jquery-qtip/header.txt
65
+ *= include ./jquery-qtip/core.css
66
+ *= include ./jquery-qtip/modal/modal.css
67
+ *= include ./jquery-qtip/tips/tips.css
68
+ *= include ./jquery-qtip/styles.css
69
+ *= include ./jquery-qtip/extra.css
70
+ */
71
+
72
+ This allows you to enable only the plugins that you need and thus reduce qTip2 size.
73
+ This is possible thanks to the asset pipeline preprocessor `include` directive.
74
+ Files `jquery.qtip.js` and `jquery.qtip.basic.js` provided with this gem work the exact same way.
75
+
76
+ # Minification
77
+
78
+ This gem does not include minified and compressed version of qTip2 since there is no need of.
79
+ This is done automatically by [Rails asset pipeline](http://guides.rubyonrails.org/asset_pipeline.html).
80
+
81
+ # License
82
+
83
+ qTip2 is being developed by [Craig Thompson](http://craigsworks.com/) and is dual-licensed
84
+ under the open source [MIT](http://en.wikipedia.org/wiki/MIT_License) and
85
+ [GPLv2](http://en.wikipedia.org/wiki/MIT_License) licenses.
86
+
87
+ This gem is licensed under [MIT license](https://raw.github.com/tkrotoff/jquery-qtip2-rails/master/LICENSE).
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/jquery-qtip2-rails/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Tanguy Krotoff (jQuery plugin by Craig Thompson)"]
6
+ gem.email = ["tkrotoff@gmail.com"]
7
+ gem.description = %q{qTip2 jQuery plugin}
8
+ gem.summary = %q{qTip2 packaged for the Rails 3.1+ asset pipeline}
9
+ gem.homepage = "http://tkrotoff.github.com/jquery-qtip2-rails/"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "jquery-qtip2-rails"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Jquery::Qtip2::Rails::VERSION
17
+
18
+ gem.add_dependency "railties", ">= 3.1.0"
19
+ end
@@ -0,0 +1,10 @@
1
+ require "jquery-qtip2-rails/version"
2
+
3
+ module Jquery
4
+ module Qtip2
5
+ module Rails
6
+ class Engine < ::Rails::Engine
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module Jquery
2
+ module Qtip2
3
+ module Rails
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,167 @@
1
+ function Ajax(api)
2
+ {
3
+ var self = this,
4
+ tooltip = api.elements.tooltip,
5
+ opts = api.options.content.ajax,
6
+ defaults = QTIP.defaults.content.ajax,
7
+ namespace = '.qtip-ajax',
8
+ rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
9
+ first = TRUE,
10
+ stop = FALSE,
11
+ xhr;
12
+
13
+ api.checks.ajax = {
14
+ '^content.ajax': function(obj, name, v) {
15
+ // If content.ajax object was reset, set our local var
16
+ if(name === 'ajax') { opts = v; }
17
+
18
+ if(name === 'once') {
19
+ self.init();
20
+ }
21
+ else if(opts && opts.url) {
22
+ self.load();
23
+ }
24
+ else {
25
+ tooltip.unbind(namespace);
26
+ }
27
+ }
28
+ };
29
+
30
+ $.extend(self, {
31
+ init: function() {
32
+ // Make sure ajax options are enabled and bind event
33
+ if(opts && opts.url) {
34
+ tooltip.unbind(namespace)[ opts.once ? 'one' : 'bind' ]('tooltipshow'+namespace, self.load);
35
+ }
36
+
37
+ return self;
38
+ },
39
+
40
+ load: function(event) {
41
+ if(stop) {stop = FALSE; return; }
42
+
43
+ var hasSelector = opts.url.indexOf(' '),
44
+ url = opts.url,
45
+ selector,
46
+ hideFirst = !opts.loading && first;
47
+
48
+ // If loading option is disabled, prevent the tooltip showing until we've completed the request
49
+ if(hideFirst) { try{ event.preventDefault(); } catch(e) {} }
50
+
51
+ // Make sure default event hasn't been prevented
52
+ else if(event && event.isDefaultPrevented()) { return self; }
53
+
54
+ // Cancel old request
55
+ if(xhr && xhr.abort) { xhr.abort(); }
56
+
57
+ // Check if user delcared a content selector like in .load()
58
+ if(hasSelector > -1) {
59
+ selector = url.substr(hasSelector);
60
+ url = url.substr(0, hasSelector);
61
+ }
62
+
63
+ // Define common after callback for both success/error handlers
64
+ function after() {
65
+ var complete;
66
+
67
+ // Don't proceed if tooltip is destroyed
68
+ if(api.destroyed) { return; }
69
+
70
+ // Set first flag to false
71
+ first = FALSE;
72
+
73
+ // Re-display tip if loading and first time, and reset first flag
74
+ if(hideFirst) { stop = TRUE; api.show(event.originalEvent); }
75
+
76
+ // Call users complete method if it was defined
77
+ if((complete = defaults.complete || opts.complete) && $.isFunction(complete)) {
78
+ complete.apply(opts.context || api, arguments);
79
+ }
80
+ }
81
+
82
+ // Define success handler
83
+ function successHandler(content, status, jqXHR) {
84
+ var success;
85
+
86
+ // Don't proceed if tooltip is destroyed
87
+ if(api.destroyed) { return; }
88
+
89
+ if(selector) {
90
+ // Create a dummy div to hold the results and grab the selector element
91
+ content = $('<div/>')
92
+ // inject the contents of the document in, removing the scripts
93
+ // to avoid any 'Permission Denied' errors in IE
94
+ .append(content.replace(rscript, ""))
95
+
96
+ // Locate the specified elements
97
+ .find(selector);
98
+ }
99
+
100
+ // Call the success function if one is defined
101
+ if((success = defaults.success || opts.success) && $.isFunction(success)) {
102
+ success.call(opts.context || api, content, status, jqXHR);
103
+ }
104
+
105
+ // Otherwise set the content
106
+ else { api.set('content.text', content); }
107
+ }
108
+
109
+ // Error handler
110
+ function errorHandler(xhr, status, error) {
111
+ if(api.destroyed || xhr.status === 0) { return; }
112
+ api.set('content.text', status + ': ' + error);
113
+ }
114
+
115
+ // Setup $.ajax option object and process the request
116
+ xhr = $.ajax(
117
+ $.extend({
118
+ error: defaults.error || errorHandler,
119
+ context: api
120
+ },
121
+ opts, { url: url, success: successHandler, complete: after })
122
+ );
123
+ },
124
+
125
+ destroy: function() {
126
+ // Cancel ajax request if possible
127
+ if(xhr && xhr.abort) { xhr.abort(); }
128
+
129
+ // Set api.destroyed flag
130
+ api.destroyed = TRUE;
131
+ }
132
+ });
133
+
134
+ self.init();
135
+ }
136
+
137
+
138
+ PLUGINS.ajax = function(api)
139
+ {
140
+ var self = api.plugins.ajax;
141
+
142
+ return 'object' === typeof self ? self : (api.plugins.ajax = new Ajax(api));
143
+ };
144
+
145
+ PLUGINS.ajax.initialize = 'render';
146
+
147
+ // Setup plugin sanitization
148
+ PLUGINS.ajax.sanitize = function(options)
149
+ {
150
+ var content = options.content, opts;
151
+ if(content && 'ajax' in content) {
152
+ opts = content.ajax;
153
+ if(typeof opts !== 'object') { opts = options.content.ajax = { url: opts }; }
154
+ if('boolean' !== typeof opts.once && opts.once) { opts.once = !!opts.once; }
155
+ }
156
+ };
157
+
158
+ // Extend original api defaults
159
+ $.extend(TRUE, QTIP.defaults, {
160
+ content: {
161
+ ajax: {
162
+ loading: TRUE,
163
+ once: TRUE
164
+ }
165
+ }
166
+ });
167
+
@@ -0,0 +1,76 @@
1
+ /*
2
+ * BGIFrame adaption (http://plugins.jquery.com/project/bgiframe)
3
+ * Special thanks to Brandon Aaron
4
+ */
5
+ function BGIFrame(api)
6
+ {
7
+ var self = this,
8
+ elems = api.elements,
9
+ tooltip = elems.tooltip,
10
+ namespace = '.bgiframe-' + api.id;
11
+
12
+ $.extend(self, {
13
+ init: function()
14
+ {
15
+ // Create the BGIFrame element
16
+ elems.bgiframe = $('<iframe class="ui-tooltip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" ' +
17
+ ' style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); ' +
18
+ '-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>');
19
+
20
+ // Append the new element to the tooltip
21
+ elems.bgiframe.appendTo(tooltip);
22
+
23
+ // Update BGIFrame on tooltip move
24
+ tooltip.bind('tooltipmove'+namespace, self.adjust);
25
+ },
26
+
27
+ adjust: function()
28
+ {
29
+ var dimensions = api.get('dimensions'), // Determine current tooltip dimensions
30
+ plugin = api.plugins.tip,
31
+ tip = elems.tip,
32
+ tipAdjust, offset;
33
+
34
+ // Adjust border offset
35
+ offset = parseInt(tooltip.css('border-left-width'), 10) || 0;
36
+ offset = { left: -offset, top: -offset };
37
+
38
+ // Adjust for tips plugin
39
+ if(plugin && tip) {
40
+ tipAdjust = (plugin.corner.precedance === 'x') ? ['width', 'left'] : ['height', 'top'];
41
+ offset[ tipAdjust[1] ] -= tip[ tipAdjust[0] ]();
42
+ }
43
+
44
+ // Update bgiframe
45
+ elems.bgiframe.css(offset).css(dimensions);
46
+ },
47
+
48
+ destroy: function()
49
+ {
50
+ // Remove iframe
51
+ elems.bgiframe.remove();
52
+
53
+ // Remove bound events
54
+ tooltip.unbind(namespace);
55
+ }
56
+ });
57
+
58
+ self.init();
59
+ }
60
+
61
+ PLUGINS.bgiframe = function(api)
62
+ {
63
+ var browser = $.browser,
64
+ self = api.plugins.bgiframe;
65
+
66
+ // Proceed only if the browser is IE6 and offending elements are present
67
+ if($('select, object').length < 1 || !(browser.msie && (''+browser.version).charAt(0) === '6')) {
68
+ return FALSE;
69
+ }
70
+
71
+ return 'object' === typeof self ? self : (api.plugins.bgiframe = new BGIFrame(api));
72
+ };
73
+
74
+ // Plugin needs to be initialized on render
75
+ PLUGINS.bgiframe.initialize = 'render';
76
+
@@ -0,0 +1,1923 @@
1
+ // Option object sanitizer
2
+ function sanitizeOptions(opts)
3
+ {
4
+ var content;
5
+
6
+ if(!opts || 'object' !== typeof opts) { return FALSE; }
7
+
8
+ if(opts.metadata === NULL || 'object' !== typeof opts.metadata) {
9
+ opts.metadata = {
10
+ type: opts.metadata
11
+ };
12
+ }
13
+
14
+ if('content' in opts) {
15
+ if(opts.content === NULL || 'object' !== typeof opts.content || opts.content.jquery) {
16
+ opts.content = {
17
+ text: opts.content
18
+ };
19
+ }
20
+
21
+ content = opts.content.text || FALSE;
22
+ if(!$.isFunction(content) && ((!content && !content.attr) || content.length < 1 || ('object' === typeof content && !content.jquery))) {
23
+ opts.content.text = FALSE;
24
+ }
25
+
26
+ if('title' in opts.content) {
27
+ if(opts.content.title === NULL || 'object' !== typeof opts.content.title) {
28
+ opts.content.title = {
29
+ text: opts.content.title
30
+ };
31
+ }
32
+
33
+ content = opts.content.title.text || FALSE;
34
+ if(!$.isFunction(content) && ((!content && !content.attr) || content.length < 1 || ('object' === typeof content && !content.jquery))) {
35
+ opts.content.title.text = FALSE;
36
+ }
37
+ }
38
+ }
39
+
40
+ if('position' in opts) {
41
+ if(opts.position === NULL || 'object' !== typeof opts.position) {
42
+ opts.position = {
43
+ my: opts.position,
44
+ at: opts.position
45
+ };
46
+ }
47
+ }
48
+
49
+ if('show' in opts) {
50
+ if(opts.show === NULL || 'object' !== typeof opts.show) {
51
+ if(opts.show.jquery) {
52
+ opts.show = { target: opts.show };
53
+ }
54
+ else {
55
+ opts.show = { event: opts.show };
56
+ }
57
+ }
58
+ }
59
+
60
+ if('hide' in opts) {
61
+ if(opts.hide === NULL || 'object' !== typeof opts.hide) {
62
+ if(opts.hide.jquery) {
63
+ opts.hide = { target: opts.hide };
64
+ }
65
+ else {
66
+ opts.hide = { event: opts.hide };
67
+ }
68
+ }
69
+ }
70
+
71
+ if('style' in opts) {
72
+ if(opts.style === NULL || 'object' !== typeof opts.style) {
73
+ opts.style = {
74
+ classes: opts.style
75
+ };
76
+ }
77
+ }
78
+
79
+ // Sanitize plugin options
80
+ $.each(PLUGINS, function() {
81
+ if(this.sanitize) { this.sanitize(opts); }
82
+ });
83
+
84
+ return opts;
85
+ }
86
+
87
+ /*
88
+ * Core plugin implementation
89
+ */
90
+ function QTip(target, options, id, attr)
91
+ {
92
+ // Declare this reference
93
+ var self = this,
94
+ docBody = document.body,
95
+ tooltipID = uitooltip + '-' + id,
96
+ isPositioning = 0,
97
+ isDrawing = 0,
98
+ tooltip = $(),
99
+ namespace = '.qtip-' + id,
100
+ elements, cache;
101
+
102
+ // Setup class attributes
103
+ self.id = id;
104
+ self.rendered = FALSE;
105
+ self.destroyed = FALSE;
106
+ self.elements = elements = { target: target };
107
+ self.timers = { img: {} };
108
+ self.options = options;
109
+ self.checks = {};
110
+ self.plugins = {};
111
+ self.cache = cache = {
112
+ event: {},
113
+ target: $(),
114
+ disabled: FALSE,
115
+ attr: attr,
116
+ onTarget: FALSE
117
+ };
118
+
119
+ /*
120
+ * Private core functions
121
+ */
122
+ function convertNotation(notation)
123
+ {
124
+ var i = 0, obj, option = options,
125
+
126
+ // Split notation into array
127
+ levels = notation.split('.');
128
+
129
+ // Loop through
130
+ while( option = option[ levels[i++] ] ) {
131
+ if(i < levels.length) { obj = option; }
132
+ }
133
+
134
+ return [obj || options, levels.pop()];
135
+ }
136
+
137
+ function setWidget() {
138
+ var on = options.style.widget;
139
+
140
+ tooltip.toggleClass(widget, on).toggleClass(defaultClass, options.style.def && !on);
141
+ elements.content.toggleClass(widget+'-content', on);
142
+
143
+ if(elements.titlebar){
144
+ elements.titlebar.toggleClass(widget+'-header', on);
145
+ }
146
+ if(elements.button){
147
+ elements.button.toggleClass(uitooltip+'-icon', !on);
148
+ }
149
+ }
150
+
151
+ function removeTitle(reposition)
152
+ {
153
+ if(elements.title) {
154
+ elements.titlebar.remove();
155
+ elements.titlebar = elements.title = elements.button = NULL;
156
+
157
+ // Reposition if enabled
158
+ if(reposition !== FALSE) { self.reposition(); }
159
+ }
160
+ }
161
+
162
+ function createButton()
163
+ {
164
+ var button = options.content.title.button,
165
+ isString = typeof button === 'string',
166
+ close = isString ? button : 'Close tooltip';
167
+
168
+ if(elements.button) { elements.button.remove(); }
169
+
170
+ // Use custom button if one was supplied by user, else use default
171
+ if(button.jquery) {
172
+ elements.button = button;
173
+ }
174
+ else {
175
+ elements.button = $('<a />', {
176
+ 'class': 'ui-state-default ui-tooltip-close ' + (options.style.widget ? '' : uitooltip+'-icon'),
177
+ 'title': close,
178
+ 'aria-label': close
179
+ })
180
+ .prepend(
181
+ $('<span />', {
182
+ 'class': 'ui-icon ui-icon-close',
183
+ 'html': '&times;'
184
+ })
185
+ );
186
+ }
187
+
188
+ // Create button and setup attributes
189
+ elements.button.appendTo(elements.titlebar)
190
+ .attr('role', 'button')
191
+ .click(function(event) {
192
+ if(!tooltip.hasClass(disabled)) { self.hide(event); }
193
+ return FALSE;
194
+ });
195
+
196
+ // Redraw the tooltip when we're done
197
+ self.redraw();
198
+ }
199
+
200
+ function createTitle()
201
+ {
202
+ var id = tooltipID+'-title';
203
+
204
+ // Destroy previous title element, if present
205
+ if(elements.titlebar) { removeTitle(); }
206
+
207
+ // Create title bar and title elements
208
+ elements.titlebar = $('<div />', {
209
+ 'class': uitooltip + '-titlebar ' + (options.style.widget ? 'ui-widget-header' : '')
210
+ })
211
+ .append(
212
+ elements.title = $('<div />', {
213
+ 'id': id,
214
+ 'class': uitooltip + '-title',
215
+ 'aria-atomic': TRUE
216
+ })
217
+ )
218
+ .insertBefore(elements.content)
219
+
220
+ // Button-specific events
221
+ .delegate('.ui-tooltip-close', 'mousedown keydown mouseup keyup mouseout', function(event) {
222
+ $(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down');
223
+ })
224
+ .delegate('.ui-tooltip-close', 'mouseover mouseout', function(event){
225
+ $(this).toggleClass('ui-state-hover', event.type === 'mouseover');
226
+ });
227
+
228
+ // Create button if enabled
229
+ if(options.content.title.button) { createButton(); }
230
+
231
+ // Redraw the tooltip dimensions if it's rendered
232
+ else if(self.rendered){ self.redraw(); }
233
+ }
234
+
235
+ function updateButton(button)
236
+ {
237
+ var elem = elements.button,
238
+ title = elements.title;
239
+
240
+ // Make sure tooltip is rendered and if not, return
241
+ if(!self.rendered) { return FALSE; }
242
+
243
+ if(!button) {
244
+ elem.remove();
245
+ }
246
+ else {
247
+ if(!title) {
248
+ createTitle();
249
+ }
250
+ createButton();
251
+ }
252
+ }
253
+
254
+ function updateTitle(content, reposition)
255
+ {
256
+ var elem = elements.title;
257
+
258
+ // Make sure tooltip is rendered and if not, return
259
+ if(!self.rendered || !content) { return FALSE; }
260
+
261
+ // Use function to parse content
262
+ if($.isFunction(content)) {
263
+ content = content.call(target, cache.event, self);
264
+ }
265
+
266
+ // Remove title if callback returns false or null/undefined (but not '')
267
+ if(content === FALSE || (!content && content !== '')) { return removeTitle(FALSE); }
268
+
269
+ // Append new content if its a DOM array and show it if hidden
270
+ else if(content.jquery && content.length > 0) {
271
+ elem.empty().append(content.css({ display: 'block' }));
272
+ }
273
+
274
+ // Content is a regular string, insert the new content
275
+ else { elem.html(content); }
276
+
277
+ // Redraw and reposition
278
+ self.redraw();
279
+ if(reposition !== FALSE && self.rendered && tooltip[0].offsetWidth > 0) {
280
+ self.reposition(cache.event);
281
+ }
282
+ }
283
+
284
+ function updateContent(content, reposition)
285
+ {
286
+ var elem = elements.content;
287
+
288
+ // Make sure tooltip is rendered and content is defined. If not return
289
+ if(!self.rendered || !content) { return FALSE; }
290
+
291
+ // Use function to parse content
292
+ if($.isFunction(content)) {
293
+ content = content.call(target, cache.event, self) || '';
294
+ }
295
+
296
+ // Append new content if its a DOM array and show it if hidden
297
+ if(content.jquery && content.length > 0) {
298
+ elem.empty().append(content.css({ display: 'block' }));
299
+ }
300
+
301
+ // Content is a regular string, insert the new content
302
+ else { elem.html(content); }
303
+
304
+ // Image detection
305
+ function detectImages(next) {
306
+ var images, srcs = {};
307
+
308
+ function imageLoad(image) {
309
+ // Clear src from object and any timers and events associated with the image
310
+ if(image) {
311
+ delete srcs[image.src];
312
+ clearTimeout(self.timers.img[image.src]);
313
+ $(image).unbind(namespace);
314
+ }
315
+
316
+ // If queue is empty after image removal, update tooltip and continue the queue
317
+ if($.isEmptyObject(srcs)) {
318
+ self.redraw();
319
+ if(reposition !== FALSE) {
320
+ self.reposition(cache.event);
321
+ }
322
+
323
+ next();
324
+ }
325
+ }
326
+
327
+ // Find all content images without dimensions, and if no images were found, continue
328
+ if((images = elem.find('img[src]:not([height]):not([width])')).length === 0) { return imageLoad(); }
329
+
330
+ // Apply timer to each image to poll for dimensions
331
+ images.each(function(i, elem) {
332
+ // Skip if the src is already present
333
+ if(srcs[elem.src] !== undefined) { return; }
334
+
335
+ // Keep track of how many times we poll for image dimensions.
336
+ // If it doesn't return in a reasonable amount of time, it's better
337
+ // to display the tooltip, rather than hold up the queue.
338
+ var iterations = 0, maxIterations = 3;
339
+
340
+ (function timer(){
341
+ // When the dimensions are found, remove the image from the queue
342
+ if(elem.height || elem.width || (iterations > maxIterations)) { return imageLoad(elem); }
343
+
344
+ // Increase iterations and restart timer
345
+ iterations += 1;
346
+ self.timers.img[elem.src] = setTimeout(timer, 700);
347
+ }());
348
+
349
+ // Also apply regular load/error event handlers
350
+ $(elem).bind('error'+namespace+' load'+namespace, function(){ imageLoad(this); });
351
+
352
+ // Store the src and element in our object
353
+ srcs[elem.src] = elem;
354
+ });
355
+ }
356
+
357
+ /*
358
+ * If we're still rendering... insert into 'fx' queue our image dimension
359
+ * checker which will halt the showing of the tooltip until image dimensions
360
+ * can be detected properly.
361
+ */
362
+ if(self.rendered < 0) { tooltip.queue('fx', detectImages); }
363
+
364
+ // We're fully rendered, so reset isDrawing flag and proceed without queue delay
365
+ else { isDrawing = 0; detectImages($.noop); }
366
+
367
+ return self;
368
+ }
369
+
370
+ function assignEvents()
371
+ {
372
+ var posOptions = options.position,
373
+ targets = {
374
+ show: options.show.target,
375
+ hide: options.hide.target,
376
+ viewport: $(posOptions.viewport),
377
+ document: $(document),
378
+ body: $(document.body),
379
+ window: $(window)
380
+ },
381
+ events = {
382
+ show: $.trim('' + options.show.event).split(' '),
383
+ hide: $.trim('' + options.hide.event).split(' ')
384
+ },
385
+ IE6 = $.browser.msie && parseInt($.browser.version, 10) === 6;
386
+
387
+ // Define show event method
388
+ function showMethod(event)
389
+ {
390
+ if(tooltip.hasClass(disabled)) { return FALSE; }
391
+
392
+ // Clear hide timers
393
+ clearTimeout(self.timers.show);
394
+ clearTimeout(self.timers.hide);
395
+
396
+ // Start show timer
397
+ var callback = function(){ self.toggle(TRUE, event); };
398
+ if(options.show.delay > 0) {
399
+ self.timers.show = setTimeout(callback, options.show.delay);
400
+ }
401
+ else{ callback(); }
402
+ }
403
+
404
+ // Define hide method
405
+ function hideMethod(event)
406
+ {
407
+ if(tooltip.hasClass(disabled) || isPositioning || isDrawing) { return FALSE; }
408
+
409
+ // Check if new target was actually the tooltip element
410
+ var relatedTarget = $(event.relatedTarget || event.target),
411
+ ontoTooltip = relatedTarget.closest(selector)[0] === tooltip[0],
412
+ ontoTarget = relatedTarget[0] === targets.show[0];
413
+
414
+ // Clear timers and stop animation queue
415
+ clearTimeout(self.timers.show);
416
+ clearTimeout(self.timers.hide);
417
+
418
+ // Prevent hiding if tooltip is fixed and event target is the tooltip. Or if mouse positioning is enabled and cursor momentarily overlaps
419
+ if((posOptions.target === 'mouse' && ontoTooltip) || (options.hide.fixed && ((/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget)))) {
420
+ try { event.preventDefault(); event.stopImmediatePropagation(); } catch(e) {} return;
421
+ }
422
+
423
+ // If tooltip has displayed, start hide timer
424
+ if(options.hide.delay > 0) {
425
+ self.timers.hide = setTimeout(function(){ self.hide(event); }, options.hide.delay);
426
+ }
427
+ else{ self.hide(event); }
428
+ }
429
+
430
+ // Define inactive method
431
+ function inactiveMethod(event)
432
+ {
433
+ if(tooltip.hasClass(disabled)) { return FALSE; }
434
+
435
+ // Clear timer
436
+ clearTimeout(self.timers.inactive);
437
+ self.timers.inactive = setTimeout(function(){ self.hide(event); }, options.hide.inactive);
438
+ }
439
+
440
+ function repositionMethod(event) {
441
+ if(self.rendered && tooltip[0].offsetWidth > 0) { self.reposition(event); }
442
+ }
443
+
444
+ // On mouseenter/mouseleave...
445
+ tooltip.bind('mouseenter'+namespace+' mouseleave'+namespace, function(event) {
446
+ var state = event.type === 'mouseenter';
447
+
448
+ // Focus the tooltip on mouseenter (z-index stacking)
449
+ if(state) { self.focus(event); }
450
+
451
+ // Add hover class
452
+ tooltip.toggleClass(hoverClass, state);
453
+ });
454
+
455
+ // Enable hide.fixed
456
+ if(options.hide.fixed) {
457
+ // Add tooltip as a hide target
458
+ targets.hide = targets.hide.add(tooltip);
459
+
460
+ // Clear hide timer on tooltip hover to prevent it from closing
461
+ tooltip.bind('mouseover'+namespace, function() {
462
+ if(!tooltip.hasClass(disabled)) { clearTimeout(self.timers.hide); }
463
+ });
464
+ }
465
+
466
+ // If using mouseout/mouseleave as a hide event...
467
+ if(/mouse(out|leave)/i.test(options.hide.event)) {
468
+ // Hide tooltips when leaving current window/frame (but not select/option elements)
469
+ if(options.hide.leave === 'window') {
470
+ targets.window.bind('mouseout'+namespace+' blur'+namespace, function(event) {
471
+ if(/select|option/.test(event.target) && !event.relatedTarget) { self.hide(event); }
472
+ });
473
+ }
474
+ }
475
+
476
+ /*
477
+ * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
478
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
479
+ */
480
+ else if(/mouse(over|enter)/i.test(options.show.event)) {
481
+ targets.hide.bind('mouseleave'+namespace, function(event) {
482
+ clearTimeout(self.timers.show);
483
+ });
484
+ }
485
+
486
+ // Hide tooltip on document mousedown if unfocus events are enabled
487
+ if(('' + options.hide.event).indexOf('unfocus') > -1) {
488
+ posOptions.container.closest('html').bind('mousedown'+namespace, function(event) {
489
+ var elem = $(event.target),
490
+ enabled = self.rendered && !tooltip.hasClass(disabled) && tooltip[0].offsetWidth > 0,
491
+ isAncestor = elem.parents(selector).filter(tooltip[0]).length > 0;
492
+
493
+ if(elem[0] !== target[0] && elem[0] !== tooltip[0] && !isAncestor &&
494
+ !target.has(elem[0]).length && !elem.attr('disabled')
495
+ ) {
496
+ self.hide(event);
497
+ }
498
+ });
499
+ }
500
+
501
+ // Check if the tooltip hides when inactive
502
+ if('number' === typeof options.hide.inactive) {
503
+ // Bind inactive method to target as a custom event
504
+ targets.show.bind('qtip-'+id+'-inactive', inactiveMethod);
505
+
506
+ // Define events which reset the 'inactive' event handler
507
+ $.each(QTIP.inactiveEvents, function(index, type){
508
+ targets.hide.add(elements.tooltip).bind(type+namespace+'-inactive', inactiveMethod);
509
+ });
510
+ }
511
+
512
+ // Apply hide events
513
+ $.each(events.hide, function(index, type) {
514
+ var showIndex = $.inArray(type, events.show),
515
+ targetHide = $(targets.hide);
516
+
517
+ // Both events and targets are identical, apply events using a toggle
518
+ if((showIndex > -1 && targetHide.add(targets.show).length === targetHide.length) || type === 'unfocus')
519
+ {
520
+ targets.show.bind(type+namespace, function(event) {
521
+ if(tooltip[0].offsetWidth > 0) { hideMethod(event); }
522
+ else { showMethod(event); }
523
+ });
524
+
525
+ // Don't bind the event again
526
+ delete events.show[ showIndex ];
527
+ }
528
+
529
+ // Events are not identical, bind normally
530
+ else { targets.hide.bind(type+namespace, hideMethod); }
531
+ });
532
+
533
+ // Apply show events
534
+ $.each(events.show, function(index, type) {
535
+ targets.show.bind(type+namespace, showMethod);
536
+ });
537
+
538
+ // Check if the tooltip hides when mouse is moved a certain distance
539
+ if('number' === typeof options.hide.distance) {
540
+ // Bind mousemove to target to detect distance difference
541
+ targets.show.add(tooltip).bind('mousemove'+namespace, function(event) {
542
+ var origin = cache.origin || {},
543
+ limit = options.hide.distance,
544
+ abs = Math.abs;
545
+
546
+ // Check if the movement has gone beyond the limit, and hide it if so
547
+ if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) {
548
+ self.hide(event);
549
+ }
550
+ });
551
+ }
552
+
553
+ // Mouse positioning events
554
+ if(posOptions.target === 'mouse') {
555
+ // Cache mousemove coords on show targets
556
+ targets.show.bind('mousemove'+namespace, function(event) {
557
+ MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' };
558
+ });
559
+
560
+ // If mouse adjustment is on...
561
+ if(posOptions.adjust.mouse) {
562
+ // Apply a mouseleave event so we don't get problems with overlapping
563
+ if(options.hide.event) {
564
+ // Hide when we leave the tooltip and not onto the show target
565
+ tooltip.bind('mouseleave'+namespace, function(event) {
566
+ if((event.relatedTarget || event.target) !== targets.show[0]) { self.hide(event); }
567
+ });
568
+
569
+ // Track if we're on the target or not
570
+ elements.target.bind('mouseenter'+namespace+' mouseleave'+namespace, function(event) {
571
+ cache.onTarget = event.type === 'mouseenter';
572
+ });
573
+ }
574
+
575
+ // Update tooltip position on mousemove
576
+ targets.document.bind('mousemove'+namespace, function(event) {
577
+ // Update the tooltip position only if the tooltip is visible and adjustment is enabled
578
+ if(self.rendered && cache.onTarget && !tooltip.hasClass(disabled) && tooltip[0].offsetWidth > 0) {
579
+ self.reposition(event || MOUSE);
580
+ }
581
+ });
582
+ }
583
+ }
584
+
585
+ // Adjust positions of the tooltip on window resize if enabled
586
+ if(posOptions.adjust.resize || targets.viewport.length) {
587
+ ($.event.special.resize ? targets.viewport : targets.window).bind('resize'+namespace, repositionMethod);
588
+ }
589
+
590
+ // Adjust tooltip position on scroll if screen adjustment is enabled
591
+ if(targets.viewport.length || (IE6 && tooltip.css('position') === 'fixed')) {
592
+ targets.viewport.bind('scroll'+namespace, repositionMethod);
593
+ }
594
+ }
595
+
596
+ function unassignEvents()
597
+ {
598
+ var targets = [
599
+ options.show.target[0],
600
+ options.hide.target[0],
601
+ self.rendered && elements.tooltip[0],
602
+ options.position.container[0],
603
+ options.position.viewport[0],
604
+ window,
605
+ document
606
+ ];
607
+
608
+ // Check if tooltip is rendered
609
+ if(self.rendered) {
610
+ $([]).pushStack( $.grep(targets, function(i){ return typeof i === 'object'; }) ).unbind(namespace);
611
+ }
612
+
613
+ // Tooltip isn't yet rendered, remove render event
614
+ else { options.show.target.unbind(namespace+'-create'); }
615
+ }
616
+
617
+ // Setup builtin .set() option checks
618
+ self.checks.builtin = {
619
+ // Core checks
620
+ '^id$': function(obj, o, v) {
621
+ var id = v === TRUE ? QTIP.nextid : v,
622
+ tooltipID = uitooltip + '-' + id;
623
+
624
+ if(id !== FALSE && id.length > 0 && !$('#'+tooltipID).length) {
625
+ tooltip[0].id = tooltipID;
626
+ elements.content[0].id = tooltipID + '-content';
627
+ elements.title[0].id = tooltipID + '-title';
628
+ }
629
+ },
630
+
631
+ // Content checks
632
+ '^content.text$': function(obj, o, v){ updateContent(v); },
633
+ '^content.title.text$': function(obj, o, v) {
634
+ // Remove title if content is null
635
+ if(!v) { return removeTitle(); }
636
+
637
+ // If title isn't already created, create it now and update
638
+ if(!elements.title && v) { createTitle(); }
639
+ updateTitle(v);
640
+ },
641
+ '^content.title.button$': function(obj, o, v){ updateButton(v); },
642
+
643
+ // Position checks
644
+ '^position.(my|at)$': function(obj, o, v){
645
+ // Parse new corner value into Corner objecct
646
+ if('string' === typeof v) {
647
+ obj[o] = new PLUGINS.Corner(v);
648
+ }
649
+ },
650
+ '^position.container$': function(obj, o, v){
651
+ if(self.rendered) { tooltip.appendTo(v); }
652
+ },
653
+
654
+ // Show checks
655
+ '^show.ready$': function() {
656
+ if(!self.rendered) { self.render(1); }
657
+ else { self.toggle(TRUE); }
658
+ },
659
+
660
+ // Style checks
661
+ '^style.classes$': function(obj, o, v) {
662
+ tooltip.attr('class', uitooltip + ' qtip ui-helper-reset ' + v);
663
+ },
664
+ '^style.widget|content.title': setWidget,
665
+
666
+ // Events check
667
+ '^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
668
+ tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
669
+ },
670
+
671
+ // Properties which require event reassignment
672
+ '^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
673
+ var posOptions = options.position;
674
+
675
+ // Set tracking flag
676
+ tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);
677
+
678
+ // Reassign events
679
+ unassignEvents(); assignEvents();
680
+ }
681
+ };
682
+
683
+ /*
684
+ * Public API methods
685
+ */
686
+ $.extend(self, {
687
+ render: function(show)
688
+ {
689
+ if(self.rendered) { return self; } // If tooltip has already been rendered, exit
690
+
691
+ var text = options.content.text,
692
+ title = options.content.title.text,
693
+ posOptions = options.position,
694
+ callback = $.Event('tooltiprender');
695
+
696
+ // Add ARIA attributes to target
697
+ $.attr(target[0], 'aria-describedby', tooltipID);
698
+
699
+ // Create tooltip element
700
+ tooltip = elements.tooltip = $('<div/>', {
701
+ 'id': tooltipID,
702
+ 'class': uitooltip + ' qtip ui-helper-reset ' + defaultClass + ' ' + options.style.classes + ' '+ uitooltip + '-pos-' + options.position.my.abbrev(),
703
+ 'width': options.style.width || '',
704
+ 'height': options.style.height || '',
705
+ 'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,
706
+
707
+ /* ARIA specific attributes */
708
+ 'role': 'alert',
709
+ 'aria-live': 'polite',
710
+ 'aria-atomic': FALSE,
711
+ 'aria-describedby': tooltipID + '-content',
712
+ 'aria-hidden': TRUE
713
+ })
714
+ .toggleClass(disabled, cache.disabled)
715
+ .data('qtip', self)
716
+ .appendTo(options.position.container)
717
+ .append(
718
+ // Create content element
719
+ elements.content = $('<div />', {
720
+ 'class': uitooltip + '-content',
721
+ 'id': tooltipID + '-content',
722
+ 'aria-atomic': TRUE
723
+ })
724
+ );
725
+
726
+ // Set rendered flag and prevent redundant redraw/reposition calls for now
727
+ self.rendered = -1;
728
+ isDrawing = 1; isPositioning = 1;
729
+
730
+ // Create title...
731
+ if(title) {
732
+ createTitle();
733
+
734
+ // Update title only if its not a callback (called in toggle if so)
735
+ if(!$.isFunction(title)) { updateTitle(title, FALSE); }
736
+ }
737
+
738
+ // Set proper rendered flag and update content if not a callback function (called in toggle)
739
+ if(!$.isFunction(text)) { updateContent(text, FALSE); }
740
+ self.rendered = TRUE;
741
+
742
+ // Setup widget classes
743
+ setWidget();
744
+
745
+ // Assign passed event callbacks (before plugins!)
746
+ $.each(options.events, function(name, callback) {
747
+ if($.isFunction(callback)) {
748
+ tooltip.bind(name === 'toggle' ? 'tooltipshow tooltiphide' : 'tooltip'+name, callback);
749
+ }
750
+ });
751
+
752
+ // Initialize 'render' plugins
753
+ $.each(PLUGINS, function() {
754
+ if(this.initialize === 'render') { this(self); }
755
+ });
756
+
757
+ // Assign events
758
+ assignEvents();
759
+
760
+ /* Queue this part of the render process in our fx queue so we can
761
+ * load images before the tooltip renders fully.
762
+ *
763
+ * See: updateContent method
764
+ */
765
+ tooltip.queue('fx', function(next) {
766
+ // Trigger tooltiprender event and pass original triggering event as original
767
+ callback.originalEvent = cache.event;
768
+ tooltip.trigger(callback, [self]);
769
+
770
+ // Reset flags
771
+ isDrawing = 0; isPositioning = 0;
772
+
773
+ // Redraw the tooltip manually now we're fully rendered
774
+ self.redraw();
775
+
776
+ // Show tooltip if needed
777
+ if(options.show.ready || show) {
778
+ self.toggle(TRUE, cache.event, FALSE);
779
+ }
780
+
781
+ next(); // Move on to next method in queue
782
+ });
783
+
784
+ return self;
785
+ },
786
+
787
+ get: function(notation)
788
+ {
789
+ var result, o;
790
+
791
+ switch(notation.toLowerCase())
792
+ {
793
+ case 'dimensions':
794
+ result = {
795
+ height: tooltip.outerHeight(), width: tooltip.outerWidth()
796
+ };
797
+ break;
798
+
799
+ case 'offset':
800
+ result = PLUGINS.offset(tooltip, options.position.container);
801
+ break;
802
+
803
+ default:
804
+ o = convertNotation(notation.toLowerCase());
805
+ result = o[0][ o[1] ];
806
+ result = result.precedance ? result.string() : result;
807
+ break;
808
+ }
809
+
810
+ return result;
811
+ },
812
+
813
+ set: function(option, value)
814
+ {
815
+ var rmove = /^position\.(my|at|adjust|target|container)|style|content|show\.ready/i,
816
+ rdraw = /^content\.(title|attr)|style/i,
817
+ reposition = FALSE,
818
+ redraw = FALSE,
819
+ checks = self.checks,
820
+ name;
821
+
822
+ function callback(notation, args) {
823
+ var category, rule, match;
824
+
825
+ for(category in checks) {
826
+ for(rule in checks[category]) {
827
+ if(match = (new RegExp(rule, 'i')).exec(notation)) {
828
+ args.push(match);
829
+ checks[category][rule].apply(self, args);
830
+ }
831
+ }
832
+ }
833
+ }
834
+
835
+ // Convert singular option/value pair into object form
836
+ if('string' === typeof option) {
837
+ name = option; option = {}; option[name] = value;
838
+ }
839
+ else { option = $.extend(TRUE, {}, option); }
840
+
841
+ // Set all of the defined options to their new values
842
+ $.each(option, function(notation, value) {
843
+ var obj = convertNotation( notation.toLowerCase() ), previous;
844
+
845
+ // Set new obj value
846
+ previous = obj[0][ obj[1] ];
847
+ obj[0][ obj[1] ] = 'object' === typeof value && value.nodeType ? $(value) : value;
848
+
849
+ // Set the new params for the callback
850
+ option[notation] = [obj[0], obj[1], value, previous];
851
+
852
+ // Also check if we need to reposition / redraw
853
+ reposition = rmove.test(notation) || reposition;
854
+ redraw = rdraw.test(notation) || redraw;
855
+ });
856
+
857
+ // Re-sanitize options
858
+ sanitizeOptions(options);
859
+
860
+ /*
861
+ * Execute any valid callbacks for the set options
862
+ * Also set isPositioning/isDrawing so we don't get loads of redundant repositioning
863
+ * and redraw calls.
864
+ */
865
+ isPositioning = isDrawing = 1; $.each(option, callback); isPositioning = isDrawing = 0;
866
+
867
+ // Update position / redraw if needed
868
+ if(self.rendered && tooltip[0].offsetWidth > 0) {
869
+ if(reposition) {
870
+ self.reposition( options.position.target === 'mouse' ? NULL : cache.event );
871
+ }
872
+ if(redraw) { self.redraw(); }
873
+ }
874
+
875
+ return self;
876
+ },
877
+
878
+ toggle: function(state, event)
879
+ {
880
+ // Render the tooltip if showing and it isn't already
881
+ if(!self.rendered) { return state ? self.render(1) : self; }
882
+
883
+ var type = state ? 'show' : 'hide',
884
+ opts = options[type],
885
+ otherOpts = options[ !state ? 'show' : 'hide' ],
886
+ posOptions = options.position,
887
+ contentOptions = options.content,
888
+ visible = tooltip[0].offsetWidth > 0,
889
+ animate = state || opts.target.length === 1,
890
+ sameTarget = !event || opts.target.length < 2 || cache.target[0] === event.target,
891
+ delay, callback;
892
+
893
+ // Detect state if valid one isn't provided
894
+ if((typeof state).search('boolean|number')) { state = !visible; }
895
+
896
+ // Return if element is already in correct state
897
+ if(!tooltip.is(':animated') && visible === state && sameTarget) { return self; }
898
+
899
+ // Try to prevent flickering when tooltip overlaps show element
900
+ if(event) {
901
+ if((/over|enter/).test(event.type) && (/out|leave/).test(cache.event.type) &&
902
+ options.show.target.add(event.target).length === options.show.target.length &&
903
+ tooltip.has(event.relatedTarget).length) {
904
+ return self;
905
+ }
906
+
907
+ // Cache event
908
+ cache.event = $.extend({}, event);
909
+ }
910
+
911
+ // Call API methods
912
+ callback = $.Event('tooltip'+type);
913
+ callback.originalEvent = event ? cache.event : NULL;
914
+ tooltip.trigger(callback, [self, 90]);
915
+ if(callback.isDefaultPrevented()){ return self; }
916
+
917
+ // Set ARIA hidden status attribute
918
+ $.attr(tooltip[0], 'aria-hidden', !!!state);
919
+
920
+ // Execute state specific properties
921
+ if(state) {
922
+ // Store show origin coordinates
923
+ cache.origin = $.extend({}, MOUSE);
924
+
925
+ // Focus the tooltip
926
+ self.focus(event);
927
+
928
+ // Update tooltip content & title if it's a dynamic function
929
+ if($.isFunction(contentOptions.text)) { updateContent(contentOptions.text, FALSE); }
930
+ if($.isFunction(contentOptions.title.text)) { updateTitle(contentOptions.title.text, FALSE); }
931
+
932
+ // Cache mousemove events for positioning purposes (if not already tracking)
933
+ if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) {
934
+ $(document).bind('mousemove.qtip', function(event) {
935
+ MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' };
936
+ });
937
+ trackingBound = TRUE;
938
+ }
939
+
940
+ // Update the tooltip position
941
+ self.reposition(event, arguments[2]);
942
+
943
+ // Hide other tooltips if tooltip is solo, using it as the context
944
+ if((callback.solo = !!opts.solo)) { $(selector, opts.solo).not(tooltip).qtip('hide', callback); }
945
+ }
946
+ else {
947
+ // Clear show timer if we're hiding
948
+ clearTimeout(self.timers.show);
949
+
950
+ // Remove cached origin on hide
951
+ delete cache.origin;
952
+
953
+ // Remove mouse tracking event if not needed (all tracking qTips are hidden)
954
+ if(trackingBound && !$(selector+'[tracking="true"]:visible', opts.solo).not(tooltip).length) {
955
+ $(document).unbind('mousemove.qtip');
956
+ trackingBound = FALSE;
957
+ }
958
+
959
+ // Blur the tooltip
960
+ self.blur(event);
961
+ }
962
+
963
+ // Define post-animation, state specific properties
964
+ function after() {
965
+ if(state) {
966
+ // Prevent antialias from disappearing in IE by removing filter
967
+ if($.browser.msie) { tooltip[0].style.removeAttribute('filter'); }
968
+
969
+ // Remove overflow setting to prevent tip bugs
970
+ tooltip.css('overflow', '');
971
+
972
+ // Autofocus elements if enabled
973
+ if('string' === typeof opts.autofocus) {
974
+ $(opts.autofocus, tooltip).focus();
975
+ }
976
+
977
+ // If set, hide tooltip when inactive for delay period
978
+ opts.target.trigger('qtip-'+id+'-inactive');
979
+ }
980
+ else {
981
+ // Reset CSS states
982
+ tooltip.css({
983
+ display: '',
984
+ visibility: '',
985
+ opacity: '',
986
+ left: '',
987
+ top: ''
988
+ });
989
+ }
990
+
991
+ // Call API method
992
+ callback = $.Event('tooltip'+(state ? 'visible' : 'hidden'));
993
+ callback.originalEvent = event ? cache.event : NULL;
994
+ tooltip.trigger(callback, [self]);
995
+ }
996
+
997
+ // If no effect type is supplied, use a simple toggle
998
+ if(opts.effect === FALSE || animate === FALSE) {
999
+ tooltip[ type ]();
1000
+ after.call(tooltip);
1001
+ }
1002
+
1003
+ // Use custom function if provided
1004
+ else if($.isFunction(opts.effect)) {
1005
+ tooltip.stop(1, 1);
1006
+ opts.effect.call(tooltip, self);
1007
+ tooltip.queue('fx', function(n){ after(); n(); });
1008
+ }
1009
+
1010
+ // Use basic fade function by default
1011
+ else { tooltip.fadeTo(90, state ? 1 : 0, after); }
1012
+
1013
+ // If inactive hide method is set, active it
1014
+ if(state) { opts.target.trigger('qtip-'+id+'-inactive'); }
1015
+
1016
+ return self;
1017
+ },
1018
+
1019
+ show: function(event){ return self.toggle(TRUE, event); },
1020
+
1021
+ hide: function(event){ return self.toggle(FALSE, event); },
1022
+
1023
+ focus: function(event)
1024
+ {
1025
+ if(!self.rendered) { return self; }
1026
+
1027
+ var qtips = $(selector),
1028
+ curIndex = parseInt(tooltip[0].style.zIndex, 10),
1029
+ newIndex = QTIP.zindex + qtips.length,
1030
+ cachedEvent = $.extend({}, event),
1031
+ focusedElem, callback;
1032
+
1033
+ // Only update the z-index if it has changed and tooltip is not already focused
1034
+ if(!tooltip.hasClass(focusClass))
1035
+ {
1036
+ // Call API method
1037
+ callback = $.Event('tooltipfocus');
1038
+ callback.originalEvent = cachedEvent;
1039
+ tooltip.trigger(callback, [self, newIndex]);
1040
+
1041
+ // If default action wasn't prevented...
1042
+ if(!callback.isDefaultPrevented()) {
1043
+ // Only update z-index's if they've changed
1044
+ if(curIndex !== newIndex) {
1045
+ // Reduce our z-index's and keep them properly ordered
1046
+ qtips.each(function() {
1047
+ if(this.style.zIndex > curIndex) {
1048
+ this.style.zIndex = this.style.zIndex - 1;
1049
+ }
1050
+ });
1051
+
1052
+ // Fire blur event for focused tooltip
1053
+ qtips.filter('.' + focusClass).qtip('blur', cachedEvent);
1054
+ }
1055
+
1056
+ // Set the new z-index
1057
+ tooltip.addClass(focusClass)[0].style.zIndex = newIndex;
1058
+ }
1059
+ }
1060
+
1061
+ return self;
1062
+ },
1063
+
1064
+ blur: function(event) {
1065
+ var cachedEvent = $.extend({}, event),
1066
+ callback;
1067
+
1068
+ // Set focused status to FALSE
1069
+ tooltip.removeClass(focusClass);
1070
+
1071
+ // Trigger blur event
1072
+ callback = $.Event('tooltipblur');
1073
+ callback.originalEvent = cachedEvent;
1074
+ tooltip.trigger(callback, [self]);
1075
+
1076
+ return self;
1077
+ },
1078
+
1079
+ reposition: function(event, effect)
1080
+ {
1081
+ if(!self.rendered || isPositioning) { return self; }
1082
+
1083
+ // Set positioning flag
1084
+ isPositioning = 1;
1085
+
1086
+ var target = options.position.target,
1087
+ posOptions = options.position,
1088
+ my = posOptions.my,
1089
+ at = posOptions.at,
1090
+ adjust = posOptions.adjust,
1091
+ method = adjust.method.split(' '),
1092
+ elemWidth = tooltip.outerWidth(),
1093
+ elemHeight = tooltip.outerHeight(),
1094
+ targetWidth = 0,
1095
+ targetHeight = 0,
1096
+ callback = $.Event('tooltipmove'),
1097
+ fixed = tooltip.css('position') === 'fixed',
1098
+ viewport = posOptions.viewport,
1099
+ position = { left: 0, top: 0 },
1100
+ container = posOptions.container,
1101
+ flipoffset = FALSE,
1102
+ tip = self.plugins.tip,
1103
+ visible = tooltip[0].offsetWidth > 0,
1104
+ readjust = {
1105
+ // Axis detection and readjustment indicator
1106
+ horizontal: method[0],
1107
+ vertical: (method[1] = method[1] || method[0]),
1108
+ enabled: viewport.jquery && target[0] !== window && target[0] !== docBody && adjust.method !== 'none',
1109
+
1110
+ // Reposition methods
1111
+ left: function(posLeft) {
1112
+ var isShift = readjust.horizontal === 'shift',
1113
+ adjustx = adjust.x * (readjust.horizontal.substr(-6) === 'invert' ? 2 : 0),
1114
+ viewportScroll = -container.offset.left + viewport.offset.left + viewport.scrollLeft,
1115
+ myWidth = my.x === 'left' ? elemWidth : my.x === 'right' ? -elemWidth : -elemWidth / 2,
1116
+ atWidth = at.x === 'left' ? targetWidth : at.x === 'right' ? -targetWidth : -targetWidth / 2,
1117
+ tipWidth = tip && tip.size ? tip.size.width || 0 : 0,
1118
+ tipAdjust = tip && tip.corner && tip.corner.precedance === 'x' && !isShift ? tipWidth : 0,
1119
+ overflowLeft = viewportScroll - posLeft + tipAdjust,
1120
+ overflowRight = posLeft + elemWidth - viewport.width - viewportScroll + tipAdjust,
1121
+ offset = myWidth - (my.precedance === 'x' || my.x === my.y ? atWidth : 0) - (at.x === 'center' ? targetWidth / 2 : 0),
1122
+ isCenter = my.x === 'center';
1123
+
1124
+ // Optional 'shift' style repositioning
1125
+ if(isShift) {
1126
+ tipAdjust = tip && tip.corner && tip.corner.precedance === 'y' ? tipWidth : 0;
1127
+ offset = (my.x === 'left' ? 1 : -1) * myWidth - tipAdjust;
1128
+
1129
+ // Adjust position but keep it within viewport dimensions
1130
+ position.left += overflowLeft > 0 ? overflowLeft : overflowRight > 0 ? -overflowRight : 0;
1131
+ position.left = Math.max(
1132
+ -container.offset.left + viewport.offset.left + (tipAdjust && tip.corner.x === 'center' ? tip.offset : 0),
1133
+ posLeft - offset,
1134
+ Math.min(
1135
+ Math.max(-container.offset.left + viewport.offset.left + viewport.width, posLeft + offset),
1136
+ position.left
1137
+ )
1138
+ );
1139
+ }
1140
+
1141
+ // Default 'flip' repositioning
1142
+ else {
1143
+ if(overflowLeft > 0 && (my.x !== 'left' || overflowRight > 0)) {
1144
+ position.left -= offset + adjustx;
1145
+ }
1146
+ else if(overflowRight > 0 && (my.x !== 'right' || overflowLeft > 0) ) {
1147
+ position.left -= (isCenter ? -offset : offset) + adjustx;
1148
+ }
1149
+
1150
+ // Make sure we haven't made things worse with the adjustment and return the adjusted difference
1151
+ if(position.left < viewportScroll && -position.left > overflowRight) { position.left = posLeft; }
1152
+ }
1153
+
1154
+ return position.left - posLeft;
1155
+ },
1156
+ top: function(posTop) {
1157
+ var isShift = readjust.vertical === 'shift',
1158
+ adjusty = adjust.y * (readjust.vertical.substr(-6) === 'invert' ? 2 : 0),
1159
+ viewportScroll = -container.offset.top + viewport.offset.top + viewport.scrollTop,
1160
+ myHeight = my.y === 'top' ? elemHeight : my.y === 'bottom' ? -elemHeight : -elemHeight / 2,
1161
+ atHeight = at.y === 'top' ? targetHeight : at.y === 'bottom' ? -targetHeight : -targetHeight / 2,
1162
+ tipHeight = tip && tip.size ? tip.size.height || 0 : 0,
1163
+ tipAdjust = tip && tip.corner && tip.corner.precedance === 'y' && !isShift ? tipHeight : 0,
1164
+ overflowTop = viewportScroll - posTop + tipAdjust,
1165
+ overflowBottom = posTop + elemHeight - viewport.height - viewportScroll + tipAdjust,
1166
+ offset = myHeight - (my.precedance === 'y' || my.x === my.y ? atHeight : 0) - (at.y === 'center' ? targetHeight / 2 : 0),
1167
+ isCenter = my.y === 'center';
1168
+
1169
+ // Optional 'shift' style repositioning
1170
+ if(isShift) {
1171
+ tipAdjust = tip && tip.corner && tip.corner.precedance === 'x' ? tipHeight : 0;
1172
+ offset = (my.y === 'top' ? 1 : -1) * myHeight - tipAdjust;
1173
+
1174
+ // Adjust position but keep it within viewport dimensions
1175
+ position.top += overflowTop > 0 ? overflowTop : overflowBottom > 0 ? -overflowBottom : 0;
1176
+ position.top = Math.max(
1177
+ -container.offset.top + viewport.offset.top + (tipAdjust && tip.corner.x === 'center' ? tip.offset : 0),
1178
+ posTop - offset,
1179
+ Math.min(
1180
+ Math.max(-container.offset.top + viewport.offset.top + viewport.height, posTop + offset),
1181
+ position.top
1182
+ )
1183
+ );
1184
+ }
1185
+
1186
+ // Default 'flip' repositioning
1187
+ else {
1188
+ if(overflowTop > 0 && (my.y !== 'top' || overflowBottom > 0)) {
1189
+ position.top -= offset + adjusty;
1190
+ }
1191
+ else if(overflowBottom > 0 && (my.y !== 'bottom' || overflowTop > 0) ) {
1192
+ position.top -= (isCenter ? -offset : offset) + adjusty;
1193
+ }
1194
+
1195
+ // Make sure we haven't made things worse with the adjustment and return the adjusted difference
1196
+ if(position.top < 0 && -position.top > overflowBottom) { position.top = posTop; }
1197
+ }
1198
+
1199
+ return position.top - posTop;
1200
+ }
1201
+ },
1202
+ win;
1203
+
1204
+ // Check if absolute position was passed
1205
+ if($.isArray(target) && target.length === 2) {
1206
+ // Force left top and set position
1207
+ at = { x: 'left', y: 'top' };
1208
+ position = { left: target[0], top: target[1] };
1209
+ }
1210
+
1211
+ // Check if mouse was the target
1212
+ else if(target === 'mouse' && ((event && event.pageX) || cache.event.pageX)) {
1213
+ // Force left top to allow flipping
1214
+ at = { x: 'left', y: 'top' };
1215
+
1216
+ // Use cached event if one isn't available for positioning
1217
+ event = (event && (event.type === 'resize' || event.type === 'scroll') ? cache.event :
1218
+ event && event.pageX && event.type === 'mousemove' ? event :
1219
+ MOUSE && MOUSE.pageX && (adjust.mouse || !event || !event.pageX) ? { pageX: MOUSE.pageX, pageY: MOUSE.pageY } :
1220
+ !adjust.mouse && cache.origin && cache.origin.pageX && options.show.distance ? cache.origin :
1221
+ event) || event || cache.event || MOUSE || {};
1222
+
1223
+ // Use event coordinates for position
1224
+ position = { top: event.pageY, left: event.pageX };
1225
+ }
1226
+
1227
+ // Target wasn't mouse or absolute...
1228
+ else {
1229
+ // Check if event targetting is being used
1230
+ if(target === 'event') {
1231
+ if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
1232
+ target = cache.target = $(event.target);
1233
+ }
1234
+ else {
1235
+ target = cache.target;
1236
+ }
1237
+ }
1238
+ else {
1239
+ target = cache.target = $(target.jquery ? target : elements.target);
1240
+ }
1241
+
1242
+ // Parse the target into a jQuery object and make sure there's an element present
1243
+ target = $(target).eq(0);
1244
+ if(target.length === 0) { return self; }
1245
+
1246
+ // Check if window or document is the target
1247
+ else if(target[0] === document || target[0] === window) {
1248
+ targetWidth = PLUGINS.iOS ? window.innerWidth : target.width();
1249
+ targetHeight = PLUGINS.iOS ? window.innerHeight : target.height();
1250
+
1251
+ if(target[0] === window) {
1252
+ position = {
1253
+ top: (viewport || target).scrollTop(),
1254
+ left: (viewport || target).scrollLeft()
1255
+ };
1256
+ }
1257
+ }
1258
+
1259
+ // Use Imagemap/SVG plugins if needed
1260
+ else if(target.is('area') && PLUGINS.imagemap) {
1261
+ position = PLUGINS.imagemap(target, at, readjust.enabled ? method : FALSE);
1262
+ }
1263
+ else if(target[0].namespaceURI === 'http://www.w3.org/2000/svg' && PLUGINS.svg) {
1264
+ position = PLUGINS.svg(target, at);
1265
+ }
1266
+
1267
+ else {
1268
+ targetWidth = target.outerWidth();
1269
+ targetHeight = target.outerHeight();
1270
+
1271
+ position = PLUGINS.offset(target, container);
1272
+ }
1273
+
1274
+ // Parse returned plugin values into proper variables
1275
+ if(position.offset) {
1276
+ targetWidth = position.width;
1277
+ targetHeight = position.height;
1278
+ flipoffset = position.flipoffset;
1279
+ position = position.offset;
1280
+ }
1281
+
1282
+ // Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2 - v4.0)
1283
+ if((PLUGINS.iOS < 4.1 && PLUGINS.iOS > 3.1) || PLUGINS.iOS == 4.3 || (!PLUGINS.iOS && fixed)) {
1284
+ win = $(window);
1285
+ position.left -= win.scrollLeft();
1286
+ position.top -= win.scrollTop();
1287
+ }
1288
+
1289
+ // Adjust position relative to target
1290
+ position.left += at.x === 'right' ? targetWidth : at.x === 'center' ? targetWidth / 2 : 0;
1291
+ position.top += at.y === 'bottom' ? targetHeight : at.y === 'center' ? targetHeight / 2 : 0;
1292
+ }
1293
+
1294
+ // Adjust position relative to tooltip
1295
+ position.left += adjust.x + (my.x === 'right' ? -elemWidth : my.x === 'center' ? -elemWidth / 2 : 0);
1296
+ position.top += adjust.y + (my.y === 'bottom' ? -elemHeight : my.y === 'center' ? -elemHeight / 2 : 0);
1297
+
1298
+ // Calculate collision offset values if viewport positioning is enabled
1299
+ if(readjust.enabled) {
1300
+ // Cache our viewport details
1301
+ viewport = {
1302
+ elem: viewport,
1303
+ height: viewport[ (viewport[0] === window ? 'h' : 'outerH') + 'eight' ](),
1304
+ width: viewport[ (viewport[0] === window ? 'w' : 'outerW') + 'idth' ](),
1305
+ scrollLeft: fixed ? 0 : viewport.scrollLeft(),
1306
+ scrollTop: fixed ? 0 : viewport.scrollTop(),
1307
+ offset: viewport.offset() || { left: 0, top: 0 }
1308
+ };
1309
+ container = {
1310
+ elem: container,
1311
+ scrollLeft: container.scrollLeft(),
1312
+ scrollTop: container.scrollTop(),
1313
+ offset: container.offset() || { left: 0, top: 0 }
1314
+ };
1315
+
1316
+ // Adjust position based onviewport and adjustment options
1317
+ position.adjusted = {
1318
+ left: readjust.horizontal !== 'none' ? readjust.left(position.left) : 0,
1319
+ top: readjust.vertical !== 'none' ? readjust.top(position.top) : 0
1320
+ };
1321
+
1322
+ // Set tooltip position class
1323
+ if(position.adjusted.left + position.adjusted.top) {
1324
+ tooltip.attr('class', tooltip[0].className.replace(/ui-tooltip-pos-\w+/i, uitooltip + '-pos-' + my.abbrev()));
1325
+ }
1326
+
1327
+ // Apply flip offsets supplied by positioning plugins
1328
+ if(flipoffset && position.adjusted.left) { position.left += flipoffset.left; }
1329
+ if(flipoffset && position.adjusted.top) { position.top += flipoffset.top; }
1330
+ }
1331
+
1332
+ //Viewport adjustment is disabled, set values to zero
1333
+ else { position.adjusted = { left: 0, top: 0 }; }
1334
+
1335
+ // Call API method
1336
+ callback.originalEvent = $.extend({}, event);
1337
+ tooltip.trigger(callback, [self, position, viewport.elem || viewport]);
1338
+ if(callback.isDefaultPrevented()){ return self; }
1339
+ delete position.adjusted;
1340
+
1341
+ // If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly
1342
+ if(effect === FALSE || !visible || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) {
1343
+ tooltip.css(position);
1344
+ }
1345
+
1346
+ // Use custom function if provided
1347
+ else if($.isFunction(posOptions.effect)) {
1348
+ posOptions.effect.call(tooltip, self, $.extend({}, position));
1349
+ tooltip.queue(function(next) {
1350
+ // Reset attributes to avoid cross-browser rendering bugs
1351
+ $(this).css({ opacity: '', height: '' });
1352
+ if($.browser.msie) { this.style.removeAttribute('filter'); }
1353
+
1354
+ next();
1355
+ });
1356
+ }
1357
+
1358
+ // Set positioning flag
1359
+ isPositioning = 0;
1360
+
1361
+ return self;
1362
+ },
1363
+
1364
+ // Max/min width simulator function for all browsers.. yeaaah!
1365
+ redraw: function()
1366
+ {
1367
+ if(self.rendered < 1 || isDrawing) { return self; }
1368
+
1369
+ var container = options.position.container,
1370
+ perc, width, max, min;
1371
+
1372
+ // Set drawing flag
1373
+ isDrawing = 1;
1374
+
1375
+ // If tooltip has a set height, just set it... like a boss!
1376
+ if(options.style.height) { tooltip.css('height', options.style.height); }
1377
+
1378
+ // If tooltip has a set width, just set it... like a boss!
1379
+ if(options.style.width) { tooltip.css('width', options.style.width); }
1380
+
1381
+ // Otherwise simualte max/min width...
1382
+ else {
1383
+ // Reset width and add fluid class
1384
+ tooltip.css('width', '').addClass(fluidClass);
1385
+
1386
+ // Grab our tooltip width (add 1 so we don't get wrapping problems.. huzzah!)
1387
+ width = tooltip.width() + 1;
1388
+
1389
+ // Grab our max/min properties
1390
+ max = tooltip.css('max-width') || '';
1391
+ min = tooltip.css('min-width') || '';
1392
+
1393
+ // Parse into proper pixel values
1394
+ perc = (max + min).indexOf('%') > -1 ? container.width() / 100 : 0;
1395
+ max = ((max.indexOf('%') > -1 ? perc : 1) * parseInt(max, 10)) || width;
1396
+ min = ((min.indexOf('%') > -1 ? perc : 1) * parseInt(min, 10)) || 0;
1397
+
1398
+ // Determine new dimension size based on max/min/current values
1399
+ width = max + min ? Math.min(Math.max(width, min), max) : width;
1400
+
1401
+ // Set the newly calculated width and remvoe fluid class
1402
+ tooltip.css('width', Math.round(width)).removeClass(fluidClass);
1403
+ }
1404
+
1405
+ // Set drawing flag
1406
+ isDrawing = 0;
1407
+
1408
+ return self;
1409
+ },
1410
+
1411
+ disable: function(state)
1412
+ {
1413
+ if('boolean' !== typeof state) {
1414
+ state = !(tooltip.hasClass(disabled) || cache.disabled);
1415
+ }
1416
+
1417
+ if(self.rendered) {
1418
+ tooltip.toggleClass(disabled, state);
1419
+ $.attr(tooltip[0], 'aria-disabled', state);
1420
+ }
1421
+ else {
1422
+ cache.disabled = !!state;
1423
+ }
1424
+
1425
+ return self;
1426
+ },
1427
+
1428
+ enable: function() { return self.disable(FALSE); },
1429
+
1430
+ destroy: function()
1431
+ {
1432
+ var t = target[0],
1433
+ title = $.attr(t, oldtitle),
1434
+ elemAPI = target.data('qtip');
1435
+
1436
+ // Set flag the signify destroy is taking place to plugins
1437
+ self.destroyed = TRUE;
1438
+
1439
+ // Destroy tooltip and any associated plugins if rendered
1440
+ if(self.rendered) {
1441
+ tooltip.stop(1,0).remove();
1442
+
1443
+ $.each(self.plugins, function() {
1444
+ if(this.destroy) { this.destroy(); }
1445
+ });
1446
+ }
1447
+
1448
+ // Clear timers and remove bound events
1449
+ clearTimeout(self.timers.show);
1450
+ clearTimeout(self.timers.hide);
1451
+ unassignEvents();
1452
+
1453
+ // If the API if actually this qTip API...
1454
+ if(!elemAPI || self === elemAPI) {
1455
+ // Remove api object
1456
+ $.removeData(t, 'qtip');
1457
+
1458
+ // Reset old title attribute if removed
1459
+ if(options.suppress && title) {
1460
+ $.attr(t, 'title', title);
1461
+ target.removeAttr(oldtitle);
1462
+ }
1463
+
1464
+ // Remove ARIA attributes
1465
+ target.removeAttr('aria-describedby');
1466
+ }
1467
+
1468
+ // Remove qTip events associated with this API
1469
+ target.unbind('.qtip-'+id);
1470
+
1471
+ // Remove ID from sued id object
1472
+ delete usedIDs[self.id];
1473
+
1474
+ return target;
1475
+ }
1476
+ });
1477
+ }
1478
+
1479
+ // Initialization method
1480
+ function init(id, opts)
1481
+ {
1482
+ var obj, posOptions, attr, config, title,
1483
+
1484
+ // Setup element references
1485
+ elem = $(this),
1486
+ docBody = $(document.body),
1487
+
1488
+ // Use document body instead of document element if needed
1489
+ newTarget = this === document ? docBody : elem,
1490
+
1491
+ // Grab metadata from element if plugin is present
1492
+ metadata = (elem.metadata) ? elem.metadata(opts.metadata) : NULL,
1493
+
1494
+ // If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise
1495
+ metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL,
1496
+
1497
+ // Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,
1498
+ html5 = elem.data(opts.metadata.name || 'qtipopts');
1499
+
1500
+ // If we don't get an object returned attempt to parse it manualyl without parseJSON
1501
+ try { html5 = typeof html5 === 'string' ? (new Function("return " + html5))() : html5; }
1502
+ catch(e) { log('Unable to parse HTML5 attribute data: ' + html5); }
1503
+
1504
+ // Merge in and sanitize metadata
1505
+ config = $.extend(TRUE, {}, QTIP.defaults, opts,
1506
+ typeof html5 === 'object' ? sanitizeOptions(html5) : NULL,
1507
+ sanitizeOptions(metadata5 || metadata));
1508
+
1509
+ // Re-grab our positioning options now we've merged our metadata and set id to passed value
1510
+ posOptions = config.position;
1511
+ config.id = id;
1512
+
1513
+ // Setup missing content if none is detected
1514
+ if('boolean' === typeof config.content.text) {
1515
+ attr = elem.attr(config.content.attr);
1516
+
1517
+ // Grab from supplied attribute if available
1518
+ if(config.content.attr !== FALSE && attr) { config.content.text = attr; }
1519
+
1520
+ // No valid content was found, abort render
1521
+ else {
1522
+ log('Unable to locate content for tooltip! Aborting render of tooltip on element: ', elem);
1523
+ return FALSE;
1524
+ }
1525
+ }
1526
+
1527
+ // Setup target options
1528
+ if(!posOptions.container.length) { posOptions.container = docBody; }
1529
+ if(posOptions.target === FALSE) { posOptions.target = newTarget; }
1530
+ if(config.show.target === FALSE) { config.show.target = newTarget; }
1531
+ if(config.show.solo === TRUE) { config.show.solo = posOptions.container.closest('body'); }
1532
+ if(config.hide.target === FALSE) { config.hide.target = newTarget; }
1533
+ if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; }
1534
+
1535
+ // Ensure we only use a single container
1536
+ posOptions.container = posOptions.container.eq(0);
1537
+
1538
+ // Convert position corner values into x and y strings
1539
+ posOptions.at = new PLUGINS.Corner(posOptions.at);
1540
+ posOptions.my = new PLUGINS.Corner(posOptions.my);
1541
+
1542
+ // Destroy previous tooltip if overwrite is enabled, or skip element if not
1543
+ if($.data(this, 'qtip')) {
1544
+ if(config.overwrite) {
1545
+ elem.qtip('destroy');
1546
+ }
1547
+ else if(config.overwrite === FALSE) {
1548
+ return FALSE;
1549
+ }
1550
+ }
1551
+
1552
+ // Remove title attribute and store it if present
1553
+ if(config.suppress && (title = $.attr(this, 'title'))) {
1554
+ $(this).removeAttr('title').attr(oldtitle, title);
1555
+ }
1556
+
1557
+ // Initialize the tooltip and add API reference
1558
+ obj = new QTip(elem, config, id, !!attr);
1559
+ $.data(this, 'qtip', obj);
1560
+
1561
+ // Catch remove/removeqtip events on target element to destroy redundant tooltip
1562
+ elem.bind('remove.qtip-'+id+' removeqtip.qtip-'+id, function(){ obj.destroy(); });
1563
+
1564
+ return obj;
1565
+ }
1566
+
1567
+ // jQuery $.fn extension method
1568
+ QTIP = $.fn.qtip = function(options, notation, newValue)
1569
+ {
1570
+ var command = ('' + options).toLowerCase(), // Parse command
1571
+ returned = NULL,
1572
+ args = $.makeArray(arguments).slice(1),
1573
+ event = args[args.length - 1],
1574
+ opts = this[0] ? $.data(this[0], 'qtip') : NULL;
1575
+
1576
+ // Check for API request
1577
+ if((!arguments.length && opts) || command === 'api') {
1578
+ return opts;
1579
+ }
1580
+
1581
+ // Execute API command if present
1582
+ else if('string' === typeof options)
1583
+ {
1584
+ this.each(function()
1585
+ {
1586
+ var api = $.data(this, 'qtip');
1587
+ if(!api) { return TRUE; }
1588
+
1589
+ // Cache the event if possible
1590
+ if(event && event.timeStamp) { api.cache.event = event; }
1591
+
1592
+ // Check for specific API commands
1593
+ if((command === 'option' || command === 'options') && notation) {
1594
+ if($.isPlainObject(notation) || newValue !== undefined) {
1595
+ api.set(notation, newValue);
1596
+ }
1597
+ else {
1598
+ returned = api.get(notation);
1599
+ return FALSE;
1600
+ }
1601
+ }
1602
+
1603
+ // Execute API command
1604
+ else if(api[command]) {
1605
+ api[command].apply(api[command], args);
1606
+ }
1607
+ });
1608
+
1609
+ return returned !== NULL ? returned : this;
1610
+ }
1611
+
1612
+ // No API commands. validate provided options and setup qTips
1613
+ else if('object' === typeof options || !arguments.length)
1614
+ {
1615
+ opts = sanitizeOptions($.extend(TRUE, {}, options));
1616
+
1617
+ // Bind the qTips
1618
+ return QTIP.bind.call(this, opts, event);
1619
+ }
1620
+ };
1621
+
1622
+ // $.fn.qtip Bind method
1623
+ QTIP.bind = function(opts, event)
1624
+ {
1625
+ return this.each(function(i) {
1626
+ var options, targets, events, namespace, api, id;
1627
+
1628
+ // Find next available ID, or use custom ID if provided
1629
+ id = $.isArray(opts.id) ? opts.id[i] : opts.id;
1630
+ id = !id || id === FALSE || id.length < 1 || usedIDs[id] ? QTIP.nextid++ : (usedIDs[id] = id);
1631
+
1632
+ // Setup events namespace
1633
+ namespace = '.qtip-'+id+'-create';
1634
+
1635
+ // Initialize the qTip and re-grab newly sanitized options
1636
+ api = init.call(this, id, opts);
1637
+ if(api === FALSE) { return TRUE; }
1638
+ options = api.options;
1639
+
1640
+ // Initialize plugins
1641
+ $.each(PLUGINS, function() {
1642
+ if(this.initialize === 'initialize') { this(api); }
1643
+ });
1644
+
1645
+ // Determine hide and show targets
1646
+ targets = { show: options.show.target, hide: options.hide.target };
1647
+ events = {
1648
+ show: $.trim('' + options.show.event).replace(/ /g, namespace+' ') + namespace,
1649
+ hide: $.trim('' + options.hide.event).replace(/ /g, namespace+' ') + namespace
1650
+ };
1651
+
1652
+ /*
1653
+ * Make sure hoverIntent functions properly by using mouseleave as a hide event if
1654
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
1655
+ */
1656
+ if(/mouse(over|enter)/i.test(events.show) && !/mouse(out|leave)/i.test(events.hide)) {
1657
+ events.hide += ' mouseleave' + namespace;
1658
+ }
1659
+
1660
+ /*
1661
+ * Also make sure initial mouse targetting works correctly by caching mousemove coords
1662
+ * on show targets before the tooltip has rendered.
1663
+ *
1664
+ * Also set onTarget when triggered to keep mouse tracking working
1665
+ */
1666
+ targets.show.bind('mousemove'+namespace, function(event) {
1667
+ MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' };
1668
+ api.cache.onTarget = TRUE;
1669
+ });
1670
+
1671
+ // Define hoverIntent function
1672
+ function hoverIntent(event) {
1673
+ function render() {
1674
+ // Cache mouse coords,render and render the tooltip
1675
+ api.render(typeof event === 'object' || options.show.ready);
1676
+
1677
+ // Unbind show and hide events
1678
+ targets.show.add(targets.hide).unbind(namespace);
1679
+ }
1680
+
1681
+ // Only continue if tooltip isn't disabled
1682
+ if(api.cache.disabled) { return FALSE; }
1683
+
1684
+ // Cache the event data
1685
+ api.cache.event = $.extend({}, event);
1686
+ api.cache.target = event ? $(event.target) : [undefined];
1687
+
1688
+ // Start the event sequence
1689
+ if(options.show.delay > 0) {
1690
+ clearTimeout(api.timers.show);
1691
+ api.timers.show = setTimeout(render, options.show.delay);
1692
+ if(events.show !== events.hide) {
1693
+ targets.hide.bind(events.hide, function() { clearTimeout(api.timers.show); });
1694
+ }
1695
+ }
1696
+ else { render(); }
1697
+ }
1698
+
1699
+ // Bind show events to target
1700
+ targets.show.bind(events.show, hoverIntent);
1701
+
1702
+ // Prerendering is enabled, create tooltip now
1703
+ if(options.show.ready || options.prerender) { hoverIntent(event); }
1704
+ });
1705
+ };
1706
+
1707
+ // Setup base plugins
1708
+ PLUGINS = QTIP.plugins = {
1709
+ // Corner object parser
1710
+ Corner: function(corner) {
1711
+ corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, 'center').toLowerCase();
1712
+ this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase();
1713
+ this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase();
1714
+
1715
+ var f = corner.charAt(0); this.precedance = (f === 't' || f === 'b' ? 'y' : 'x');
1716
+
1717
+ this.string = function() { return this.precedance === 'y' ? this.y+this.x : this.x+this.y; };
1718
+ this.abbrev = function() {
1719
+ var x = this.x.substr(0,1), y = this.y.substr(0,1);
1720
+ return x === y ? x : (x === 'c' || (x !== 'c' && y !== 'c')) ? y + x : x + y;
1721
+ };
1722
+
1723
+ this.clone = function() {
1724
+ return { x: this.x, y: this.y, precedance: this.precedance, string: this.string, abbrev: this.abbrev, clone: this.clone };
1725
+ };
1726
+ },
1727
+
1728
+ // Custom (more correct for qTip!) offset calculator
1729
+ offset: function(elem, container) {
1730
+ var pos = elem.offset(),
1731
+ docBody = elem.closest('body')[0],
1732
+ parent = container, scrolled,
1733
+ coffset, overflow;
1734
+
1735
+ function scroll(e, i) {
1736
+ pos.left += i * e.scrollLeft();
1737
+ pos.top += i * e.scrollTop();
1738
+ }
1739
+
1740
+ if(parent) {
1741
+ // Compensate for non-static containers offset
1742
+ do {
1743
+ if(parent.css('position') !== 'static') {
1744
+ coffset = parent.position();
1745
+
1746
+ // Account for element positioning, borders and margins
1747
+ pos.left -= coffset.left + (parseInt(parent.css('borderLeftWidth'), 10) || 0) + (parseInt(parent.css('marginLeft'), 10) || 0);
1748
+ pos.top -= coffset.top + (parseInt(parent.css('borderTopWidth'), 10) || 0) + (parseInt(parent.css('marginTop'), 10) || 0);
1749
+
1750
+ // If this is the first parent element with an overflow of "scroll" or "auto", store it
1751
+ if(!scrolled && (overflow = parent.css('overflow')) !== 'hidden' && overflow !== 'visible') { scrolled = parent; }
1752
+ }
1753
+ }
1754
+ while((parent = $(parent[0].offsetParent)).length);
1755
+
1756
+ // Compensate for containers scroll if it also has an offsetParent
1757
+ if(scrolled && scrolled[0] !== docBody) { scroll( scrolled, 1 ); }
1758
+ }
1759
+
1760
+ return pos;
1761
+ },
1762
+
1763
+ /*
1764
+ * iOS 3.2 - 4.0 scroll fix detection used in offset() function.
1765
+ */
1766
+ iOS: parseFloat(
1767
+ ('' + (/CPU.*OS ([0-9_]{1,3})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
1768
+ .replace('undefined', '3_2').replace('_','.')
1769
+ ) || FALSE,
1770
+
1771
+ /*
1772
+ * jQuery-specific $.fn overrides
1773
+ */
1774
+ fn: {
1775
+ /* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
1776
+ attr: function(attr, val) {
1777
+ if(this.length) {
1778
+ var self = this[0],
1779
+ title = 'title',
1780
+ api = $.data(self, 'qtip');
1781
+
1782
+ if(attr === title && api && 'object' === typeof api && api.options.suppress) {
1783
+ if(arguments.length < 2) {
1784
+ return $.attr(self, oldtitle);
1785
+ }
1786
+ else {
1787
+ // If qTip is rendered and title was originally used as content, update it
1788
+ if(api && api.options.content.attr === title && api.cache.attr) {
1789
+ api.set('content.text', val);
1790
+ }
1791
+
1792
+ // Use the regular attr method to set, then cache the result
1793
+ return this.attr(oldtitle, val);
1794
+ }
1795
+ }
1796
+ }
1797
+
1798
+ return $.fn['attr'+replaceSuffix].apply(this, arguments);
1799
+ },
1800
+
1801
+ /* Allow clone to correctly retrieve cached title attributes */
1802
+ clone: function(keepData) {
1803
+ var titles = $([]), title = 'title',
1804
+
1805
+ // Clone our element using the real clone method
1806
+ elems = $.fn['clone'+replaceSuffix].apply(this, arguments);
1807
+
1808
+ // Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false
1809
+ if(!keepData) {
1810
+ elems.filter('['+oldtitle+']').attr('title', function() {
1811
+ return $.attr(this, oldtitle);
1812
+ })
1813
+ .removeAttr(oldtitle);
1814
+ }
1815
+
1816
+ return elems;
1817
+ }
1818
+ }
1819
+ };
1820
+
1821
+ // Apply the fn overrides above
1822
+ $.each(PLUGINS.fn, function(name, func) {
1823
+ if(!func || $.fn[name+replaceSuffix]) { return TRUE; }
1824
+
1825
+ var old = $.fn[name+replaceSuffix] = $.fn[name];
1826
+ $.fn[name] = function() {
1827
+ return func.apply(this, arguments) || old.apply(this, arguments);
1828
+ };
1829
+ });
1830
+
1831
+ /* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar).
1832
+ * This snippet is taken directly from jQuery UI source code found here:
1833
+ * http://code.jquery.com/ui/jquery-ui-git.js
1834
+ */
1835
+ if(!$.ui) {
1836
+ $['cleanData'+replaceSuffix] = $.cleanData;
1837
+ $.cleanData = function( elems ) {
1838
+ for(var i = 0, elem; (elem = elems[i]) !== undefined; i++) {
1839
+ try { $( elem ).triggerHandler('removeqtip'); }
1840
+ catch( e ) {}
1841
+ }
1842
+ $['cleanData'+replaceSuffix]( elems );
1843
+ };
1844
+ }
1845
+
1846
+ // Set global qTip properties
1847
+ QTIP.version = '@VERSION';
1848
+ QTIP.nextid = 0;
1849
+ QTIP.inactiveEvents = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' ');
1850
+ QTIP.zindex = 15000;
1851
+
1852
+ // Define configuration defaults
1853
+ QTIP.defaults = {
1854
+ prerender: FALSE,
1855
+ id: FALSE,
1856
+ overwrite: TRUE,
1857
+ suppress: TRUE,
1858
+ content: {
1859
+ text: TRUE,
1860
+ attr: 'title',
1861
+ title: {
1862
+ text: FALSE,
1863
+ button: FALSE
1864
+ }
1865
+ },
1866
+ position: {
1867
+ my: 'top left',
1868
+ at: 'bottom right',
1869
+ target: FALSE,
1870
+ container: FALSE,
1871
+ viewport: FALSE,
1872
+ adjust: {
1873
+ x: 0, y: 0,
1874
+ mouse: TRUE,
1875
+ resize: TRUE,
1876
+ method: 'flip flip'
1877
+ },
1878
+ effect: function(api, pos, viewport) {
1879
+ $(this).animate(pos, {
1880
+ duration: 200,
1881
+ queue: FALSE
1882
+ });
1883
+ }
1884
+ },
1885
+ show: {
1886
+ target: FALSE,
1887
+ event: 'mouseenter',
1888
+ effect: TRUE,
1889
+ delay: 90,
1890
+ solo: FALSE,
1891
+ ready: FALSE,
1892
+ autofocus: FALSE
1893
+ },
1894
+ hide: {
1895
+ target: FALSE,
1896
+ event: 'mouseleave',
1897
+ effect: TRUE,
1898
+ delay: 0,
1899
+ fixed: FALSE,
1900
+ inactive: FALSE,
1901
+ leave: 'window',
1902
+ distance: FALSE
1903
+ },
1904
+ style: {
1905
+ classes: '',
1906
+ widget: FALSE,
1907
+ width: FALSE,
1908
+ height: FALSE,
1909
+ def: TRUE
1910
+ },
1911
+ events: {
1912
+ render: NULL,
1913
+ move: NULL,
1914
+ show: NULL,
1915
+ hide: NULL,
1916
+ toggle: NULL,
1917
+ visible: NULL,
1918
+ hidden: NULL,
1919
+ focus: NULL,
1920
+ blur: NULL
1921
+ }
1922
+ };
1923
+