iscjs 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # ISCJS
2
+
3
+ This gem includes all of the JavaScript plugins we wrote at eBay ISC.
4
+
5
+ ## Installation
6
+
7
+ Add `gem iscjs` to your Gemfile
8
+
9
+ ## Usage
10
+
11
+ Version 0.1 supports the following plugins:
12
+
13
+ ez_tip with QTip2 - `//= require iscjs/qtip`
14
+
15
+ JQuery plugins:
16
+
17
+ JQuery Placeholder - `//= require iscjs/jquery/placeholder`
18
+
19
+ JQuery Shake - `//= require iscjs/jquery/shake`
20
+
21
+ JQuery Toggle Display - `//= require iscjs/jquery/toggle_display`
22
+
23
+ You can also specify all of 'em: `//= require iscjs/jquery/all`
@@ -0,0 +1 @@
1
+ //= require_tree .
@@ -0,0 +1,152 @@
1
+ class Placeholder
2
+ constructor: (@input, @options) ->
3
+ defaults =
4
+ placeholderCss: 'input-placeholder'
5
+ placeholderFocusCss: 'focus'
6
+ placeholderHiddenCss: 'with_value'
7
+ wrapperCss: 'input-wrapper'
8
+
9
+ @options = $.extend {}, defaults, @options
10
+ @_wrap()
11
+ @_createPlaceholder()
12
+ @_overrideDefVal()
13
+ @hide() if @input.val().length
14
+
15
+ hide: ->
16
+ @placeholder.addClass(@options.placeholderHiddenCss).hide()
17
+
18
+ show: ->
19
+ @placeholder.removeClass(@options.placeholderHiddenCss).show()
20
+
21
+ focus: ->
22
+ @placeholder.addClass(@options.placeholderFocusCss)
23
+ if @input.val().length
24
+ @hide()
25
+ else
26
+ @show()
27
+
28
+ keyup: -> @focus()
29
+
30
+ blur: ->
31
+ @placeholder.removeClass(@options.placeholderFocusCss)
32
+ @show() unless @input.val().length
33
+
34
+ setNewContent: (content)->
35
+ @placeholder.html(content)
36
+ @input.data("placeholder", content)
37
+
38
+ # ============================================================PRIVATES==================================================
39
+ ###
40
+ @private
41
+ ###
42
+ _wrap: ->
43
+ @wrapperDiv = $("<div class='#{@options.wrapperCss}'/>")
44
+ @input.wrapAll(@wrapperDiv)
45
+ ###
46
+ @private
47
+ ###
48
+ _createPlaceholder: ->
49
+ inputId = @input.attr("id") ? @_getRandomId()
50
+ @input.attr("id",inputId)
51
+ @_calculateMeasurements()
52
+ @placeholder =
53
+ $("<label class='#{@options.placeholderCss}' for='#{inputId}'>#{@input.attr("placeholder")}</label>")
54
+ .css(
55
+ top: @_topOffset(),
56
+ left: @_leftOffset(),
57
+ "line-height": @measurements.height + 'px',
58
+ width: @measurements.width,
59
+ height: @measurements.height)
60
+ .click(=> @input.focus())
61
+ .insertBefore(@input)
62
+ @_removeHtmlPlaceholder()
63
+
64
+ ###
65
+ @private
66
+ ###
67
+ _calculateMeasurements: ->
68
+ @clone = @input.clone().insertBefore(@input).css(
69
+ top: -99999
70
+ left: -99999
71
+ position: "absolute"
72
+ ).show()
73
+
74
+ @measurements or= {
75
+ width: @clone.css("width")
76
+ height: @clone.height() || undefined
77
+ left: {
78
+ padding:parseInt(@clone.css("padding-left").replace("px",''))
79
+ border:parseInt(@clone.css("border-left-width").replace("px",''))
80
+ margin:parseInt(@clone.css("margin-left").replace("px",''))
81
+ }
82
+ top: {
83
+ padding:parseInt(@clone.css("padding-top").replace("px",''))
84
+ border:parseInt(@clone.css("border-top-width").replace("px",''))
85
+ margin:parseInt(@clone.css("margin-top").replace("px",''))
86
+ }
87
+ }
88
+ @clone.remove()
89
+ @measurements
90
+
91
+ ###
92
+ @private
93
+ ###
94
+ _getRandomId: ->
95
+ id = ""
96
+ id = "Placeholder_#{Math.floor(Math.random()*1000)}" while id.length == 0 || $("##{id}").length > 0
97
+ id
98
+ ###
99
+ @private
100
+ ###
101
+ _removeHtmlPlaceholder: ->
102
+ @input.data("placeholder",@input.attr("placeholder"))
103
+ @input.attr("placeholder","")
104
+ ###
105
+ @private
106
+ ###
107
+ _leftOffset: ->
108
+ left = @measurements.left
109
+ left.padding + left.border + left.margin + 2
110
+ ###
111
+ @private
112
+ ###
113
+ _topOffset: ->
114
+ top = @measurements.top
115
+ top.padding + top.border + top.margin
116
+
117
+ ###
118
+ @private
119
+ ###
120
+ _overrideDefVal: ->
121
+ return if $.fn.val == @_newVal
122
+ $.fn.rVal = $.fn.val
123
+ $.fn.val = @_newVal
124
+ ###
125
+ @private
126
+ ###
127
+ _newVal: (value) ->
128
+ val = if value!=undefined then @rVal(value) else @rVal()
129
+ @trigger('change') if @parent().hasClass('input-wrapper') and value!=undefined
130
+ val
131
+
132
+
133
+ $.fn.placeholder = (options) ->
134
+ initPlaceholder = (input)->
135
+ placeholder = new Placeholder(input)
136
+ input.data("placeholderInstance", placeholder)
137
+ input
138
+ .keyup(=>placeholder.keyup())
139
+ .blur(=>placeholder.blur())
140
+ .change(=>placeholder.keyup())
141
+ .focus(=>placeholder.focus())
142
+
143
+ newContent = options
144
+ input = @[0]
145
+ $input = @
146
+ @.each ->
147
+ input = $(@)
148
+ if (newContent)
149
+ input.data("placeholderInstance").setNewContent(newContent)
150
+ else
151
+ return input.data("placeholderInstance") if input.data("placeholderInstance")?.length #prevent double creation
152
+ initPlaceholder(input)
@@ -0,0 +1,8 @@
1
+ jQuery.fn.shake = (shakes=4, distance=3, duration=400)->
2
+ @stop(true, true)
3
+ @each(->
4
+ for i in [1..shakes]
5
+ $(@).animate(left:(distance*-1), ((duration/shakes) / 4))
6
+ .animate(left:distance, ((duration/shakes) / 2 ))
7
+ .animate(left:0, ((duration/shakes) / 4))
8
+ )
@@ -0,0 +1,8 @@
1
+ # the built-in toggle method doesn't work properly in firefox and in IE.
2
+ # If an element has an original display value of inline or inline-block, then the toggle method
3
+ # will not restore its original display value but instead set it as display:block.
4
+ jQuery.fn.toggleDisplay = (condition, displayMode) ->
5
+ if condition
6
+ $(@).css("display", displayMode)
7
+ else
8
+ $(@).hide()
@@ -0,0 +1,2 @@
1
+ //= require iscjs/qtip/jquery.qtip
2
+ //= require ./tgp_qtip
@@ -0,0 +1,157 @@
1
+ # Extends the given extended by extendee:
2
+ # (1) If extendee is a JSON, extends extended using deep jQuery extend.
3
+ # (2) If extendee is an array, extends extended by the array elements expecting each to be a JSON (scneario 1)
4
+ # (3) If extendee is a string, extends extended by the static JSONs saved in jQuery.tgp_qtips
5
+ jQuery.qtipDeepExtend = (extended, extendee) ->
6
+ if typeof extendee == "object"
7
+ if extendee.length?
8
+ jQuery.qtipDeepExtend(extended, extendeeArg) for extendeeArg in extendee
9
+ else
10
+ jQuery.extend(true, extended, extendee)
11
+ else
12
+ jQuery.extend(true, extended, jQuery.tgp_qtips.qtips[extendee]) if typeof extendee == "string"
13
+
14
+ jQuery.fn.tgp_qtip = () ->
15
+ qtip_params = {}
16
+
17
+ for param in arguments
18
+ do (param) ->
19
+ jQuery.qtipDeepExtend(qtip_params, param)
20
+
21
+ @qtip(qtip_params)
22
+
23
+ jQuery.fn.error_qtip = (message, position = "above") ->
24
+ @tgp_qtip("error_red", "semi-modal", position, "big_tip",
25
+ content: message
26
+ )
27
+
28
+ jQuery.tgp_qtips = qtips:
29
+ plain_white:
30
+ style:
31
+ classes: 'ui-tooltip-light ui-tooltip-shadow'
32
+ tip:
33
+ corner: 'bottom center'
34
+ mimic: 'bottom left'
35
+
36
+ shadowed_white:
37
+ style:
38
+ classes: 'ui-tooltip-light ui-tooltip-big-shadow ui-tooltip-rounded '
39
+
40
+ error_red: # error color
41
+ style:
42
+ classes: 'ui-tooltip-red-and-white ui-tooltip-rounded ui-tooltip-shadow'
43
+
44
+ info_green:
45
+ style:
46
+ classes: 'ui-tooltip-info-green ui-tooltip-rounded ui-tooltip-shadow'
47
+
48
+ info_white:
49
+ style:
50
+ classes: 'ui-tooltip-light ui-tooltip-rounded ui-tooltip-shadow'
51
+
52
+ black:
53
+ style:
54
+ classes: "ui-tooltip-dark"
55
+
56
+ dropdown:
57
+ style:
58
+ classes: 'ui-tooltip-light ui-tooltip-shadow ui-tooltip-dropdown ui-tooltip-rounded'
59
+ tip:
60
+ offset: 40
61
+ position:
62
+ at: "bottom center"
63
+ my: "top center"
64
+
65
+ big_tip:
66
+ style:
67
+ tip:
68
+ width: 9
69
+ height: 9
70
+
71
+ huge_tip:
72
+ style:
73
+ tip:
74
+ width: 15
75
+ height: 15
76
+
77
+ map_qtip:
78
+ position:
79
+ my: 'bottom left'
80
+ style:
81
+ tip:
82
+ offset: 30
83
+
84
+ follow_mouse:
85
+ position:
86
+ target: 'mouse'
87
+ viewport: $(window)
88
+ adjust:
89
+ x: 5
90
+ y: -5
91
+ hide:
92
+ fixed: true
93
+
94
+ above: #above the element
95
+ position:
96
+ my: "bottom center"
97
+ at: "top center"
98
+
99
+ on_right: # to the right of the element
100
+ position:
101
+ my: "middle left"
102
+ at: "middle right"
103
+
104
+ on_left: # to the left of the element
105
+ position:
106
+ my: "middle right"
107
+ at: "middle left"
108
+
109
+ below: # under the element
110
+ position:
111
+ at: "bottom center"
112
+ my: "top center"
113
+
114
+ modal: # A popup that opens and stays open until someone closes it
115
+ show:
116
+ ready: true
117
+ effect: false
118
+ hide:
119
+ event: false
120
+ fixed: true
121
+
122
+ "modal-overlay":
123
+ show:
124
+ ready: true
125
+ effect: false
126
+ modal:
127
+ on: true
128
+ escape: false
129
+ blur: true
130
+ hide:
131
+ event: false
132
+ fixed: true
133
+
134
+ "semi-modal": #A popup that opens and stays open for interaction- closes when you click out side
135
+ show:
136
+ ready: true
137
+ event: false
138
+ hide:
139
+ event: "unfocus"
140
+ fixed: true
141
+ delay: 1000
142
+
143
+ "semi-modal-on-click":
144
+ show:
145
+ event: 'click'
146
+ hide:
147
+ event: "unfocus"
148
+ fixed: true
149
+ delay: 1000
150
+
151
+ hover: # Shows on hover, hides on leave
152
+ show:
153
+ event: 'mouseenter'
154
+ hide:
155
+ fixed: true
156
+ event: 'mouseleave'
157
+ delay: 200
@@ -0,0 +1 @@
1
+ //= require iscjs/qtip/jquery.qtip
data/lib/iscjs.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'active_support/dependencies'
2
+
3
+ module ISCJS
4
+ class Engine < Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,3213 @@
1
+ /*
2
+ * qTip2 - Pretty powerful tooltips
3
+ * http://craigsworks.com/projects/qtip2/
4
+ *
5
+ * Version: nightly
6
+ * Copyright 2009-2010 Craig Michael Thompson - http://craigsworks.com
7
+ *
8
+ * Dual licensed under MIT or GPLv2 licenses
9
+ * http://en.wikipedia.org/wiki/MIT_License
10
+ * http://en.wikipedia.org/wiki/GNU_General_Public_License
11
+ *
12
+ * Date: Thu Oct 20 13:41:21.0000000000 2011
13
+ */
14
+
15
+ /*jslint browser: true, onevar: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: true */
16
+ /*global window: false, jQuery: false, console: false */
17
+
18
+
19
+ (function($, window, undefined) {
20
+
21
+ "use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
22
+
23
+ // Munge the primitives - Paul Irish tip
24
+ var TRUE = true,
25
+ FALSE = false,
26
+ NULL = null,
27
+
28
+ // Shortcut vars
29
+ QTIP, PLUGINS, MOUSE,
30
+ usedIDs = {},
31
+ uitooltip = 'ui-tooltip',
32
+ widget = 'ui-widget',
33
+ disabled = 'ui-state-disabled',
34
+ selector = 'div.qtip.'+uitooltip,
35
+ defaultClass = uitooltip + '-default',
36
+ focusClass = uitooltip + '-focus',
37
+ hoverClass = uitooltip + '-hover',
38
+ fluidClass = uitooltip + '-fluid',
39
+ hideOffset = '-31000px',
40
+ replaceSuffix = '_replacedByqTip',
41
+ oldtitle = 'oldtitle',
42
+ trackingBound;
43
+
44
+ /* Thanks to Paul Irish for this one: http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/ */
45
+ function log() {
46
+ log.history = log.history || [];
47
+ log.history.push(arguments);
48
+
49
+ // Make sure console is present
50
+ if('object' === typeof console) {
51
+
52
+ // Setup console and arguments
53
+ var c = console[ console.warn ? 'warn' : 'log' ],
54
+ args = Array.prototype.slice.call(arguments), a;
55
+
56
+ // Add qTip2 marker to first argument if it's a string
57
+ if(typeof arguments[0] === 'string') { args[0] = 'qTip2: ' + args[0]; }
58
+
59
+ // Apply console.warn or .log if not supported
60
+ a = c.apply ? c.apply(console, args) : c(args);
61
+ }
62
+ }
63
+
64
+ // Option object sanitizer
65
+ function sanitizeOptions(opts)
66
+ {
67
+ var content;
68
+
69
+ if(!opts || 'object' !== typeof opts) { return FALSE; }
70
+
71
+ if('object' !== typeof opts.metadata) {
72
+ opts.metadata = {
73
+ type: opts.metadata
74
+ };
75
+ }
76
+
77
+ if('content' in opts) {
78
+ if('object' !== typeof opts.content || opts.content.jquery) {
79
+ opts.content = {
80
+ text: opts.content
81
+ };
82
+ }
83
+
84
+ content = opts.content.text || FALSE;
85
+ if(!$.isFunction(content) && ((!content && !content.attr) || content.length < 1 || ('object' === typeof content && !content.jquery))) {
86
+ opts.content.text = FALSE;
87
+ }
88
+
89
+ if('title' in opts.content) {
90
+ if('object' !== typeof opts.content.title) {
91
+ opts.content.title = {
92
+ text: opts.content.title
93
+ };
94
+ }
95
+
96
+ content = opts.content.title.text || FALSE;
97
+ if(!$.isFunction(content) && ((!content && !content.attr) || content.length < 1 || ('object' === typeof content && !content.jquery))) {
98
+ opts.content.title.text = FALSE;
99
+ }
100
+ }
101
+ }
102
+
103
+ if('position' in opts) {
104
+ if('object' !== typeof opts.position) {
105
+ opts.position = {
106
+ my: opts.position,
107
+ at: opts.position
108
+ };
109
+ }
110
+ }
111
+
112
+ if('show' in opts) {
113
+ if('object' !== typeof opts.show) {
114
+ if(opts.show.jquery) {
115
+ opts.show = { target: opts.show };
116
+ }
117
+ else {
118
+ opts.show = { event: opts.show };
119
+ }
120
+ }
121
+ }
122
+
123
+ if('hide' in opts) {
124
+ if('object' !== typeof opts.hide) {
125
+ if(opts.hide.jquery) {
126
+ opts.hide = { target: opts.hide };
127
+ }
128
+ else {
129
+ opts.hide = { event: opts.hide };
130
+ }
131
+ }
132
+ }
133
+
134
+ if('style' in opts) {
135
+ if('object' !== typeof opts.style) {
136
+ opts.style = {
137
+ classes: opts.style
138
+ };
139
+ }
140
+ }
141
+
142
+ // Sanitize plugin options
143
+ $.each(PLUGINS, function() {
144
+ if(this.sanitize) { this.sanitize(opts); }
145
+ });
146
+
147
+ return opts;
148
+ }
149
+
150
+ /*
151
+ * Core plugin implementation
152
+ */
153
+ function QTip(target, options, id, attr)
154
+ {
155
+ // Declare this reference
156
+ var self = this,
157
+ docBody = document.body,
158
+ tooltipID = uitooltip + '-' + id,
159
+ isPositioning = 0,
160
+ isDrawing = 0,
161
+ tooltip = $(),
162
+ namespace = '.qtip-' + id,
163
+ elements, cache;
164
+
165
+ // Setup class attributes
166
+ self.id = id;
167
+ self.rendered = FALSE;
168
+ self.elements = elements = { target: target };
169
+ self.timers = { img: {} };
170
+ self.options = options;
171
+ self.checks = {};
172
+ self.plugins = {};
173
+ self.cache = cache = {
174
+ event: {},
175
+ target: $(),
176
+ disabled: FALSE,
177
+ attr: attr
178
+ };
179
+
180
+ /*
181
+ * Private core functions
182
+ */
183
+ function convertNotation(notation)
184
+ {
185
+ var i = 0, obj, option = options,
186
+
187
+ // Split notation into array
188
+ levels = notation.split('.');
189
+
190
+ // Loop through
191
+ while( option = option[ levels[i++] ] ) {
192
+ if(i < levels.length) { obj = option; }
193
+ }
194
+
195
+ return [obj || options, levels.pop()];
196
+ }
197
+
198
+ function setWidget() {
199
+ var on = options.style.widget;
200
+
201
+ tooltip.toggleClass(widget, on).toggleClass(defaultClass, !on);
202
+ elements.content.toggleClass(widget+'-content', on);
203
+
204
+ if(elements.titlebar){
205
+ elements.titlebar.toggleClass(widget+'-header', on);
206
+ }
207
+ if(elements.button){
208
+ elements.button.toggleClass(uitooltip+'-icon', !on);
209
+ }
210
+ }
211
+
212
+ function removeTitle(reposition)
213
+ {
214
+ if(elements.title) {
215
+ elements.titlebar.remove();
216
+ elements.titlebar = elements.title = elements.button = NULL;
217
+
218
+ // Reposition if enabled
219
+ if(reposition !== FALSE) { self.reposition(); }
220
+ }
221
+ }
222
+
223
+ function createButton()
224
+ {
225
+ var button = options.content.title.button,
226
+ isString = typeof button === 'string',
227
+ close = isString ? button : 'Close tooltip';
228
+
229
+ if(elements.button) { elements.button.remove(); }
230
+
231
+ // Use custom button if one was supplied by user, else use default
232
+ if(button.jquery) {
233
+ elements.button = button;
234
+ }
235
+ else {
236
+ elements.button = $('<a />', {
237
+ 'class': 'ui-state-default ' + (options.style.widget ? '' : uitooltip+'-icon'),
238
+ 'title': close,
239
+ 'aria-label': close
240
+ })
241
+ .prepend(
242
+ $('<span />', {
243
+ 'class': 'ui-icon ui-icon-close',
244
+ 'html': '&times;'
245
+ })
246
+ );
247
+ }
248
+
249
+ // Create button and setup attributes
250
+ elements.button.appendTo(elements.titlebar)
251
+ .attr('role', 'button')
252
+ .click(function(event) {
253
+ if(!tooltip.hasClass(disabled)) { self.hide(event); }
254
+ return FALSE;
255
+ });
256
+
257
+ // Redraw the tooltip when we're done
258
+ self.redraw();
259
+ }
260
+
261
+ function createTitle()
262
+ {
263
+ var id = tooltipID+'-title';
264
+
265
+ // Destroy previous title element, if present
266
+ if(elements.titlebar) { removeTitle(); }
267
+
268
+ // Create title bar and title elements
269
+ elements.titlebar = $('<div />', {
270
+ 'class': uitooltip + '-titlebar ' + (options.style.widget ? 'ui-widget-header' : '')
271
+ })
272
+ .append(
273
+ elements.title = $('<div />', {
274
+ 'id': id,
275
+ 'class': uitooltip + '-title',
276
+ 'aria-atomic': TRUE
277
+ })
278
+ )
279
+ .insertBefore(elements.content)
280
+
281
+ // Button-specific events
282
+ .delegate('.ui-state-default', 'mousedown keydown mouseup keyup mouseout', function(event) {
283
+ $(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down');
284
+ })
285
+ .delegate('.ui-state-default', 'mouseover mouseout', function(event){
286
+ $(this).toggleClass('ui-state-hover', event.type === 'mouseover');
287
+ });
288
+
289
+ // Create button if enabled
290
+ if(options.content.title.button) { createButton(); }
291
+
292
+
293
+ // Redraw the tooltip dimensions if it's rendered
294
+ else if(self.rendered){ self.redraw(); }
295
+ }
296
+
297
+ function updateButton(button)
298
+ {
299
+ var elem = elements.button,
300
+ title = elements.title;
301
+
302
+ // Make sure tooltip is rendered and if not, return
303
+ if(!self.rendered) { return FALSE; }
304
+
305
+ if(!button) {
306
+ elem.remove();
307
+ }
308
+ else {
309
+ if(!title) {
310
+ createTitle();
311
+ }
312
+ createButton();
313
+ }
314
+ }
315
+
316
+ function updateTitle(content, reposition)
317
+ {
318
+ var elem = elements.title;
319
+
320
+ // Make sure tooltip is rendered and if not, return
321
+ if(!self.rendered || !content) { return FALSE; }
322
+
323
+ // Use function to parse content
324
+ if($.isFunction(content)) {
325
+ content = content.call(target, cache.event, self);
326
+ }
327
+
328
+ // Remove title if callback returns false
329
+ if(content === FALSE) { return removeTitle(FALSE); }
330
+
331
+ // Append new content if its a DOM array and show it if hidden
332
+ else if(content.jquery && content.length > 0) {
333
+ elem.empty().append(content.css({ display: 'block' }));
334
+ }
335
+
336
+ // Content is a regular string, insert the new content
337
+ else { elem.html(content); }
338
+
339
+ // Redraw and reposition
340
+ self.redraw();
341
+ if(reposition !== FALSE && self.rendered && tooltip.is(':visible')) {
342
+ self.reposition(cache.event);
343
+ }
344
+ }
345
+
346
+ function updateContent(content, reposition)
347
+ {
348
+ var elem = elements.content;
349
+
350
+ // Make sure tooltip is rendered and content is defined. If not return
351
+ if(!self.rendered || !content) { return FALSE; }
352
+
353
+ // Use function to parse content
354
+ if($.isFunction(content)) {
355
+ content = content.call(target, cache.event, self) || '';
356
+ }
357
+
358
+ // Append new content if its a DOM array and show it if hidden
359
+ if(content.jquery && content.length > 0) {
360
+ elem.empty().append(content.css({ display: 'block' }));
361
+ }
362
+
363
+ // Content is a regular string, insert the new content
364
+ else { elem.html(content); }
365
+
366
+ // Image detection
367
+ function detectImages(next) {
368
+ var images, srcs = {};
369
+
370
+ function imageLoad(image) {
371
+ // Clear src from object and any timers and events associated with the image
372
+ if(image) {
373
+ delete srcs[image.src];
374
+ clearTimeout(self.timers.img[image.src]);
375
+ $(image).unbind(namespace);
376
+ }
377
+
378
+ // If queue is empty after image removal, update tooltip and continue the queue
379
+ if($.isEmptyObject(srcs)) {
380
+ self.redraw();
381
+ if(reposition !== FALSE) {
382
+ self.reposition(cache.event);
383
+ }
384
+
385
+ next();
386
+ }
387
+ }
388
+
389
+ // Find all content images without dimensions, and if no images were found, continue
390
+ if((images = elem.find('img:not([height]):not([width])')).length === 0) { return imageLoad(); }
391
+
392
+ // Apply timer to each image to poll for dimensions
393
+ images.each(function(i, elem) {
394
+ // Skip if the src is already present
395
+ if(srcs[elem.src] !== undefined) { return; }
396
+
397
+ // Keep track of how many times we poll for image dimensions.
398
+ // If it doesn't return in a reasonable amount of time, it's better
399
+ // to display the tooltip, rather than hold up the queue.
400
+ var iterations = 0, maxIterations = 3;
401
+
402
+ (function timer(){
403
+ // When the dimensions are found, remove the image from the queue
404
+ if(elem.height || elem.width || (iterations > maxIterations)) { return imageLoad(elem); }
405
+
406
+ iterations += 1;
407
+
408
+ // Restart timer
409
+ self.timers.img[elem.src] = setTimeout(timer, 700);
410
+ }());
411
+
412
+ // Also apply regular load/error event handlers
413
+ $(elem).bind('error'+namespace+' load'+namespace, function(){ imageLoad(this); });
414
+
415
+ // Store the src and element in our object
416
+ srcs[elem.src] = elem;
417
+ });
418
+ }
419
+
420
+ /*
421
+ * If we're still rendering... insert into 'fx' queue our image dimension
422
+ * checker which will halt the showing of the tooltip until image dimensions
423
+ * can be detected properly.
424
+ */
425
+ if(self.rendered < 0) { tooltip.queue('fx', detectImages); }
426
+
427
+ // We're fully rendered, so reset isDrawing flag and proceed without queue delay
428
+ else { isDrawing = 0; detectImages($.noop); }
429
+
430
+ return self;
431
+ }
432
+
433
+ function assignEvents()
434
+ {
435
+ var posOptions = options.position,
436
+ targets = {
437
+ show: options.show.target,
438
+ hide: options.hide.target,
439
+ viewport: $(posOptions.viewport),
440
+ document: $(document),
441
+ window: $(window)
442
+ },
443
+ events = {
444
+ show: $.trim('' + options.show.event).split(' '),
445
+ hide: $.trim('' + options.hide.event).split(' ')
446
+ },
447
+ IE6 = $.browser.msie && parseInt($.browser.version, 10) === 6;
448
+
449
+ // Define show event method
450
+ function showMethod(event)
451
+ {
452
+ if(tooltip.hasClass(disabled)) { return FALSE; }
453
+
454
+ // If set, hide tooltip when inactive for delay period
455
+ targets.show.trigger('qtip-'+id+'-inactive');
456
+
457
+ // Clear hide timers
458
+ clearTimeout(self.timers.show);
459
+ clearTimeout(self.timers.hide);
460
+
461
+ // Start show timer
462
+ var callback = function(){ self.toggle(TRUE, event); };
463
+ if(options.show.delay > 0) {
464
+ self.timers.show = setTimeout(callback, options.show.delay);
465
+ }
466
+ else{ callback(); }
467
+ }
468
+
469
+ // Define hide method
470
+ function hideMethod(event)
471
+ {
472
+ if(tooltip.hasClass(disabled) || isPositioning || isDrawing) { return FALSE; }
473
+
474
+ // Check if new target was actually the tooltip element
475
+ var relatedTarget = $(event.relatedTarget || event.target),
476
+ ontoTooltip = relatedTarget.closest(selector)[0] === tooltip[0],
477
+ ontoTarget = relatedTarget[0] === targets.show[0];
478
+
479
+ // Clear timers and stop animation queue
480
+ clearTimeout(self.timers.show);
481
+ clearTimeout(self.timers.hide);
482
+
483
+ // Prevent hiding if tooltip is fixed and event target is the tooltip. Or if mouse positioning is enabled and cursor momentarily overlaps
484
+ if((posOptions.target === 'mouse' && ontoTooltip) || (options.hide.fixed && ((/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget)))) {
485
+ try { event.preventDefault(); event.stopImmediatePropagation(); } catch(e) {} return;
486
+ }
487
+
488
+ // If tooltip has displayed, start hide timer
489
+ if(options.hide.delay > 0) {
490
+ self.timers.hide = setTimeout(function(){ self.hide(event); }, options.hide.delay);
491
+ }
492
+ else{ self.hide(event); }
493
+ }
494
+
495
+ // Define inactive method
496
+ function inactiveMethod(event)
497
+ {
498
+ if(tooltip.hasClass(disabled)) { return FALSE; }
499
+
500
+ // Clear timer
501
+ clearTimeout(self.timers.inactive);
502
+ self.timers.inactive = setTimeout(function(){ self.hide(event); }, options.hide.inactive);
503
+ }
504
+
505
+ function repositionMethod(event) {
506
+ if(tooltip.is(':visible')) { self.reposition(event); }
507
+ }
508
+
509
+ // On mouseenter/mouseleave...
510
+ tooltip.bind('mouseenter'+namespace+' mouseleave'+namespace, function(event) {
511
+ var state = event.type === 'mouseenter';
512
+
513
+ // Focus the tooltip on mouseenter (z-index stacking)
514
+ if(state) { self.focus(event); }
515
+
516
+ // Add hover class
517
+ tooltip.toggleClass(hoverClass, state);
518
+ });
519
+
520
+ // Enable hide.fixed
521
+ if(options.hide.fixed) {
522
+ // Add tooltip as a hide target
523
+ targets.hide = targets.hide.add(tooltip);
524
+
525
+ // Clear hide timer on tooltip hover to prevent it from closing
526
+ tooltip.bind('mouseover'+namespace, function() {
527
+ if(!tooltip.hasClass(disabled)) { clearTimeout(self.timers.hide); }
528
+ });
529
+ }
530
+
531
+ // If using mouseout/mouseleave as a hide event...
532
+ if(/mouse(out|leave)/i.test(options.hide.event)) {
533
+ // Hide tooltips when leaving current window/frame (but not select/option elements)
534
+ if(options.hide.leave === 'window') {
535
+ targets.window.bind('mouseout' + namespace, function(event) {
536
+ if(/select|option/.test(event.target) && !event.relatedTarget) { self.hide(event); }
537
+ });
538
+ }
539
+ }
540
+
541
+ /*
542
+ * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
543
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
544
+ */
545
+ else if(/mouse(over|enter)/i.test(options.show.event)) {
546
+ targets.hide.bind('mouseleave'+namespace, function(event) {
547
+ clearTimeout(self.timers.show);
548
+ });
549
+ }
550
+
551
+ // Hide tooltip on document mousedown if unfocus events are enabled
552
+ if(('' + options.hide.event).indexOf('unfocus') > -1) {
553
+ targets.document.bind('mousedown'+namespace, function(event) {
554
+ var $target = $(event.target),
555
+ enabled = !tooltip.hasClass(disabled) && tooltip.is(':visible');
556
+
557
+ if($target[0] !== tooltip[0] && $target.parents(selector).length === 0 && $target.add(target).length > 1 && !$target.is(':disabled')) {
558
+ self.hide(event);
559
+ }
560
+ });
561
+ }
562
+
563
+ // Check if the tooltip hides when inactive
564
+ if('number' === typeof options.hide.inactive) {
565
+ // Bind inactive method to target as a custom event
566
+ targets.show.bind('qtip-'+id+'-inactive', inactiveMethod);
567
+
568
+ // Define events which reset the 'inactive' event handler
569
+ $.each(QTIP.inactiveEvents, function(index, type){
570
+ targets.hide.add(elements.tooltip).bind(type+namespace+'-inactive', inactiveMethod);
571
+ });
572
+ }
573
+
574
+ // Apply hide events
575
+ $.each(events.hide, function(index, type) {
576
+ var showIndex = $.inArray(type, events.show),
577
+ targetHide = $(targets.hide);
578
+
579
+ // Both events and targets are identical, apply events using a toggle
580
+ if((showIndex > -1 && targetHide.add(targets.show).length === targetHide.length) || type === 'unfocus')
581
+ {
582
+ targets.show.bind(type+namespace, function(event) {
583
+ if(tooltip.is(':visible')) { hideMethod(event); }
584
+ else { showMethod(event); }
585
+ });
586
+
587
+ // Don't bind the event again
588
+ delete events.show[ showIndex ];
589
+ }
590
+
591
+ // Events are not identical, bind normally
592
+ else { targets.hide.bind(type+namespace, hideMethod); }
593
+ });
594
+
595
+ // Apply show events
596
+ $.each(events.show, function(index, type) {
597
+ targets.show.bind(type+namespace, showMethod);
598
+ });
599
+
600
+ // Check if the tooltip hides when mouse is moved a certain distance
601
+ if('number' === typeof options.hide.distance) {
602
+ // Bind mousemove to target to detect distance difference
603
+ targets.show.add(tooltip).bind('mousemove'+namespace, function(event) {
604
+ var origin = cache.origin || {},
605
+ limit = options.hide.distance,
606
+ abs = Math.abs;
607
+
608
+ // Check if the movement has gone beyond the limit, and hide it if so
609
+ if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) {
610
+ self.hide(event);
611
+ }
612
+ });
613
+ }
614
+
615
+ // Mouse positioning events
616
+ if(posOptions.target === 'mouse') {
617
+ // Cache mousemove coords on show targets
618
+ targets.show.bind('mousemove'+namespace, function(event) {
619
+ MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' };
620
+ });
621
+
622
+ // If mouse adjustment is on...
623
+ if(posOptions.adjust.mouse) {
624
+ // Apply a mouseleave event so we don't get problems with overlapping
625
+ if(options.hide.event) {
626
+ tooltip.bind('mouseleave'+namespace, function(event) {
627
+ if((event.relatedTarget || event.target) !== targets.show[0]) { self.hide(event); }
628
+ });
629
+ }
630
+
631
+ // Update tooltip position on mousemove
632
+ targets.document.bind('mousemove'+namespace, function(event) {
633
+ // Update the tooltip position only if the tooltip is visible and adjustment is enabled
634
+ if(!tooltip.hasClass(disabled) && tooltip.is(':visible')) {
635
+ self.reposition(event || MOUSE);
636
+ }
637
+ });
638
+ }
639
+ }
640
+
641
+ // Adjust positions of the tooltip on window resize if enabled
642
+ if(posOptions.adjust.resize || targets.viewport.length) {
643
+ ($.event.special.resize ? targets.viewport : targets.window).bind('resize'+namespace, repositionMethod);
644
+ }
645
+
646
+ // Adjust tooltip position on scroll if screen adjustment is enabled
647
+ if(targets.viewport.length || (IE6 && tooltip.css('position') === 'fixed')) {
648
+ targets.viewport.bind('scroll'+namespace, repositionMethod);
649
+ }
650
+ }
651
+
652
+ function unassignEvents()
653
+ {
654
+ var targets = [
655
+ options.show.target[0],
656
+ options.hide.target[0],
657
+ self.rendered && elements.tooltip[0],
658
+ options.position.container[0],
659
+ options.position.viewport[0],
660
+ window,
661
+ document
662
+ ];
663
+
664
+ // Check if tooltip is rendered
665
+ if(self.rendered) {
666
+ $([]).pushStack( $.grep(targets, function(i){ return typeof i === 'object'; }) ).unbind(namespace);
667
+ }
668
+
669
+ // Tooltip isn't yet rendered, remove render event
670
+ else { options.show.target.unbind(namespace+'-create'); }
671
+ }
672
+
673
+ // Setup builtin .set() option checks
674
+ self.checks.builtin = {
675
+ // Core checks
676
+ '^id$': function(obj, o, v) {
677
+ var id = v === TRUE ? QTIP.nextid : v,
678
+ tooltipID = uitooltip + '-' + id;
679
+
680
+ if(id !== FALSE && id.length > 0 && !$('#'+tooltipID).length) {
681
+ tooltip[0].id = tooltipID;
682
+ elements.content[0].id = tooltipID + '-content';
683
+ elements.title[0].id = tooltipID + '-title';
684
+ }
685
+ },
686
+
687
+ // Content checks
688
+ '^content.text$': function(obj, o, v){ updateContent(v); },
689
+ '^content.title.text$': function(obj, o, v) {
690
+ // Remove title if content is null
691
+ if(!v) { return removeTitle(); }
692
+
693
+ // If title isn't already created, create it now and update
694
+ if(!elements.title && v) { createTitle(); }
695
+ updateTitle(v);
696
+ },
697
+ '^content.title.button$': function(obj, o, v){ updateButton(v); },
698
+
699
+ // Position checks
700
+ '^position.(my|at)$': function(obj, o, v){
701
+ // Parse new corner value into Corner objecct
702
+ if('string' === typeof v) {
703
+ obj[o] = new PLUGINS.Corner(v);
704
+ }
705
+ },
706
+ '^position.container$': function(obj, o, v){
707
+ if(self.rendered) { tooltip.appendTo(v); }
708
+ },
709
+
710
+ // Show checks
711
+ '^show.ready$': function() {
712
+ if(!self.rendered) { self.render(1); }
713
+ else { self.toggle(TRUE); }
714
+ },
715
+
716
+ // Style checks
717
+ '^style.classes$': function(obj, o, v) {
718
+ tooltip.attr('class', uitooltip + ' qtip ui-helper-reset ' + v);
719
+ },
720
+ '^style.widget|content.title': setWidget,
721
+
722
+ // Events check
723
+ '^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
724
+ tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
725
+ },
726
+
727
+ // Properties which require event reassignment
728
+ '^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() {
729
+ var posOptions = options.position;
730
+
731
+ // Set tracking flag
732
+ tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse);
733
+
734
+ // Reassign events
735
+ unassignEvents(); assignEvents();
736
+ }
737
+ };
738
+
739
+ /*
740
+ * Public API methods
741
+ */
742
+ $.extend(self, {
743
+ render: function(show)
744
+ {
745
+ if(self.rendered) { return self; } // If tooltip has already been rendered, exit
746
+
747
+ var text = options.content.text,
748
+ title = options.content.title.text,
749
+ posOptions = options.position,
750
+ callback = $.Event('tooltiprender');
751
+
752
+ // Add ARIA attributes to target
753
+ $.attr(target[0], 'aria-describedby', tooltipID);
754
+
755
+ // Create tooltip element
756
+ tooltip = elements.tooltip = $('<div/>', {
757
+ 'id': tooltipID,
758
+ 'class': uitooltip + ' qtip ui-helper-reset ' + defaultClass + ' ' + options.style.classes + ' '+ uitooltip + '-pos-' + options.position.my.abbreviation(),
759
+ 'width': options.style.width || '',
760
+ 'height': options.style.height || '',
761
+ 'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse,
762
+
763
+ /* ARIA specific attributes */
764
+ 'role': 'alert',
765
+ 'aria-live': 'polite',
766
+ 'aria-atomic': FALSE,
767
+ 'aria-describedby': tooltipID + '-content',
768
+ 'aria-hidden': TRUE
769
+ })
770
+ .toggleClass(disabled, cache.disabled)
771
+ .data('qtip', self)
772
+ .appendTo(options.position.container)
773
+ .append(
774
+ // Create content element
775
+ elements.content = $('<div />', {
776
+ 'class': uitooltip + '-content',
777
+ 'id': tooltipID + '-content',
778
+ 'aria-atomic': TRUE
779
+ })
780
+ );
781
+
782
+ // Set rendered flag and prevent redundant redraw/reposition calls for now
783
+ self.rendered = -1;
784
+ isDrawing = 1; isPositioning = 1;
785
+
786
+ // Create title...
787
+ if(title) {
788
+ createTitle();
789
+
790
+ // Update title only if its not a callback (called in toggle if so)
791
+ if(!$.isFunction(title)) { updateTitle(title, FALSE); }
792
+ }
793
+
794
+ // Set proper rendered flag and update content if not a callback function (called in toggle)
795
+ if(!$.isFunction(text)) { updateContent(text, FALSE); }
796
+ self.rendered = TRUE;
797
+
798
+ // Setup widget classes
799
+ setWidget();
800
+
801
+ // Assign passed event callbacks (before plugins!)
802
+ $.each(options.events, function(name, callback) {
803
+ if($.isFunction(callback)) {
804
+ tooltip.bind(name === 'toggle' ? 'tooltipshow tooltiphide' : 'tooltip'+name, callback);
805
+ }
806
+ });
807
+
808
+ // Initialize 'render' plugins
809
+ $.each(PLUGINS, function() {
810
+ if(this.initialize === 'render') { this(self); }
811
+ });
812
+
813
+ // Assign events
814
+ assignEvents();
815
+
816
+ /* Queue this part of the render process in our fx queue so we can
817
+ * load images before the tooltip renders fully.
818
+ *
819
+ * See: updateContent method
820
+ */
821
+ tooltip.queue('fx', function(next) {
822
+ // Trigger tooltiprender event and pass original triggering event as original
823
+ callback.originalEvent = cache.event;
824
+ tooltip.trigger(callback, [self]);
825
+
826
+ // Reset flags
827
+ isDrawing = 0; isPositioning = 0;
828
+
829
+ // Redraw the tooltip manually now we're fully rendered
830
+ self.redraw();
831
+
832
+ // Show tooltip if needed
833
+ if(options.show.ready || show) {
834
+ self.toggle(TRUE, cache.event);
835
+ }
836
+
837
+ next(); // Move on to next method in queue
838
+ });
839
+
840
+ return self;
841
+ },
842
+
843
+ get: function(notation)
844
+ {
845
+ var result, o;
846
+
847
+ switch(notation.toLowerCase())
848
+ {
849
+ case 'dimensions':
850
+ result = {
851
+ height: tooltip.outerHeight(), width: tooltip.outerWidth()
852
+ };
853
+ break;
854
+
855
+ case 'offset':
856
+ result = PLUGINS.offset(tooltip, options.position.container);
857
+ break;
858
+
859
+ default:
860
+ o = convertNotation(notation.toLowerCase());
861
+ result = o[0][ o[1] ];
862
+ result = result.precedance ? result.string() : result;
863
+ break;
864
+ }
865
+
866
+ return result;
867
+ },
868
+
869
+ set: function(option, value)
870
+ {
871
+ var rmove = /^position\.(my|at|adjust|target|container)|style|content|show\.ready/i,
872
+ rdraw = /^content\.(title|attr)|style/i,
873
+ reposition = FALSE,
874
+ redraw = FALSE,
875
+ checks = self.checks,
876
+ name;
877
+
878
+ function callback(notation, args) {
879
+ var category, rule, match;
880
+
881
+ for(category in checks) {
882
+ for(rule in checks[category]) {
883
+ if(match = (new RegExp(rule, 'i')).exec(notation)) {
884
+ args.push(match);
885
+ checks[category][rule].apply(self, args);
886
+ }
887
+ }
888
+ }
889
+ }
890
+
891
+ // Convert singular option/value pair into object form
892
+ if('string' === typeof option) {
893
+ name = option; option = {}; option[name] = value;
894
+ }
895
+ else { option = $.extend(TRUE, {}, option); }
896
+
897
+ // Set all of the defined options to their new values
898
+ $.each(option, function(notation, value) {
899
+ var obj = convertNotation( notation.toLowerCase() ), previous;
900
+
901
+ // Set new obj value
902
+ previous = obj[0][ obj[1] ];
903
+ obj[0][ obj[1] ] = 'object' === typeof value && value.nodeType ? $(value) : value;
904
+
905
+ // Set the new params for the callback
906
+ option[notation] = [obj[0], obj[1], value, previous];
907
+
908
+ // Also check if we need to reposition / redraw
909
+ reposition = rmove.test(notation) || reposition;
910
+ redraw = rdraw.test(notation) || redraw;
911
+ });
912
+
913
+ // Re-sanitize options
914
+ sanitizeOptions(options);
915
+
916
+ /*
917
+ * Execute any valid callbacks for the set options
918
+ * Also set isPositioning/isDrawing so we don't get loads of redundant repositioning
919
+ * and redraw calls.
920
+ */
921
+ isPositioning = isDrawing = 1; $.each(option, callback); isPositioning = isDrawing = 0;
922
+
923
+ // Update position / redraw if needed
924
+ if(tooltip.is(':visible') && self.rendered) {
925
+ if(reposition) {
926
+ self.reposition( options.position.target === 'mouse' ? NULL : cache.event );
927
+ }
928
+ if(redraw) { self.redraw(); }
929
+ }
930
+
931
+ return self;
932
+ },
933
+
934
+ toggle: function(state, event)
935
+ {
936
+ // Make sure tooltip is rendered
937
+ if(!self.rendered) {
938
+ if(state) { self.render(0); } // Render the tooltip if showing and it isn't already
939
+ else { return self; }
940
+ }
941
+
942
+ var type = state ? 'show' : 'hide',
943
+ opts = options[type],
944
+ visible = tooltip.is(':visible'),
945
+ sameTarget = !event || options[type].target.length < 2 || cache.target[0] === event.target,
946
+ posOptions = options.position,
947
+ contentOptions = options.content,
948
+ delay,
949
+ callback;
950
+
951
+ // Detect state if valid one isn't provided
952
+ if((typeof state).search('boolean|number')) { state = !visible; }
953
+
954
+ // Return if element is already in correct state
955
+ if(!tooltip.is(':animated') && visible === state && sameTarget) { return self; }
956
+
957
+ // Try to prevent flickering when tooltip overlaps show element
958
+ if(event) {
959
+ if((/over|enter/).test(event.type) && (/out|leave/).test(cache.event.type) &&
960
+ event.target === options.show.target[0] && tooltip.has(event.relatedTarget).length) {
961
+ return self;
962
+ }
963
+
964
+ // Cache event
965
+ cache.event = $.extend({}, event);
966
+ }
967
+
968
+ // Call API methods
969
+ callback = $.Event('tooltip'+type);
970
+ callback.originalEvent = event ? cache.event : NULL;
971
+ tooltip.trigger(callback, [self, 90]);
972
+ if(callback.isDefaultPrevented()){ return self; }
973
+
974
+ // Set ARIA hidden status attribute
975
+ $.attr(tooltip[0], 'aria-hidden', !!!state);
976
+
977
+ // Execute state specific properties
978
+ if(state) {
979
+ // Store show origin coordinates
980
+ cache.origin = $.extend({}, MOUSE);
981
+
982
+ // Focus the tooltip
983
+ self.focus(event);
984
+
985
+ // Update tooltip content & title if it's a dynamic function
986
+ if($.isFunction(contentOptions.text)) { updateContent(contentOptions.text, FALSE); }
987
+ if($.isFunction(contentOptions.title.text)) { updateTitle(contentOptions.title.text, FALSE); }
988
+
989
+ // Cache mousemove events for positioning purposes (if not already tracking)
990
+ if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) {
991
+ $(document).bind('mousemove.qtip', function(event) {
992
+ MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' };
993
+ });
994
+ trackingBound = TRUE;
995
+ }
996
+
997
+ // Update the tooltip position
998
+ self.reposition(event);
999
+
1000
+ // Hide other tooltips if tooltip is solo, using it as the context
1001
+ if((callback.solo = !!opts.solo)) { $(selector, opts.solo).not(tooltip).qtip('hide', callback); }
1002
+ }
1003
+ else {
1004
+ // Clear show timer if we're hiding
1005
+ clearTimeout(self.timers.show);
1006
+
1007
+ // Remove cached origin on hide
1008
+ delete cache.origin;
1009
+
1010
+ // Remove mouse tracking event if not needed (all tracking qTips are hidden)
1011
+ if(trackingBound && !$(selector+'[tracking="true"]:visible', opts.solo).not(tooltip).length) {
1012
+ $(document).unbind('mousemove.qtip');
1013
+ trackingBound = FALSE;
1014
+ }
1015
+
1016
+ // Blur the tooltip
1017
+ self.blur(event);
1018
+ }
1019
+
1020
+ // Define post-animation, state specific properties
1021
+ function after() {
1022
+ if(state) {
1023
+ // Prevent antialias from disappearing in IE by removing filter
1024
+ if($.browser.msie) { tooltip[0].style.removeAttribute('filter'); }
1025
+
1026
+ // Remove overflow setting to prevent tip bugs
1027
+ tooltip.css('overflow', '');
1028
+
1029
+ // Autofocus elements if enabled
1030
+ if('string' === typeof opts.autofocus) {
1031
+ $(opts.autofocus, tooltip).focus();
1032
+ }
1033
+
1034
+ // Call API method
1035
+ callback = $.Event('tooltipvisible');
1036
+ callback.originalEvent = event ? cache.event : NULL;
1037
+ tooltip.trigger(callback, [self]);
1038
+ }
1039
+ else {
1040
+ // Reset CSS states
1041
+ tooltip.css({
1042
+ display: '',
1043
+ visibility: '',
1044
+ opacity: '',
1045
+ left: '',
1046
+ top: ''
1047
+ });
1048
+ }
1049
+ }
1050
+
1051
+ // Clear animation queue if same target
1052
+ if(sameTarget) { tooltip.stop(0, 1); }
1053
+
1054
+ // If no effect type is supplied, use a simple toggle
1055
+ if(opts.effect === FALSE) {
1056
+ tooltip[ type ]();
1057
+ after.call(tooltip);
1058
+ }
1059
+
1060
+ // Use custom function if provided
1061
+ else if($.isFunction(opts.effect)) {
1062
+ opts.effect.call(tooltip, self);
1063
+ tooltip.queue('fx', function(n){ after(); n(); });
1064
+ }
1065
+
1066
+ // Use basic fade function by default
1067
+ else { tooltip.fadeTo(90, state ? 1 : 0, after); }
1068
+
1069
+ // If inactive hide method is set, active it
1070
+ if(state) { opts.target.trigger('qtip-'+id+'-inactive'); }
1071
+
1072
+ return self;
1073
+ },
1074
+
1075
+ show: function(event){ return self.toggle(TRUE, event); },
1076
+
1077
+ hide: function(event){ return self.toggle(FALSE, event); },
1078
+
1079
+ focus: function(event)
1080
+ {
1081
+ if(!self.rendered) { return self; }
1082
+
1083
+ var qtips = $(selector),
1084
+ curIndex = parseInt(tooltip[0].style.zIndex, 10),
1085
+ newIndex = QTIP.zindex + qtips.length,
1086
+ cachedEvent = $.extend({}, event),
1087
+ focusedElem, callback;
1088
+
1089
+ // Only update the z-index if it has changed and tooltip is not already focused
1090
+ if(!tooltip.hasClass(focusClass))
1091
+ {
1092
+ // Call API method
1093
+ callback = $.Event('tooltipfocus');
1094
+ callback.originalEvent = cachedEvent;
1095
+ tooltip.trigger(callback, [self, newIndex]);
1096
+
1097
+ // If default action wasn't prevented...
1098
+ if(!callback.isDefaultPrevented()) {
1099
+ // Only update z-index's if they've changed
1100
+ if(curIndex !== newIndex) {
1101
+ // Reduce our z-index's and keep them properly ordered
1102
+ qtips.each(function() {
1103
+ if(this.style.zIndex > curIndex) {
1104
+ this.style.zIndex = this.style.zIndex - 1;
1105
+ }
1106
+ });
1107
+
1108
+ // Fire blur event for focused tooltip
1109
+ qtips.filter('.' + focusClass).qtip('blur', cachedEvent);
1110
+ }
1111
+
1112
+ // Set the new z-index
1113
+ tooltip.addClass(focusClass)[0].style.zIndex = newIndex;
1114
+ }
1115
+ }
1116
+
1117
+ return self;
1118
+ },
1119
+
1120
+ blur: function(event) {
1121
+ var cachedEvent = $.extend({}, event),
1122
+ callback;
1123
+
1124
+ // Set focused status to FALSE
1125
+ tooltip.removeClass(focusClass);
1126
+
1127
+ // Trigger blur event
1128
+ callback = $.Event('tooltipblur');
1129
+ callback.originalEvent = cachedEvent;
1130
+ tooltip.trigger(callback, [self]);
1131
+
1132
+ return self;
1133
+ },
1134
+
1135
+ reposition: function(event, effect)
1136
+ {
1137
+ if(!self.rendered || isPositioning) { return self; }
1138
+
1139
+ // Set positioning flag
1140
+ isPositioning = 1;
1141
+
1142
+ var target = options.position.target,
1143
+ posOptions = options.position,
1144
+ my = posOptions.my,
1145
+ at = posOptions.at,
1146
+ adjust = posOptions.adjust,
1147
+ method = adjust.method.split(' '),
1148
+ elemWidth = tooltip.outerWidth(),
1149
+ elemHeight = tooltip.outerHeight(),
1150
+ targetWidth = 0,
1151
+ targetHeight = 0,
1152
+ callback = $.Event('tooltipmove'),
1153
+ fixed = tooltip.css('position') === 'fixed',
1154
+ viewport = posOptions.viewport,
1155
+ position = { left: 0, top: 0 },
1156
+ flipoffset = FALSE,
1157
+ tip = self.plugins.tip,
1158
+ readjust = {
1159
+ // Axis detection and readjustment indicator
1160
+ horizontal: method[0],
1161
+ vertical: (method[1] = method[1] || method[0]),
1162
+ enabled: viewport.jquery && target[0] !== window && target[0] !== docBody && adjust.method !== 'none',
1163
+
1164
+ // Reposition methods
1165
+ left: function(posLeft) {
1166
+ var isShift = readjust.horizontal === 'shift',
1167
+ viewportScroll = viewport.offset.left + viewport.scrollLeft,
1168
+ myWidth = my.x === 'left' ? elemWidth : my.x === 'right' ? -elemWidth : -elemWidth / 2,
1169
+ atWidth = at.x === 'left' ? targetWidth : at.x === 'right' ? -targetWidth : -targetWidth / 2,
1170
+ tipWidth = tip && tip.size ? tip.size.width || 0 : 0,
1171
+ tipAdjust = tip && tip.corner && tip.corner.precedance === 'x' && !isShift ? tipWidth : 0,
1172
+ overflowLeft = viewportScroll - posLeft + tipAdjust,
1173
+ overflowRight = posLeft + elemWidth - viewport.width - viewportScroll + tipAdjust,
1174
+ offset = myWidth - (my.precedance === 'x' || my.x === my.y ? atWidth : 0),
1175
+ isCenter = my.x === 'center';
1176
+
1177
+ // Optional 'shift' style repositioning
1178
+ if(isShift) {
1179
+ tipAdjust = tip && tip.corner && tip.corner.precedance === 'y' ? tipWidth : 0;
1180
+ offset = (my.x === 'left' ? 1 : -1) * myWidth - tipAdjust;
1181
+
1182
+ // Adjust position but keep it within viewport dimensions
1183
+ position.left += overflowLeft > 0 ? overflowLeft : overflowRight > 0 ? -overflowRight : 0;
1184
+ position.left = Math.max(
1185
+ viewport.offset.left + (tipAdjust && tip.corner.x === 'center' ? tip.offset : 0),
1186
+ posLeft - offset,
1187
+ Math.min(
1188
+ Math.max(viewport.offset.left + viewport.width, posLeft + offset),
1189
+ position.left
1190
+ )
1191
+ );
1192
+ }
1193
+
1194
+ // Default 'flip' repositioning
1195
+ else {
1196
+ if(overflowLeft > 0 && (my.x !== 'left' || overflowRight > 0)) {
1197
+ position.left -= offset;
1198
+ }
1199
+ else if(overflowRight > 0 && (my.x !== 'right' || overflowLeft > 0) ) {
1200
+ position.left -= isCenter ? -offset : offset;
1201
+ }
1202
+ if(position.left !== posLeft && isCenter) { position.left -= adjust.x; }
1203
+
1204
+ // Make sure we haven't made things worse with the adjustment and return the adjusted difference
1205
+ if(position.left < viewportScroll && -position.left > overflowRight) { position.left = posLeft; }
1206
+ }
1207
+
1208
+ return position.left - posLeft;
1209
+ },
1210
+ top: function(posTop) {
1211
+ var isShift = readjust.vertical === 'shift',
1212
+ viewportScroll = viewport.offset.top + viewport.scrollTop,
1213
+ myHeight = my.y === 'top' ? elemHeight : my.y === 'bottom' ? -elemHeight : -elemHeight / 2,
1214
+ atHeight = at.y === 'top' ? targetHeight : at.y === 'bottom' ? -targetHeight : -targetHeight / 2,
1215
+ tipHeight = tip && tip.size ? tip.size.height || 0 : 0,
1216
+ tipAdjust = tip && tip.corner && tip.corner.precedance === 'y' && !isShift ? tipHeight : 0,
1217
+ overflowTop = viewportScroll - posTop + tipAdjust,
1218
+ overflowBottom = posTop + elemHeight - viewport.height - viewportScroll + tipAdjust,
1219
+ offset = myHeight - (my.precedance === 'y' || my.x === my.y ? atHeight : 0),
1220
+ isCenter = my.y === 'center';
1221
+
1222
+ // Optional 'shift' style repositioning
1223
+ if(isShift) {
1224
+ tipAdjust = tip && tip.corner && tip.corner.precedance === 'x' ? tipHeight : 0;
1225
+ offset = (my.y === 'top' ? 1 : -1) * myHeight - tipAdjust;
1226
+
1227
+ // Adjust position but keep it within viewport dimensions
1228
+ position.top += overflowTop > 0 ? overflowTop : overflowBottom > 0 ? -overflowBottom : 0;
1229
+ position.top = Math.max(
1230
+ viewport.offset.top + (tipAdjust && tip.corner.x === 'center' ? tip.offset : 0),
1231
+ posTop - offset,
1232
+ Math.min(
1233
+ Math.max(viewport.offset.top + viewport.height, posTop + offset),
1234
+ position.top
1235
+ )
1236
+ );
1237
+ }
1238
+
1239
+ // Default 'flip' repositioning
1240
+ else {
1241
+ if(overflowTop > 0 && (my.y !== 'top' || overflowBottom > 0)) {
1242
+ position.top -= offset;
1243
+ }
1244
+ else if(overflowBottom > 0 && (my.y !== 'bottom' || overflowTop > 0) ) {
1245
+ position.top -= isCenter ? -offset : offset;
1246
+ }
1247
+ if(position.top !== posTop && isCenter) { position.top -= adjust.y; }
1248
+
1249
+ // Make sure we haven't made things worse with the adjustment and return the adjusted difference
1250
+ if(position.top < 0 && -position.top > overflowBottom) { position.top = posTop; }
1251
+ }
1252
+
1253
+ return position.top - posTop;
1254
+ }
1255
+ },
1256
+ win;
1257
+
1258
+ // Check if absolute position was passed
1259
+ if($.isArray(target) && target.length === 2) {
1260
+ // Force left top and set position
1261
+ at = { x: 'left', y: 'top' };
1262
+ position = { left: target[0], top: target[1] };
1263
+ }
1264
+
1265
+ // Check if mouse was the target
1266
+ else if(target === 'mouse' && ((event && event.pageX) || cache.event.pageX)) {
1267
+ // Force left top to allow flipping
1268
+ at = { x: 'left', y: 'top' };
1269
+
1270
+ // Use cached event if one isn't available for positioning
1271
+ event = (event && (event.type === 'resize' || event.type === 'scroll') ? cache.event :
1272
+ event && event.pageX && event.type === 'mousemove' ? event :
1273
+ MOUSE && MOUSE.pageX && (adjust.mouse || !event || !event.pageX) ? { pageX: MOUSE.pageX, pageY: MOUSE.pageY } :
1274
+ !adjust.mouse && cache.origin && cache.origin.pageX ? cache.origin :
1275
+ event) || event || cache.event || MOUSE || {};
1276
+
1277
+ // Use event coordinates for position
1278
+ position = { top: event.pageY, left: event.pageX };
1279
+ }
1280
+
1281
+ // Target wasn't mouse or absolute...
1282
+ else {
1283
+ // Check if event targetting is being used
1284
+ if(target === 'event') {
1285
+ if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
1286
+ target = cache.target = $(event.target);
1287
+ }
1288
+ else {
1289
+ target = cache.target;
1290
+ }
1291
+ }
1292
+ else { cache.target = $(target); }
1293
+
1294
+ // Parse the target into a jQuery object and make sure there's an element present
1295
+ target = $(target).eq(0);
1296
+ if(target.length === 0) { return self; }
1297
+
1298
+ // Check if window or document is the target
1299
+ else if(target[0] === document || target[0] === window) {
1300
+ targetWidth = PLUGINS.iOS ? window.innerWidth : target.width();
1301
+ targetHeight = PLUGINS.iOS ? window.innerHeight : target.height();
1302
+
1303
+ if(target[0] === window) {
1304
+ position = {
1305
+ top: !fixed || PLUGINS.iOS ? (viewport || target).scrollTop() : 0,
1306
+ left: !fixed || PLUGINS.iOS ? (viewport || target).scrollLeft() : 0
1307
+ };
1308
+ }
1309
+ }
1310
+
1311
+ // Use Imagemap/SVG plugins if needed
1312
+ else if(target.is('area') && PLUGINS.imagemap) {
1313
+ position = PLUGINS.imagemap(target, at, readjust.enabled ? method : FALSE);
1314
+ }
1315
+ else if(target[0].namespaceURI === 'http://www.w3.org/2000/svg' && PLUGINS.svg) {
1316
+ position = PLUGINS.svg(target, at);
1317
+ }
1318
+
1319
+ else {
1320
+ targetWidth = target.outerWidth();
1321
+ targetHeight = target.outerHeight();
1322
+
1323
+ position = PLUGINS.offset(target, posOptions.container);
1324
+ }
1325
+
1326
+ // Parse returned plugin values into proper variables
1327
+ if(position.offset) {
1328
+ targetWidth = position.width;
1329
+ targetHeight = position.height;
1330
+ flipoffset = position.flipoffset;
1331
+ position = position.offset;
1332
+ }
1333
+
1334
+ // Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2 - v4.0)
1335
+ if((PLUGINS.iOS < 4.1 && PLUGINS.iOS > 3.1) || PLUGINS.iOS == 4.3 || (!PLUGINS.iOS && fixed)) {
1336
+ win = $(window);
1337
+ position.left -= win.scrollLeft();
1338
+ position.top -= win.scrollTop();
1339
+ }
1340
+
1341
+ // Adjust position relative to target
1342
+ position.left += at.x === 'right' ? targetWidth : at.x === 'center' ? targetWidth / 2 : 0;
1343
+ position.top += at.y === 'bottom' ? targetHeight : at.y === 'center' ? targetHeight / 2 : 0;
1344
+ }
1345
+
1346
+ // Adjust position relative to tooltip
1347
+ position.left += adjust.x + (my.x === 'right' ? -elemWidth : my.x === 'center' ? -elemWidth / 2 : 0);
1348
+ position.top += adjust.y + (my.y === 'bottom' ? -elemHeight : my.y === 'center' ? -elemHeight / 2 : 0);
1349
+
1350
+ // Calculate collision offset values if viewport positioning is enabled
1351
+ if(readjust.enabled) {
1352
+ // Cache our viewport details
1353
+ viewport = {
1354
+ elem: viewport,
1355
+ height: viewport[ (viewport[0] === window ? 'h' : 'outerH') + 'eight' ](),
1356
+ width: viewport[ (viewport[0] === window ? 'w' : 'outerW') + 'idth' ](),
1357
+ scrollLeft: fixed ? 0 : viewport.scrollLeft(),
1358
+ scrollTop: fixed ? 0 : viewport.scrollTop(),
1359
+ offset: viewport.offset() || { left: 0, top: 0 }
1360
+ };
1361
+
1362
+ // Adjust position based onviewport and adjustment options
1363
+ position.adjusted = {
1364
+ left: readjust.horizontal !== 'none' ? readjust.left(position.left) : 0,
1365
+ top: readjust.vertical !== 'none' ? readjust.top(position.top) : 0
1366
+ };
1367
+
1368
+ // Set tooltip position class
1369
+ if(position.adjusted.left + position.adjusted.top) {
1370
+ tooltip.attr('class', function(i, val) {
1371
+ return val.replace(/ui-tooltip-pos-\w+/i, uitooltip + '-pos-' + my.abbreviation());
1372
+ });
1373
+ }
1374
+
1375
+ // Apply flip offsets supplied by positioning plugins
1376
+ if(flipoffset && position.adjusted.left) { position.left += flipoffset.left; }
1377
+ if(flipoffset && position.adjusted.top) { position.top += flipoffset.top; }
1378
+ }
1379
+
1380
+ //Viewport adjustment is disabled, set values to zero
1381
+ else { position.adjusted = { left: 0, top: 0 }; }
1382
+
1383
+ // Call API method
1384
+ callback.originalEvent = $.extend({}, event);
1385
+ tooltip.trigger(callback, [self, position, viewport.elem || viewport]);
1386
+ if(callback.isDefaultPrevented()){ return self; }
1387
+ delete position.adjusted;
1388
+
1389
+ // If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly
1390
+ if(effect === FALSE || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) {
1391
+ tooltip.css(position);
1392
+ }
1393
+
1394
+ // Use custom function if provided
1395
+ else if($.isFunction(posOptions.effect)) {
1396
+ posOptions.effect.call(tooltip, self, $.extend({}, position));
1397
+ tooltip.queue(function(next) {
1398
+ // Reset attributes to avoid cross-browser rendering bugs
1399
+ $(this).css({ opacity: '', height: '' });
1400
+ if($.browser.msie) { this.style.removeAttribute('filter'); }
1401
+
1402
+ next();
1403
+ });
1404
+ }
1405
+
1406
+ // Set positioning flag
1407
+ isPositioning = 0;
1408
+
1409
+ return self;
1410
+ },
1411
+
1412
+ // Max/min width simulator function for all browsers.. yeaaah!
1413
+ redraw: function()
1414
+ {
1415
+ if(self.rendered < 1 || isDrawing) { return self; }
1416
+
1417
+ var container = options.position.container,
1418
+ perc, width, max, min;
1419
+
1420
+ // Set drawing flag
1421
+ isDrawing = 1;
1422
+
1423
+ // If tooltip has a set height, just set it... like a boss!
1424
+ if(options.style.height) { tooltip.css('height', options.style.height); }
1425
+
1426
+ // If tooltip has a set width, just set it... like a boss!
1427
+ if(options.style.width) { tooltip.css('width', options.style.width); }
1428
+
1429
+ // Otherwise simualte max/min width...
1430
+ else {
1431
+ // Reset width and add fluid class
1432
+ tooltip.css('width', '').addClass(fluidClass);
1433
+
1434
+ // Grab our tooltip width (add 1 so we don't get wrapping problems.. huzzah!)
1435
+ width = tooltip.width() + 1;
1436
+
1437
+ // Grab our max/min properties
1438
+ max = tooltip.css('max-width') || '';
1439
+ min = tooltip.css('min-width') || '';
1440
+
1441
+ // Parse into proper pixel values
1442
+ perc = (max + min).indexOf('%') > -1 ? container.width() / 100 : 0;
1443
+ max = ((max.indexOf('%') > -1 ? perc : 1) * parseInt(max, 10)) || width;
1444
+ min = ((min.indexOf('%') > -1 ? perc : 1) * parseInt(min, 10)) || 0;
1445
+
1446
+ // Determine new dimension size based on max/min/current values
1447
+ width = max + min ? Math.min(Math.max(width, min), max) : width;
1448
+
1449
+ // Set the newly calculated width and remvoe fluid class
1450
+ tooltip.css('width', Math.round(width)).removeClass(fluidClass);
1451
+ }
1452
+
1453
+ // Set drawing flag
1454
+ isDrawing = 0;
1455
+
1456
+ return self;
1457
+ },
1458
+
1459
+ disable: function(state)
1460
+ {
1461
+ if('boolean' !== typeof state) {
1462
+ state = !(tooltip.hasClass(disabled) || cache.disabled);
1463
+ }
1464
+
1465
+ if(self.rendered) {
1466
+ tooltip.toggleClass(disabled, state);
1467
+ $.attr(tooltip[0], 'aria-disabled', state);
1468
+ }
1469
+ else {
1470
+ cache.disabled = !!state;
1471
+ }
1472
+
1473
+ return self;
1474
+ },
1475
+
1476
+ enable: function() { return self.disable(FALSE); },
1477
+
1478
+ destroy: function()
1479
+ {
1480
+ var t = target[0],
1481
+ title = $.attr(t, oldtitle),
1482
+ elemAPI = target.data('qtip');
1483
+
1484
+ // Destroy tooltip and any associated plugins if rendered
1485
+ if(self.rendered) {
1486
+ tooltip.remove();
1487
+
1488
+ $.each(self.plugins, function() {
1489
+ if(this.destroy) { this.destroy(); }
1490
+ });
1491
+ }
1492
+
1493
+ // Clear timers and remove bound events
1494
+ clearTimeout(self.timers.show);
1495
+ clearTimeout(self.timers.hide);
1496
+ unassignEvents();
1497
+
1498
+ // If the API if actually this qTip API...
1499
+ if(!elemAPI || self === elemAPI) {
1500
+ // Remove api object
1501
+ $.removeData(t, 'qtip');
1502
+
1503
+ // Reset old title attribute if removed
1504
+ if(options.suppress && title) {
1505
+ $.attr(t, 'title', title);
1506
+ target.removeAttr(oldtitle);
1507
+ }
1508
+
1509
+ // Remove ARIA attributes
1510
+ target.removeAttr('aria-describedby');
1511
+ }
1512
+
1513
+ // Remove qTip events associated with this API
1514
+ target.unbind('.qtip-'+id);
1515
+
1516
+ // Remove ID from sued id object
1517
+ delete usedIDs[self.id];
1518
+
1519
+ return target;
1520
+ }
1521
+ });
1522
+ }
1523
+
1524
+ // Initialization method
1525
+ function init(id, opts)
1526
+ {
1527
+ var obj, posOptions, attr, config, title,
1528
+
1529
+ // Setup element references
1530
+ elem = $(this),
1531
+ docBody = $(document.body),
1532
+
1533
+ // Use document body instead of document element if needed
1534
+ newTarget = this === document ? docBody : elem,
1535
+
1536
+ // Grab metadata from element if plugin is present
1537
+ metadata = (elem.metadata) ? elem.metadata(opts.metadata) : NULL,
1538
+
1539
+ // If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise
1540
+ metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL,
1541
+
1542
+ // Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,
1543
+ html5 = elem.data(opts.metadata.name || 'qtipopts');
1544
+
1545
+ // If we don't get an object returned attempt to parse it manualyl without parseJSON
1546
+ try { html5 = typeof html5 === 'string' ? (new Function("return " + html5))() : html5; }
1547
+ catch(e) { log('Unable to parse HTML5 attribute data: ' + html5); }
1548
+
1549
+ // Merge in and sanitize metadata
1550
+ config = $.extend(TRUE, {}, QTIP.defaults, opts,
1551
+ typeof html5 === 'object' ? sanitizeOptions(html5) : NULL,
1552
+ sanitizeOptions(metadata5 || metadata));
1553
+
1554
+ // Re-grab our positioning options now we've merged our metadata and set id to passed value
1555
+ posOptions = config.position;
1556
+ config.id = id;
1557
+
1558
+ // Setup missing content if none is detected
1559
+ if('boolean' === typeof config.content.text) {
1560
+ attr = elem.attr(config.content.attr);
1561
+
1562
+ // Grab from supplied attribute if available
1563
+ if(config.content.attr !== FALSE && attr) { config.content.text = attr; }
1564
+
1565
+ // No valid content was found, abort render
1566
+ else {
1567
+ log('Unable to locate content for tooltip! Aborting render of tooltip on element: ', elem);
1568
+ return FALSE;
1569
+ }
1570
+ }
1571
+
1572
+ // Setup target options
1573
+ if(posOptions.container === FALSE) { posOptions.container = docBody; }
1574
+ if(posOptions.target === FALSE) { posOptions.target = newTarget; }
1575
+ if(config.show.target === FALSE) { config.show.target = newTarget; }
1576
+ if(config.show.solo === TRUE) { config.show.solo = docBody; }
1577
+ if(config.hide.target === FALSE) { config.hide.target = newTarget; }
1578
+ if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; }
1579
+
1580
+ // Convert position corner values into x and y strings
1581
+ posOptions.at = new PLUGINS.Corner(posOptions.at);
1582
+ posOptions.my = new PLUGINS.Corner(posOptions.my);
1583
+
1584
+ // Destroy previous tooltip if overwrite is enabled, or skip element if not
1585
+ if($.data(this, 'qtip')) {
1586
+ if(config.overwrite) {
1587
+ elem.qtip('destroy');
1588
+ }
1589
+ else if(config.overwrite === FALSE) {
1590
+ return FALSE;
1591
+ }
1592
+ }
1593
+
1594
+ // Remove title attribute and store it if present
1595
+ if(config.suppress && (title = $.attr(this, 'title'))) {
1596
+ $(this).removeAttr('title').attr(oldtitle, title);
1597
+ }
1598
+
1599
+ // Initialize the tooltip and add API reference
1600
+ obj = new QTip(elem, config, id, !!attr);
1601
+ $.data(this, 'qtip', obj);
1602
+
1603
+ // Catch remove events on target element to destroy redundant tooltip
1604
+ elem.bind('remove.qtip-'+id, function(){ obj.destroy(); });
1605
+
1606
+ return obj;
1607
+ }
1608
+
1609
+ // jQuery $.fn extension method
1610
+ QTIP = $.fn.qtip = function(options, notation, newValue)
1611
+ {
1612
+ var command = ('' + options).toLowerCase(), // Parse command
1613
+ returned = NULL,
1614
+ args = command === 'disable' ? [TRUE] : $.makeArray(arguments).slice(1),
1615
+ event = args[args.length - 1],
1616
+ opts = this[0] ? $.data(this[0], 'qtip') : NULL;
1617
+
1618
+ // Check for API request
1619
+ if((!arguments.length && opts) || command === 'api') {
1620
+ return opts;
1621
+ }
1622
+
1623
+ // Execute API command if present
1624
+ else if('string' === typeof options)
1625
+ {
1626
+ this.each(function()
1627
+ {
1628
+ var api = $.data(this, 'qtip');
1629
+ if(!api) { return TRUE; }
1630
+
1631
+ // Cache the event if possible
1632
+ if(event && event.timeStamp) { api.cache.event = event; }
1633
+
1634
+ // Check for specific API commands
1635
+ if((command === 'option' || command === 'options') && notation) {
1636
+ if($.isPlainObject(notation) || newValue !== undefined) {
1637
+ api.set(notation, newValue);
1638
+ }
1639
+ else {
1640
+ returned = api.get(notation);
1641
+ return FALSE;
1642
+ }
1643
+ }
1644
+
1645
+ // Execute API command
1646
+ else if(api[command]) {
1647
+ api[command].apply(api[command], args);
1648
+ }
1649
+ });
1650
+
1651
+ return returned !== NULL ? returned : this;
1652
+ }
1653
+
1654
+ // No API commands. validate provided options and setup qTips
1655
+ else if('object' === typeof options || !arguments.length)
1656
+ {
1657
+ opts = sanitizeOptions($.extend(TRUE, {}, options));
1658
+
1659
+ // Bind the qTips
1660
+ return QTIP.bind.call(this, opts, event);
1661
+ }
1662
+ };
1663
+
1664
+ // $.fn.qtip Bind method
1665
+ QTIP.bind = function(opts, event)
1666
+ {
1667
+ return this.each(function(i) {
1668
+ var options, targets, events, namespace, api, id;
1669
+
1670
+ // Find next available ID, or use custom ID if provided
1671
+ id = $.isArray(opts.id) ? opts.id[i] : opts.id;
1672
+ id = !id || id === FALSE || id.length < 1 || usedIDs[id] ? QTIP.nextid++ : (usedIDs[id] = id);
1673
+
1674
+ // Setup events namespace
1675
+ namespace = '.qtip-'+id+'-create';
1676
+
1677
+ // Initialize the qTip and re-grab newly sanitized options
1678
+ api = init.call(this, id, opts);
1679
+ if(api === FALSE) { return TRUE; }
1680
+ options = api.options;
1681
+
1682
+ // Initialize plugins
1683
+ $.each(PLUGINS, function() {
1684
+ if(this.initialize === 'initialize') { this(api); }
1685
+ });
1686
+
1687
+ // Determine hide and show targets
1688
+ targets = { show: options.show.target, hide: options.hide.target };
1689
+ events = {
1690
+ show: $.trim('' + options.show.event).replace(/ /g, namespace+' ') + namespace,
1691
+ hide: $.trim('' + options.hide.event).replace(/ /g, namespace+' ') + namespace
1692
+ };
1693
+
1694
+ /*
1695
+ * Make sure hoverIntent functions properly by using mouseleave as a hide event if
1696
+ * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
1697
+ */
1698
+ if(/mouse(over|enter)/i.test(events.show) && !/mouse(out|leave)/i.test(events.hide)) {
1699
+ events.hide += ' mouseleave' + namespace;
1700
+ }
1701
+
1702
+ /*
1703
+ * Also make sure initial mouse targetting works correctly by caching mousemove coords
1704
+ * on show targets before the tooltip has rendered.
1705
+ */
1706
+ targets.show.bind('mousemove'+namespace, function(event) {
1707
+ MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' };
1708
+ });
1709
+
1710
+ // Define hoverIntent function
1711
+ function hoverIntent(event) {
1712
+ function render() {
1713
+ // Cache mouse coords,render and render the tooltip
1714
+ api.render(typeof event === 'object' || options.show.ready);
1715
+
1716
+ // Unbind show and hide events
1717
+ targets.show.add(targets.hide).unbind(namespace);
1718
+ }
1719
+
1720
+ // Only continue if tooltip isn't disabled
1721
+ if(api.cache.disabled) { return FALSE; }
1722
+
1723
+ // Cache the event data
1724
+ api.cache.event = $.extend({}, event);
1725
+ api.cache.target = event ? $(event.target) : [undefined];
1726
+
1727
+ // Start the event sequence
1728
+ if(options.show.delay > 0) {
1729
+ clearTimeout(api.timers.show);
1730
+ api.timers.show = setTimeout(render, options.show.delay);
1731
+ if(events.show !== events.hide) {
1732
+ targets.hide.bind(events.hide, function() { clearTimeout(api.timers.show); });
1733
+ }
1734
+ }
1735
+ else { render(); }
1736
+ }
1737
+
1738
+ // Bind show events to target
1739
+ targets.show.bind(events.show, hoverIntent);
1740
+
1741
+ // Prerendering is enabled, create tooltip now
1742
+ if(options.show.ready || options.prerender) { hoverIntent(event); }
1743
+ });
1744
+ };
1745
+
1746
+ // Setup base plugins
1747
+ PLUGINS = QTIP.plugins = {
1748
+ // Corner object parser
1749
+ Corner: function(corner) {
1750
+ corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, 'center').toLowerCase();
1751
+ this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase();
1752
+ this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase();
1753
+
1754
+ this.precedance = (corner.charAt(0).search(/^(t|b)/) > -1) ? 'y' : 'x';
1755
+ this.string = function() { return this.precedance === 'y' ? this.y+this.x : this.x+this.y; };
1756
+ this.abbreviation = function() {
1757
+ var x = this.x.substr(0,1), y = this.y.substr(0,1);
1758
+ return x === y ? x : (x === 'c' || (x !== 'c' && y !== 'c')) ? y + x : x + y;
1759
+ };
1760
+ },
1761
+
1762
+ // Custom (more correct for qTip!) offset calculator
1763
+ offset: function(elem, container) {
1764
+ var pos = elem.offset(),
1765
+ parent = container,
1766
+ deep = 0,
1767
+ docBody = document.body,
1768
+ coffset, overflow;
1769
+
1770
+ function scroll(e, i) {
1771
+ pos.left += i * e.scrollLeft();
1772
+ pos.top += i * e.scrollTop();
1773
+ }
1774
+
1775
+ if(parent) {
1776
+ // Compensate for non-static containers offset
1777
+ do {
1778
+ if(parent.css('position') !== 'static') {
1779
+ coffset = parent[0] === docBody ?
1780
+ { left: parseInt(parent.css('left'), 10) || 0, top: parseInt(parent.css('top'), 10) || 0 } :
1781
+ parent.position();
1782
+
1783
+ pos.left -= coffset.left + (parseInt(parent.css('borderLeftWidth'), 10) || 0) + (parseInt(parent.css('marginLeft'), 10) || 0);
1784
+ pos.top -= coffset.top + (parseInt(parent.css('borderTopWidth'), 10) || 0);
1785
+
1786
+ overflow = parent.css('overflow');
1787
+ if(overflow === 'scroll' || overflow === 'auto') { deep++; }
1788
+ }
1789
+
1790
+ if(parent[0] === docBody) { break; }
1791
+ }
1792
+ while(parent = parent.offsetParent());
1793
+
1794
+ // Compensate for containers scroll if it also has an offsetParent
1795
+ if(container[0] !== docBody && deep) { scroll( container, 1 ); }
1796
+ }
1797
+
1798
+ return pos;
1799
+ },
1800
+
1801
+ /*
1802
+ * iOS 3.2 - 4.0 scroll fix detection used in offset() function.
1803
+ */
1804
+ iOS: parseFloat(
1805
+ ('' + (/CPU.*OS ([0-9_]{1,3})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
1806
+ .replace('undefined', '3_2').replace('_','.')
1807
+ ) || FALSE,
1808
+
1809
+ /*
1810
+ * jQuery-specific $.fn overrides
1811
+ */
1812
+ fn: {
1813
+ /* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
1814
+ attr: function(attr, val) {
1815
+ if(this.length) {
1816
+ var self = this[0],
1817
+ title = 'title',
1818
+ api = $.data(self, 'qtip');
1819
+
1820
+ if(attr === title && api && 'object' === typeof api && api.options.suppress) {
1821
+ if(arguments.length < 2) {
1822
+ return $.attr(self, oldtitle);
1823
+ }
1824
+ else {
1825
+ // If qTip is rendered and title was originally used as content, update it
1826
+ if(api && api.options.content.attr === title && api.cache.attr) {
1827
+ api.set('content.text', val);
1828
+ }
1829
+
1830
+ // Use the regular attr method to set, then cache the result
1831
+ return this.attr(oldtitle, val);
1832
+ }
1833
+ }
1834
+ }
1835
+
1836
+ return $.fn['attr'+replaceSuffix].apply(this, arguments);
1837
+ },
1838
+
1839
+ /* Allow clone to correctly retrieve cached title attributes */
1840
+ clone: function(keepData) {
1841
+ var titles = $([]), title = 'title',
1842
+
1843
+ // Clone our element using the real clone method
1844
+ elems = $.fn['clone'+replaceSuffix].apply(this, arguments);
1845
+
1846
+ // Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false
1847
+ if(!keepData) {
1848
+ elems.filter('['+oldtitle+']').attr('title', function() {
1849
+ return $.attr(this, oldtitle);
1850
+ })
1851
+ .removeAttr(oldtitle);
1852
+ }
1853
+
1854
+ return elems;
1855
+ },
1856
+
1857
+ /*
1858
+ * Taken directly from jQuery 1.8.2 widget source code
1859
+ * Trigger 'remove' event on all elements on removal if jQuery UI isn't present
1860
+ */
1861
+ remove: $.ui ? NULL : function( selector, keepData ) {
1862
+ $(this).each(function() {
1863
+ if (!keepData) {
1864
+ if (!selector || $.filter( selector, [ this ] ).length) {
1865
+ $('*', this).add(this).each(function() {
1866
+ $(this).triggerHandler('remove');
1867
+ });
1868
+ }
1869
+ }
1870
+ });
1871
+ }
1872
+ }
1873
+ };
1874
+
1875
+ // Apply the fn overrides above
1876
+ $.each(PLUGINS.fn, function(name, func) {
1877
+ if(!func || $.fn[name+replaceSuffix]) { return TRUE; }
1878
+
1879
+ var old = $.fn[name+replaceSuffix] = $.fn[name];
1880
+ $.fn[name] = function() {
1881
+ return func.apply(this, arguments) || old.apply(this, arguments);
1882
+ };
1883
+ });
1884
+
1885
+ // Set global qTip properties
1886
+ QTIP.version = 'nightly';
1887
+ QTIP.nextid = 0;
1888
+ QTIP.inactiveEvents = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' ');
1889
+ QTIP.zindex = 15000;
1890
+
1891
+ // Define configuration defaults
1892
+ QTIP.defaults = {
1893
+ prerender: FALSE,
1894
+ id: FALSE,
1895
+ overwrite: TRUE,
1896
+ suppress: TRUE,
1897
+ content: {
1898
+ text: TRUE,
1899
+ attr: 'title',
1900
+ title: {
1901
+ text: FALSE,
1902
+ button: FALSE
1903
+ }
1904
+ },
1905
+ position: {
1906
+ my: 'top left',
1907
+ at: 'bottom right',
1908
+ target: FALSE,
1909
+ container: FALSE,
1910
+ viewport: FALSE,
1911
+ adjust: {
1912
+ x: 0, y: 0,
1913
+ mouse: TRUE,
1914
+ resize: TRUE,
1915
+ method: 'flip flip'
1916
+ },
1917
+ effect: function(api, pos, viewport) {
1918
+ $(this).animate(pos, {
1919
+ duration: 200,
1920
+ queue: FALSE
1921
+ });
1922
+ }
1923
+ },
1924
+ show: {
1925
+ target: FALSE,
1926
+ event: 'mouseenter',
1927
+ effect: TRUE,
1928
+ delay: 90,
1929
+ solo: FALSE,
1930
+ ready: FALSE,
1931
+ autofocus: FALSE
1932
+ },
1933
+ hide: {
1934
+ target: FALSE,
1935
+ event: 'mouseleave',
1936
+ effect: TRUE,
1937
+ delay: 0,
1938
+ fixed: FALSE,
1939
+ inactive: FALSE,
1940
+ leave: 'window',
1941
+ distance: FALSE
1942
+ },
1943
+ style: {
1944
+ classes: '',
1945
+ widget: FALSE,
1946
+ width: FALSE,
1947
+ height: FALSE
1948
+ },
1949
+ events: {
1950
+ render: NULL,
1951
+ move: NULL,
1952
+ show: NULL,
1953
+ hide: NULL,
1954
+ toggle: NULL,
1955
+ visible: NULL,
1956
+ focus: NULL,
1957
+ blur: NULL
1958
+ }
1959
+ };
1960
+
1961
+ function Ajax(api)
1962
+ {
1963
+ var self = this,
1964
+ tooltip = api.elements.tooltip,
1965
+ opts = api.options.content.ajax,
1966
+ namespace = '.qtip-ajax',
1967
+ rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
1968
+ first = TRUE;
1969
+
1970
+ api.checks.ajax = {
1971
+ '^content.ajax': function(obj, name, v) {
1972
+ // If content.ajax object was reset, set our local var
1973
+ if(name === 'ajax') { opts = v; }
1974
+
1975
+ if(name === 'once') {
1976
+ self.init();
1977
+ }
1978
+ else if(opts && opts.url) {
1979
+ self.load();
1980
+ }
1981
+ else {
1982
+ tooltip.unbind(namespace);
1983
+ }
1984
+ }
1985
+ };
1986
+
1987
+ $.extend(self, {
1988
+ init: function() {
1989
+ // Make sure ajax options are enabled and bind event
1990
+ if(opts && opts.url) {
1991
+ tooltip.unbind(namespace)[ opts.once ? 'one' : 'bind' ]('tooltipshow'+namespace, self.load);
1992
+ }
1993
+
1994
+ return self;
1995
+ },
1996
+
1997
+ load: function(event, first) {
1998
+ var hasSelector = opts.url.indexOf(' '),
1999
+ url = opts.url,
2000
+ selector,
2001
+ hideFirst = opts.once && !opts.loading && first;
2002
+
2003
+ // If loading option is disabled, prevent the tooltip showing until we've completed the request
2004
+ if(hideFirst) { try{ event.preventDefault(); } catch(e) {} }
2005
+
2006
+ // Make sure default event hasn't been prevented
2007
+ else if(event && event.isDefaultPrevented()) { return self; }
2008
+
2009
+ // Check if user delcared a content selector like in .load()
2010
+ if(hasSelector > -1) {
2011
+ selector = url.substr(hasSelector);
2012
+ url = url.substr(0, hasSelector);
2013
+ }
2014
+
2015
+ // Define common after callback for both success/error handlers
2016
+ function after() {
2017
+ // Re-display tip if loading and first time, and reset first flag
2018
+ if(hideFirst) { api.show(event.originalEvent); first = FALSE; }
2019
+
2020
+ // Call users complete if it was defined
2021
+ if($.isFunction(opts.complete)) { opts.complete.apply(this, arguments); }
2022
+ }
2023
+
2024
+ // Define success handler
2025
+ function successHandler(content) {
2026
+ if(selector) {
2027
+ // Create a dummy div to hold the results and grab the selector element
2028
+ content = $('<div/>')
2029
+ // inject the contents of the document in, removing the scripts
2030
+ // to avoid any 'Permission Denied' errors in IE
2031
+ .append(content.replace(rscript, ""))
2032
+
2033
+ // Locate the specified elements
2034
+ .find(selector);
2035
+ }
2036
+
2037
+ // Set the content
2038
+ api.set('content.text', content);
2039
+ }
2040
+
2041
+ // Error handler
2042
+ function errorHandler(xh, status, error) {
2043
+ if (xh.status === 0) { return; }
2044
+ api.set('content.text', status + ': ' + error);
2045
+ }
2046
+
2047
+ // Setup $.ajax option object and process the request
2048
+ $.ajax( $.extend({ success: successHandler, error: errorHandler, context: api }, opts, { url: url, complete: after }) );
2049
+ }
2050
+ });
2051
+
2052
+ self.init();
2053
+ }
2054
+
2055
+
2056
+ PLUGINS.ajax = function(api)
2057
+ {
2058
+ var self = api.plugins.ajax;
2059
+
2060
+ return 'object' === typeof self ? self : (api.plugins.ajax = new Ajax(api));
2061
+ };
2062
+
2063
+ PLUGINS.ajax.initialize = 'render';
2064
+
2065
+ // Setup plugin sanitization
2066
+ PLUGINS.ajax.sanitize = function(options)
2067
+ {
2068
+ var content = options.content, opts;
2069
+ if(content && 'ajax' in content) {
2070
+ opts = content.ajax;
2071
+ if(typeof opts !== 'object') { opts = options.content.ajax = { url: opts }; }
2072
+ if('boolean' !== typeof opts.once && opts.once) { opts.once = !!opts.once; }
2073
+ }
2074
+ };
2075
+
2076
+ // Extend original api defaults
2077
+ $.extend(TRUE, QTIP.defaults, {
2078
+ content: {
2079
+ ajax: {
2080
+ loading: TRUE,
2081
+ once: TRUE
2082
+ }
2083
+ }
2084
+ });
2085
+
2086
+ PLUGINS.imagemap = function(area, corner, flip)
2087
+ {
2088
+ if(!area.jquery) { area = $(area); }
2089
+
2090
+ var shape = area.attr('shape').toLowerCase(),
2091
+ baseCoords = area.attr('coords').split(','),
2092
+ coords = [],
2093
+ image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'),
2094
+ imageOffset = image.offset(),
2095
+ result = {
2096
+ width: 0, height: 0,
2097
+ offset: { top: 1e10, right: 0, bottom: 0, left: 1e10 }
2098
+ },
2099
+ i = 0, next = 0, dimensions;
2100
+
2101
+ // POLY area coordinate calculator
2102
+ // Special thanks to Ed Cradock for helping out with this.
2103
+ // Uses a binary search algorithm to find suitable coordinates.
2104
+ function polyCoordinates(result, coords, corner)
2105
+ {
2106
+ var i = 0,
2107
+ compareX = 1, compareY = 1,
2108
+ realX = 0, realY = 0,
2109
+ newWidth = result.width,
2110
+ newHeight = result.height;
2111
+
2112
+ // Use a binary search algorithm to locate most suitable coordinate (hopefully)
2113
+ while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0)
2114
+ {
2115
+ newWidth = Math.floor(newWidth / 2);
2116
+ newHeight = Math.floor(newHeight / 2);
2117
+
2118
+ if(corner.x === 'left'){ compareX = newWidth; }
2119
+ else if(corner.x === 'right'){ compareX = result.width - newWidth; }
2120
+ else{ compareX += Math.floor(newWidth / 2); }
2121
+
2122
+ if(corner.y === 'top'){ compareY = newHeight; }
2123
+ else if(corner.y === 'bottom'){ compareY = result.height - newHeight; }
2124
+ else{ compareY += Math.floor(newHeight / 2); }
2125
+
2126
+ i = coords.length; while(i--)
2127
+ {
2128
+ if(coords.length < 2){ break; }
2129
+
2130
+ realX = coords[i][0] - result.offset.left;
2131
+ realY = coords[i][1] - result.offset.top;
2132
+
2133
+ if((corner.x === 'left' && realX >= compareX) ||
2134
+ (corner.x === 'right' && realX <= compareX) ||
2135
+ (corner.x === 'center' && (realX < compareX || realX > (result.width - compareX))) ||
2136
+ (corner.y === 'top' && realY >= compareY) ||
2137
+ (corner.y === 'bottom' && realY <= compareY) ||
2138
+ (corner.y === 'center' && (realY < compareY || realY > (result.height - compareY)))) {
2139
+ coords.splice(i, 1);
2140
+ }
2141
+ }
2142
+ }
2143
+
2144
+ return { left: coords[0][0], top: coords[0][1] };
2145
+ }
2146
+
2147
+ // Make sure we account for padding and borders on the image
2148
+ imageOffset.left += Math.ceil((image.outerWidth() - image.width()) / 2);
2149
+ imageOffset.top += Math.ceil((image.outerHeight() - image.height()) / 2);
2150
+
2151
+ // Parse coordinates into proper array
2152
+ if(shape === 'poly') {
2153
+ i = baseCoords.length; while(i--)
2154
+ {
2155
+ next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ];
2156
+
2157
+ if(next[0] > result.offset.right){ result.offset.right = next[0]; }
2158
+ if(next[0] < result.offset.left){ result.offset.left = next[0]; }
2159
+ if(next[1] > result.offset.bottom){ result.offset.bottom = next[1]; }
2160
+ if(next[1] < result.offset.top){ result.offset.top = next[1]; }
2161
+
2162
+ coords.push(next);
2163
+ }
2164
+ }
2165
+ else {
2166
+ coords = $.map(baseCoords, function(coord){ return parseInt(coord, 10); });
2167
+ }
2168
+
2169
+ // Calculate details
2170
+ switch(shape)
2171
+ {
2172
+ case 'rect':
2173
+ result = {
2174
+ width: Math.abs(coords[2] - coords[0]),
2175
+ height: Math.abs(coords[3] - coords[1]),
2176
+ offset: { left: coords[0], top: coords[1] }
2177
+ };
2178
+ break;
2179
+
2180
+ case 'circle':
2181
+ result = {
2182
+ width: coords[2] + 2,
2183
+ height: coords[2] + 2,
2184
+ offset: { left: coords[0], top: coords[1] }
2185
+ };
2186
+ break;
2187
+
2188
+ case 'poly':
2189
+ $.extend(result, {
2190
+ width: Math.abs(result.offset.right - result.offset.left),
2191
+ height: Math.abs(result.offset.bottom - result.offset.top)
2192
+ });
2193
+
2194
+ if(corner.string() === 'centercenter') {
2195
+ result.offset = {
2196
+ left: result.offset.left + (result.width / 2),
2197
+ top: result.offset.top + (result.height / 2)
2198
+ };
2199
+ }
2200
+ else {
2201
+ result.offset = polyCoordinates(result, coords.slice(), corner);
2202
+
2203
+ // If flip adjustment is enabled, also calculate the closest opposite point
2204
+ if(flip && (flip[0] === 'flip' || flip[1] === 'flip')) {
2205
+ result.flipoffset = polyCoordinates(result, coords.slice(), {
2206
+ x: corner.x === 'left' ? 'right' : corner.x === 'right' ? 'left' : 'center',
2207
+ y: corner.y === 'top' ? 'bottom' : corner.y === 'bottom' ? 'top' : 'center'
2208
+ });
2209
+
2210
+ result.flipoffset.left -= result.offset.left;
2211
+ result.flipoffset.top -= result.offset.top;
2212
+ }
2213
+ }
2214
+
2215
+ result.width = result.height = 0;
2216
+ break;
2217
+ }
2218
+
2219
+ // Add image position to offset coordinates
2220
+ result.offset.left += imageOffset.left;
2221
+ result.offset.top += imageOffset.top;
2222
+
2223
+ return result;
2224
+ };
2225
+
2226
+ // Tip coordinates calculator
2227
+ function calculateTip(corner, width, height)
2228
+ {
2229
+ var width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2),
2230
+
2231
+ // Define tip coordinates in terms of height and width values
2232
+ tips = {
2233
+ bottomright: [[0,0], [width,height], [width,0]],
2234
+ bottomleft: [[0,0], [width,0], [0,height]],
2235
+ topright: [[0,height], [width,0], [width,height]],
2236
+ topleft: [[0,0], [0,height], [width,height]],
2237
+ topcenter: [[0,height], [width2,0], [width,height]],
2238
+ bottomcenter: [[0,0], [width,0], [width2,height]],
2239
+ rightcenter: [[0,0], [width,height2], [0,height]],
2240
+ leftcenter: [[width,0], [width,height], [0,height2]]
2241
+ };
2242
+
2243
+ // Set common side shapes
2244
+ tips.lefttop = tips.bottomright; tips.righttop = tips.bottomleft;
2245
+ tips.leftbottom = tips.topright; tips.rightbottom = tips.topleft;
2246
+
2247
+ return tips[ corner.string() ];
2248
+ }
2249
+
2250
+
2251
+ function Tip(qTip, command)
2252
+ {
2253
+ var self = this,
2254
+ opts = qTip.options.style.tip,
2255
+ elems = qTip.elements,
2256
+ tooltip = elems.tooltip,
2257
+ cache = {
2258
+ top: 0,
2259
+ left: 0,
2260
+ corner: ''
2261
+ },
2262
+ size = {
2263
+ width: opts.width,
2264
+ height: opts.height
2265
+ },
2266
+ color = { },
2267
+ border = opts.border || 0,
2268
+ namespace = '.qtip-tip',
2269
+ hasCanvas = !!($('<canvas />')[0] || {}).getContext;
2270
+
2271
+ self.corner = NULL;
2272
+ self.mimic = NULL;
2273
+ self.border = border;
2274
+ self.offset = opts.offset;
2275
+ self.size = size;
2276
+
2277
+ // Add new option checks for the plugin
2278
+ qTip.checks.tip = {
2279
+ '^position.my|style.tip.(corner|mimic|border)$': function() {
2280
+ // Make sure a tip can be drawn
2281
+ if(!self.init()) {
2282
+ self.destroy();
2283
+ }
2284
+
2285
+ // Reposition the tooltip
2286
+ qTip.reposition();
2287
+ },
2288
+ '^style.tip.(height|width)$': function() {
2289
+ // Re-set dimensions and redraw the tip
2290
+ size = {
2291
+ width: opts.width,
2292
+ height: opts.height
2293
+ };
2294
+ self.create();
2295
+ self.update();
2296
+
2297
+ // Reposition the tooltip
2298
+ qTip.reposition();
2299
+ },
2300
+ '^content.title.text|style.(classes|widget)$': function() {
2301
+ if(elems.tip) {
2302
+ self.update();
2303
+ }
2304
+ }
2305
+ };
2306
+
2307
+ function reposition(event, api, pos, viewport) {
2308
+ if(!elems.tip) { return; }
2309
+
2310
+ var newCorner = $.extend({}, self.corner),
2311
+ adjust = pos.adjusted,
2312
+ method = qTip.options.position.adjust.method.split(' '),
2313
+ horizontal = method[0],
2314
+ vertical = method[1] || method[0],
2315
+ shift = { left: FALSE, top: FALSE, x: 0, y: 0 },
2316
+ offset, css = {}, props;
2317
+
2318
+ // Make sure our tip position isn't fixed e.g. doesn't adjust with viewport
2319
+ if(self.corner.fixed !== TRUE) {
2320
+ // Horizontal - Shift or flip method
2321
+ if(horizontal === 'shift' && newCorner.precedance === 'x' && adjust.left && newCorner.y !== 'center') {
2322
+ newCorner.precedance = newCorner.precedance === 'x' ? 'y' : 'x';
2323
+ }
2324
+ else if(horizontal === 'flip' && adjust.left){
2325
+ newCorner.x = newCorner.x === 'center' ? (adjust.left > 0 ? 'left' : 'right') : (newCorner.x === 'left' ? 'right' : 'left');
2326
+ }
2327
+
2328
+ // Vertical - Shift or flip method
2329
+ if(vertical === 'shift' && newCorner.precedance === 'y' && adjust.top && newCorner.x !== 'center') {
2330
+ newCorner.precedance = newCorner.precedance === 'y' ? 'x' : 'y';
2331
+ }
2332
+ else if(vertical === 'flip' && adjust.top) {
2333
+ newCorner.y = newCorner.y === 'center' ? (adjust.top > 0 ? 'top' : 'bottom') : (newCorner.y === 'top' ? 'bottom' : 'top');
2334
+ }
2335
+
2336
+ // Update and redraw the tip if needed (check cached details of last drawn tip)
2337
+ if(newCorner.string() !== cache.corner && (cache.top !== adjust.top || cache.left !== adjust.left)) {
2338
+ self.update(newCorner, FALSE);
2339
+ }
2340
+ }
2341
+
2342
+ // Setup tip offset properties
2343
+ offset = self.position(newCorner, adjust);
2344
+ if(offset.right !== undefined) { offset.left = -offset.right; }
2345
+ if(offset.bottom !== undefined) { offset.top = -offset.bottom; }
2346
+ offset.user = Math.max(0, opts.offset);
2347
+
2348
+ // Viewport "shift" specific adjustments
2349
+ if(shift.left = (horizontal === 'shift' && !!adjust.left)) {
2350
+ if(newCorner.x === 'center') {
2351
+ css['margin-left'] = shift.x = offset['margin-left'] - adjust.left;
2352
+ }
2353
+ else {
2354
+ props = offset.right !== undefined ?
2355
+ [ adjust.left, -offset.left ] : [ -adjust.left, offset.left ];
2356
+
2357
+ if( (shift.x = Math.max(props[0], props[1])) > props[0] ) {
2358
+ pos.left -= adjust.left;
2359
+ shift.left = FALSE;
2360
+ }
2361
+
2362
+ css[ offset.right !== undefined ? 'right' : 'left' ] = shift.x;
2363
+ }
2364
+ }
2365
+ if(shift.top = (vertical === 'shift' && !!adjust.top)) {
2366
+ if(newCorner.y === 'center') {
2367
+ css['margin-top'] = shift.y = offset['margin-top'] - adjust.top;
2368
+ }
2369
+ else {
2370
+ props = offset.bottom !== undefined ?
2371
+ [ adjust.top, -offset.top ] : [ -adjust.top, offset.top ];
2372
+
2373
+ if( (shift.y = Math.max(props[0], props[1])) > props[0] ) {
2374
+ pos.top -= adjust.top;
2375
+ shift.top = FALSE;
2376
+ }
2377
+
2378
+ css[ offset.bottom !== undefined ? 'bottom' : 'top' ] = shift.y;
2379
+ }
2380
+ }
2381
+
2382
+ /*
2383
+ * If the tip is adjusted in both dimensions, or in a
2384
+ * direction that would cause it to be anywhere but the
2385
+ * outer border, hide it!
2386
+ */
2387
+ elems.tip.css(css).toggle(
2388
+ !((shift.x && shift.y) || (newCorner.x === 'center' && shift.y) || (newCorner.y === 'center' && shift.x))
2389
+ );
2390
+
2391
+ // Adjust position to accomodate tip dimensions
2392
+ pos.left -= offset.left.charAt ? offset.user : horizontal !== 'shift' || shift.top || !shift.left && !shift.top ? offset.left : 0;
2393
+ pos.top -= offset.top.charAt ? offset.user : vertical !== 'shift' || shift.left || !shift.left && !shift.top ? offset.top : 0;
2394
+
2395
+ // Cache details
2396
+ cache.left = adjust.left; cache.top = adjust.top;
2397
+ cache.corner = newCorner.string();
2398
+ }
2399
+
2400
+ /* border width calculator */
2401
+ function borderWidth(corner, side, backup) {
2402
+ side = !side ? corner[corner.precedance] : side;
2403
+
2404
+ var isFluid = tooltip.hasClass(fluidClass),
2405
+ isTitleTop = elems.titlebar && corner.y === 'top',
2406
+ elem = isTitleTop ? elems.titlebar : elems.content,
2407
+ css = 'border-' + side + '-width',
2408
+ val;
2409
+
2410
+ // Grab the border-width value (add fluid class if needed)
2411
+ tooltip.addClass(fluidClass);
2412
+ val = parseInt(elem.css(css), 10);
2413
+ val = (backup ? val || parseInt(tooltip.css(css), 10) : val) || 0;
2414
+ tooltip.toggleClass(fluidClass, isFluid);
2415
+
2416
+ return val;
2417
+ }
2418
+
2419
+ function borderRadius(corner) {
2420
+ var isTitleTop = elems.titlebar && corner.y === 'top',
2421
+ elem = isTitleTop ? elems.titlebar : elems.content,
2422
+ moz = $.browser.mozilla,
2423
+ prefix = moz ? '-moz-' : $.browser.webkit ? '-webkit-' : '',
2424
+ side = corner.y + (moz ? '' : '-') + corner.x,
2425
+ css = prefix + (moz ? 'border-radius-' + side : 'border-' + side + '-radius');
2426
+
2427
+ return parseInt(elem.css(css), 10) || parseInt(tooltip.css(css), 10) || 0;
2428
+ }
2429
+
2430
+ function calculateSize(corner) {
2431
+ var y = corner.precedance === 'y',
2432
+ width = size [ y ? 'width' : 'height' ],
2433
+ height = size [ y ? 'height' : 'width' ],
2434
+ isCenter = corner.string().indexOf('center') > -1,
2435
+ base = width * (isCenter ? 0.5 : 1),
2436
+ pow = Math.pow,
2437
+ round = Math.round,
2438
+ bigHyp, ratio, result,
2439
+
2440
+ smallHyp = Math.sqrt( pow(base, 2) + pow(height, 2) ),
2441
+
2442
+ hyp = [
2443
+ (border / base) * smallHyp, (border / height) * smallHyp
2444
+ ];
2445
+ hyp[2] = Math.sqrt( pow(hyp[0], 2) - pow(border, 2) );
2446
+ hyp[3] = Math.sqrt( pow(hyp[1], 2) - pow(border, 2) );
2447
+
2448
+ bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]);
2449
+ ratio = bigHyp / smallHyp;
2450
+
2451
+ result = [ round(ratio * height), round(ratio * width) ];
2452
+ return { height: result[ y ? 0 : 1 ], width: result[ y ? 1 : 0 ] };
2453
+ }
2454
+
2455
+ $.extend(self, {
2456
+ init: function()
2457
+ {
2458
+ var enabled = self.detectCorner() && (hasCanvas || $.browser.msie);
2459
+
2460
+ // Determine tip corner and type
2461
+ if(enabled) {
2462
+ // Create a new tip and draw it
2463
+ self.create();
2464
+ self.update();
2465
+
2466
+ // Bind update events
2467
+ tooltip.unbind(namespace).bind('tooltipmove'+namespace, reposition);
2468
+ }
2469
+
2470
+ return enabled;
2471
+ },
2472
+
2473
+ detectCorner: function()
2474
+ {
2475
+ var corner = opts.corner,
2476
+ posOptions = qTip.options.position,
2477
+ at = posOptions.at,
2478
+ my = posOptions.my.string ? posOptions.my.string() : posOptions.my;
2479
+
2480
+ // Detect corner and mimic properties
2481
+ if(corner === FALSE || (my === FALSE && at === FALSE)) {
2482
+ return FALSE;
2483
+ }
2484
+ else {
2485
+ if(corner === TRUE) {
2486
+ self.corner = new PLUGINS.Corner(my);
2487
+ }
2488
+ else if(!corner.string) {
2489
+ self.corner = new PLUGINS.Corner(corner);
2490
+ self.corner.fixed = TRUE;
2491
+ }
2492
+ }
2493
+
2494
+ return self.corner.string() !== 'centercenter';
2495
+ },
2496
+
2497
+ detectColours: function() {
2498
+ var i, fill, border,
2499
+ tip = elems.tip.css({ backgroundColor: '', border: '' }),
2500
+ corner = self.corner,
2501
+ precedance = corner[ corner.precedance ],
2502
+
2503
+ borderSide = 'border-' + precedance + '-color',
2504
+ borderSideCamel = 'border' + precedance.charAt(0) + precedance.substr(1) + 'Color',
2505
+
2506
+ invalid = /rgba?\(0, 0, 0(, 0)?\)|transparent/i,
2507
+ backgroundColor = 'background-color',
2508
+ transparent = 'transparent',
2509
+ important = ' !important',
2510
+
2511
+ bodyBorder = $(document.body).css('color'),
2512
+ contentColour = qTip.elements.content.css('color'),
2513
+
2514
+ useTitle = elems.titlebar && (corner.y === 'top' || (corner.y === 'center' && tip.position().top + (size.height / 2) + opts.offset < elems.titlebar.outerHeight(1))),
2515
+ colorElem = useTitle ? elems.titlebar : elems.content;
2516
+
2517
+ // Apply the fluid class so we can see our CSS values properly
2518
+ tooltip.addClass(fluidClass);
2519
+
2520
+ // Detect tip colours from CSS styles
2521
+ color.fill = fill = tip.css(backgroundColor);
2522
+ color.border = border = tip[0].style[ borderSideCamel ] || tip.css(borderSide) || tooltip.css(borderSide);
2523
+
2524
+ // Make sure colours are valid
2525
+ if(!fill || invalid.test(fill)) {
2526
+ color.fill = colorElem.css(backgroundColor) || transparent;
2527
+ if(invalid.test(color.fill)) {
2528
+ color.fill = tooltip.css(backgroundColor) || fill;
2529
+ }
2530
+ }
2531
+ if(!border || invalid.test(border) || border === bodyBorder) {
2532
+ color.border = colorElem.css(borderSide) || transparent;
2533
+ if(invalid.test(color.border) || color.border === contentColour) {
2534
+ color.border = border;
2535
+ }
2536
+ }
2537
+
2538
+ // Reset background and border colours
2539
+ $('*', tip).add(tip).css('cssText', backgroundColor+':'+transparent+important+';');
2540
+
2541
+ // Remove fluid class
2542
+ tooltip.removeClass(fluidClass);
2543
+ },
2544
+
2545
+ create: function()
2546
+ {
2547
+ var width = size.width,
2548
+ height = size.height,
2549
+ vml;
2550
+
2551
+ // Remove previous tip element if present
2552
+ if(elems.tip) { elems.tip.remove(); }
2553
+
2554
+ // Create tip element and prepend to the tooltip
2555
+ elems.tip = $('<div />', { 'class': 'ui-tooltip-tip' }).css({ width: width, height: height }).prependTo(tooltip);
2556
+
2557
+ // Create tip drawing element(s)
2558
+ if(hasCanvas) {
2559
+ // save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()!
2560
+ $('<canvas />').appendTo(elems.tip)[0].getContext('2d').save();
2561
+ }
2562
+ else {
2563
+ vml = '<vml:shape coordorigin="0,0" style="display:inline-block; position:absolute; behavior:url(#default#VML);"></vml:shape>';
2564
+ elems.tip.html(vml + vml);
2565
+ }
2566
+ },
2567
+
2568
+ update: function(corner, position)
2569
+ {
2570
+ var tip = elems.tip,
2571
+ inner = tip.children(),
2572
+ width = size.width,
2573
+ height = size.height,
2574
+ regular = 'px solid ',
2575
+ transparent = 'px dashed transparent', // Dashed IE6 border-transparency hack. Awesome!
2576
+ mimic = opts.mimic,
2577
+ round = Math.round,
2578
+ precedance, context, coords, translate, newSize;
2579
+
2580
+ // Re-determine tip if not already set
2581
+ if(!corner) { corner = self.corner; }
2582
+
2583
+ // Use corner property if we detect an invalid mimic value
2584
+ if(mimic === FALSE) { mimic = corner; }
2585
+
2586
+ // Otherwise inherit mimic properties from the corner object as necessary
2587
+ else {
2588
+ mimic = new PLUGINS.Corner(mimic);
2589
+ mimic.precedance = corner.precedance;
2590
+
2591
+ if(mimic.x === 'inherit') { mimic.x = corner.x; }
2592
+ else if(mimic.y === 'inherit') { mimic.y = corner.y; }
2593
+ else if(mimic.x === mimic.y) {
2594
+ mimic[ corner.precedance ] = corner[ corner.precedance ];
2595
+ }
2596
+ }
2597
+ precedance = mimic.precedance;
2598
+
2599
+ // Update our colours
2600
+ self.detectColours();
2601
+
2602
+ // Detect border width, taking into account colours
2603
+ if(color.border !== 'transparent' && color.border !== '#123456') {
2604
+ // Grab border width
2605
+ border = borderWidth(corner, NULL, TRUE);
2606
+
2607
+ // If border width isn't zero, use border color as fill (1.0 style tips)
2608
+ if(opts.border === 0 && border > 0) { color.fill = color.border; }
2609
+
2610
+ // Set border width (use detected border width if opts.border is true)
2611
+ self.border = border = opts.border !== TRUE ? opts.border : border;
2612
+ }
2613
+
2614
+ // Border colour was invalid, set border to zero
2615
+ else { self.border = border = 0; }
2616
+
2617
+ // Calculate coordinates
2618
+ coords = calculateTip(mimic, width , height);
2619
+
2620
+ // Determine tip size
2621
+ self.size = newSize = calculateSize(corner);
2622
+ tip.css(newSize);
2623
+
2624
+ // Calculate tip translation
2625
+ if(corner.precedance === 'y') {
2626
+ translate = [
2627
+ round(mimic.x === 'left' ? border : mimic.x === 'right' ? newSize.width - width - border : (newSize.width - width) / 2),
2628
+ round(mimic.y === 'top' ? newSize.height - height : 0)
2629
+ ];
2630
+ }
2631
+ else {
2632
+ translate = [
2633
+ round(mimic.x === 'left' ? newSize.width - width : 0),
2634
+ round(mimic.y === 'top' ? border : mimic.y === 'bottom' ? newSize.height - height - border : (newSize.height - height) / 2)
2635
+ ];
2636
+ }
2637
+
2638
+ // Canvas drawing implementation
2639
+ if(hasCanvas) {
2640
+ // Set the canvas size using calculated size
2641
+ inner.attr(newSize);
2642
+
2643
+ // Grab canvas context and clear/save it
2644
+ context = inner[0].getContext('2d');
2645
+ context.restore(); context.save();
2646
+ context.clearRect(0,0,3000,3000);
2647
+
2648
+ // Translate origin
2649
+ context.translate(translate[0], translate[1]);
2650
+
2651
+ // Draw the tip
2652
+ context.beginPath();
2653
+ context.moveTo(coords[0][0], coords[0][1]);
2654
+ context.lineTo(coords[1][0], coords[1][1]);
2655
+ context.lineTo(coords[2][0], coords[2][1]);
2656
+ context.closePath();
2657
+ context.fillStyle = color.fill;
2658
+ context.strokeStyle = color.border;
2659
+ context.lineWidth = border * 2;
2660
+ context.lineJoin = 'miter';
2661
+ context.miterLimit = 100;
2662
+ if(border) { context.stroke(); }
2663
+ context.fill();
2664
+ }
2665
+
2666
+ // VML (IE Proprietary implementation)
2667
+ else {
2668
+ // Setup coordinates string
2669
+ coords = 'm' + coords[0][0] + ',' + coords[0][1] + ' l' + coords[1][0] +
2670
+ ',' + coords[1][1] + ' ' + coords[2][0] + ',' + coords[2][1] + ' xe';
2671
+
2672
+ // Setup VML-specific offset for pixel-perfection
2673
+ translate[2] = border && /^(r|b)/i.test(corner.string()) ?
2674
+ parseFloat($.browser.version, 10) === 8 ? 2 : 1 : 0;
2675
+
2676
+ // Set initial CSS
2677
+ inner.css({
2678
+ antialias: ''+(mimic.string().indexOf('center') > -1),
2679
+ left: translate[0] - (translate[2] * Number(precedance === 'x')),
2680
+ top: translate[1] - (translate[2] * Number(precedance === 'y')),
2681
+ width: width + border,
2682
+ height: height + border
2683
+ })
2684
+ .each(function(i) {
2685
+ var $this = $(this);
2686
+
2687
+ // Set shape specific attributes
2688
+ $this[ $this.prop ? 'prop' : 'attr' ]({
2689
+ coordsize: (width+border) + ' ' + (height+border),
2690
+ path: coords,
2691
+ fillcolor: color.fill,
2692
+ filled: !!i,
2693
+ stroked: !!!i
2694
+ })
2695
+ .css({ display: border || i ? 'block' : 'none' });
2696
+
2697
+ // Check if border is enabled and add stroke element
2698
+ if(!i && $this.html() === '') {
2699
+ $this.html(
2700
+ '<vml:stroke weight="'+(border*2)+'px" color="'+color.border+'" miterlimit="1000" joinstyle="miter" ' +
2701
+ ' style="behavior:url(#default#VML); display:inline-block;" />'
2702
+ );
2703
+ }
2704
+ });
2705
+ }
2706
+
2707
+ // Position if needed
2708
+ if(position !== FALSE) { self.position(corner); }
2709
+ },
2710
+
2711
+ // Tip positioning method
2712
+ position: function(corner)
2713
+ {
2714
+ var tip = elems.tip,
2715
+ position = {},
2716
+ userOffset = Math.max(0, opts.offset),
2717
+ precedance, dimensions, corners;
2718
+
2719
+ // Return if tips are disabled or tip is not yet rendered
2720
+ if(opts.corner === FALSE || !tip) { return FALSE; }
2721
+
2722
+ // Inherit corner if not provided
2723
+ corner = corner || self.corner;
2724
+ precedance = corner.precedance;
2725
+
2726
+ // Determine which tip dimension to use for adjustment
2727
+ dimensions = calculateSize(corner);
2728
+
2729
+ // Setup corners and offset array
2730
+ corners = [ corner.x, corner.y ];
2731
+ if(precedance === 'x') { corners.reverse(); }
2732
+
2733
+ // Calculate tip position
2734
+ $.each(corners, function(i, side) {
2735
+ var b, br;
2736
+
2737
+ if(side === 'center') {
2738
+ b = precedance === 'y' ? 'left' : 'top';
2739
+ position[ b ] = '50%';
2740
+ position['margin-' + b] = -Math.round(dimensions[ precedance === 'y' ? 'width' : 'height' ] / 2) + userOffset;
2741
+ }
2742
+ else {
2743
+ b = borderWidth(corner, side, TRUE);
2744
+ br = borderRadius(corner);
2745
+
2746
+ position[ side ] = i ?
2747
+ border ? borderWidth(corner, side) : 0 :
2748
+ userOffset + (br > b ? br : 0);
2749
+ }
2750
+ });
2751
+
2752
+ // Adjust for tip dimensions
2753
+ position[ corner[precedance] ] -= dimensions[ precedance === 'x' ? 'width' : 'height' ];
2754
+
2755
+ // Set and return new position
2756
+ tip.css({ top: '', bottom: '', left: '', right: '', margin: '' }).css(position);
2757
+ return position;
2758
+ },
2759
+
2760
+ destroy: function()
2761
+ {
2762
+ // Remov tip and bound events
2763
+ if(elems.tip) { elems.tip.remove(); }
2764
+ tooltip.unbind(namespace);
2765
+ }
2766
+ });
2767
+
2768
+ self.init();
2769
+ }
2770
+
2771
+ PLUGINS.tip = function(api)
2772
+ {
2773
+ var self = api.plugins.tip;
2774
+
2775
+ return 'object' === typeof self ? self : (api.plugins.tip = new Tip(api));
2776
+ };
2777
+
2778
+ // Initialize tip on render
2779
+ PLUGINS.tip.initialize = 'render';
2780
+
2781
+ // Setup plugin sanitization options
2782
+ PLUGINS.tip.sanitize = function(options)
2783
+ {
2784
+ var style = options.style, opts;
2785
+ if(style && 'tip' in style) {
2786
+ opts = options.style.tip;
2787
+ if(typeof opts !== 'object'){ options.style.tip = { corner: opts }; }
2788
+ // if(!(/string|boolean/i).test(typeof opts.corner)) { opts.corner = TRUE; }
2789
+ if(typeof opts.width !== 'number'){ delete opts.width; }
2790
+ if(typeof opts.height !== 'number'){ delete opts.height; }
2791
+ if(typeof opts.border !== 'number' && opts.border !== TRUE){ delete opts.border; }
2792
+ if(typeof opts.offset !== 'number'){ delete opts.offset; }
2793
+ }
2794
+ };
2795
+
2796
+ // Extend original qTip defaults
2797
+ $.extend(TRUE, QTIP.defaults, {
2798
+ style: {
2799
+ tip: {
2800
+ corner: TRUE,
2801
+ mimic: FALSE,
2802
+ width: 6,
2803
+ height: 6,
2804
+ border: TRUE,
2805
+ offset: 0
2806
+ }
2807
+ }
2808
+ });
2809
+
2810
+ PLUGINS.svg = function(svg, corner)
2811
+ {
2812
+ var doc = $(document),
2813
+ elem = svg[0],
2814
+ result = {
2815
+ width: 0, height: 0,
2816
+ offset: { top: 1e10, left: 1e10 }
2817
+ },
2818
+ box, mtx, root, point, tPoint;
2819
+
2820
+ if (elem.getBBox && elem.parentNode) {
2821
+ box = elem.getBBox();
2822
+ mtx = elem.getScreenCTM();
2823
+ root = elem.farthestViewportElement || elem;
2824
+
2825
+ // Return if no method is found
2826
+ if(!root.createSVGPoint) { return result; }
2827
+
2828
+ // Create our point var
2829
+ point = root.createSVGPoint();
2830
+
2831
+ // Adjust top and left
2832
+ point.x = box.x;
2833
+ point.y = box.y;
2834
+ tPoint = point.matrixTransform(mtx);
2835
+ result.offset.left = tPoint.x;
2836
+ result.offset.top = tPoint.y;
2837
+
2838
+ // Adjust width and height
2839
+ point.x += box.width;
2840
+ point.y += box.height;
2841
+ tPoint = point.matrixTransform(mtx);
2842
+ result.width = tPoint.x - result.offset.left;
2843
+ result.height = tPoint.y - result.offset.top;
2844
+
2845
+ // Adjust by scroll offset
2846
+ result.offset.left += doc.scrollLeft();
2847
+ result.offset.top += doc.scrollTop();
2848
+ }
2849
+
2850
+ return result;
2851
+ };
2852
+
2853
+ function Modal(api)
2854
+ {
2855
+ var self = this,
2856
+ options = api.options.show.modal,
2857
+ elems = api.elements,
2858
+ tooltip = elems.tooltip,
2859
+ overlaySelector = '#qtip-overlay',
2860
+ globalNamespace = '.qtipmodal',
2861
+ namespace = globalNamespace + api.id,
2862
+ attr = 'is-modal-qtip',
2863
+ docBody = $(document.body),
2864
+ overlay;
2865
+
2866
+ // Setup option set checks
2867
+ api.checks.modal = {
2868
+ '^show.modal.(on|blur)$': function() {
2869
+ // Initialise
2870
+ self.init();
2871
+
2872
+ // Show the modal if not visible already and tooltip is visible
2873
+ elems.overlay.toggle( tooltip.is(':visible') );
2874
+ }
2875
+ };
2876
+
2877
+ $.extend(self, {
2878
+ init: function()
2879
+ {
2880
+ // If modal is disabled... return
2881
+ if(!options.on) { return self; }
2882
+
2883
+ // Create the overlay if needed
2884
+ overlay = self.create();
2885
+
2886
+ // Add unique attribute so we can grab modal tooltips easily via a selector
2887
+ tooltip.attr(attr, TRUE)
2888
+
2889
+ // Set z-index
2890
+ .css('z-index', PLUGINS.modal.zindex + $(selector+'['+attr+']').length)
2891
+
2892
+ // Remove previous bound events in globalNamespace
2893
+ .unbind(globalNamespace).unbind(namespace)
2894
+
2895
+ // Apply our show/hide/focus modal events
2896
+ .bind('tooltipshow'+globalNamespace+' tooltiphide'+globalNamespace, function(event, api, duration) {
2897
+ var oEvent = event.originalEvent;
2898
+
2899
+ // Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop
2900
+ if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(overlay[0]).length) {
2901
+ try { event.preventDefault(); } catch(e) {}
2902
+ }
2903
+ else if(oEvent && !oEvent.solo){
2904
+ self[ event.type.replace('tooltip', '') ](event, duration);
2905
+ }
2906
+ })
2907
+
2908
+ // Adjust modal z-index on tooltip focus
2909
+ .bind('tooltipfocus'+globalNamespace, function(event) {
2910
+ // If focus was cancelled before it reearch us, don't do anything
2911
+ if(event.isDefaultPrevented()) { return; }
2912
+
2913
+ var qtips = $(selector).filter('['+attr+']'),
2914
+
2915
+ // Keep the modal's lower than other, regular qtips
2916
+ newIndex = PLUGINS.modal.zindex + qtips.length,
2917
+ curIndex = parseInt(tooltip[0].style.zIndex, 10);
2918
+
2919
+ // Set overlay z-index
2920
+ overlay[0].style.zIndex = newIndex - 1;
2921
+
2922
+ // Reduce modal z-index's and keep them properly ordered
2923
+ qtips.each(function() {
2924
+ if(this.style.zIndex > curIndex) {
2925
+ this.style.zIndex -= 1;
2926
+ }
2927
+ });
2928
+
2929
+ // Fire blur event for focused tooltip
2930
+ qtips.end().filter('.' + focusClass).qtip('blur', event.originalEvent);
2931
+
2932
+ // Set the new z-index
2933
+ tooltip.addClass(focusClass)[0].style.zIndex = newIndex;
2934
+
2935
+ // Prevent default handling
2936
+ try { event.preventDefault(); } catch(e) {}
2937
+ })
2938
+
2939
+ // Focus any other visible modals when this one hides
2940
+ .bind('tooltiphide'+globalNamespace, function(event) {
2941
+ $('[' + attr + ']').filter(':visible').not(tooltip).last().qtip('focus', event);
2942
+ });
2943
+
2944
+ // Apply keyboard "Escape key" close handler
2945
+ if(options.escape) {
2946
+ $(window).unbind(namespace).bind('keydown'+namespace, function(event) {
2947
+ if(event.keyCode === 27 && tooltip.hasClass(focusClass)) {
2948
+ api.hide(event);
2949
+ }
2950
+ });
2951
+ }
2952
+
2953
+ // Apply click handler for blur option
2954
+ if(options.blur) {
2955
+ elems.overlay.unbind(namespace).bind('click'+namespace, function(event) {
2956
+ if(tooltip.hasClass(focusClass)&& options.blur) { api.hide(event); }
2957
+ });
2958
+ }
2959
+
2960
+ return self;
2961
+ },
2962
+
2963
+ create: function()
2964
+ {
2965
+ // TAKE CARE: we changed the insertAfter argument from the last qtip on the doom
2966
+ // to be the tooltip target itself. We did it because otherwise the modal might hide the target tooltip.
2967
+ // if it's positioned after a tooltip in another container.
2968
+ var elem = $(overlaySelector);
2969
+
2970
+ // Return if overlay is already rendered
2971
+ if(elem.length) {
2972
+ // Modal overlay should always be below all tooltips if possible
2973
+ return (elems.overlay = elem.insertAfter( tooltip ));
2974
+ }
2975
+
2976
+ // Create document overlay
2977
+ overlay = elems.overlay = $('<div />', {
2978
+ id: overlaySelector.substr(1),
2979
+ html: '<div></div>',
2980
+ mousedown: function() { return FALSE; }
2981
+ }).insertAfter( tooltip );
2982
+
2983
+ // Update position on window resize or scroll
2984
+ $(window).unbind(globalNamespace).bind('resize'+globalNamespace, function() {
2985
+ overlay.css({
2986
+ height: $(window).height(),
2987
+ width: $(window).width()
2988
+ });
2989
+ })
2990
+ .triggerHandler('resize');
2991
+
2992
+ return overlay;
2993
+ },
2994
+
2995
+ toggle: function(event, state, duration)
2996
+ {
2997
+ // Make sure default event hasn't been prevented
2998
+ if(event && event.isDefaultPrevented()) { return self; }
2999
+
3000
+ var effect = options.effect,
3001
+ type = state ? 'show': 'hide',
3002
+ visible = overlay.is(':visible'),
3003
+ modals = $('[' + attr + ']').filter(':visible').not(tooltip),
3004
+ zindex;
3005
+
3006
+ // Create our overlay if it isn't present already
3007
+ if(!overlay) { overlay = self.create(); }
3008
+
3009
+ // Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible
3010
+ if((overlay.is(':animated') && visible === state) || (!state && modals.length)) { return self; }
3011
+
3012
+ // State specific...
3013
+ if(state) {
3014
+ // Set position
3015
+ overlay.css({ left: 0, top: 0 });
3016
+
3017
+ // Toggle backdrop cursor style on show
3018
+ overlay.toggleClass('blurs', options.blur);
3019
+
3020
+ // Make sure we can't focus anything outside the tooltip
3021
+ docBody.bind('focusin'+namespace, function(event) {
3022
+ var target = $(event.target),
3023
+ container = target.closest('.qtip'),
3024
+
3025
+ // Determine if input container target is above this
3026
+ targetOnTop = container.length < 1 ? FALSE :
3027
+ (parseInt(container[0].style.zIndex, 10) > parseInt(tooltip[0].style.zIndex, 10));
3028
+
3029
+ // If we're showing a modal, but focus has landed on an input below
3030
+ // this modal, divert focus to the first visible input in this modal
3031
+ if(!targetOnTop && ($(event.target).closest(selector)[0] !== tooltip[0])) {
3032
+ tooltip.find('input:visible').filter(':first').focus();
3033
+ }
3034
+ });
3035
+ }
3036
+ else {
3037
+ // Undelegate focus handler
3038
+ docBody.undelegate('*', 'focusin'+namespace);
3039
+ }
3040
+
3041
+ // Stop all animations
3042
+ overlay.stop(TRUE, FALSE);
3043
+
3044
+ // Use custom function if provided
3045
+ if($.isFunction(effect)) {
3046
+ effect.call(overlay, state);
3047
+ }
3048
+
3049
+ // If no effect type is supplied, use a simple toggle
3050
+ else if(effect === FALSE) {
3051
+ overlay[ type ]();
3052
+ }
3053
+
3054
+ // Use basic fade function
3055
+ else {
3056
+ overlay.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() {
3057
+ if(!state) { $(this).hide(); }
3058
+ });
3059
+ }
3060
+
3061
+ // Reset position on hide
3062
+ if(!state) {
3063
+ overlay.queue(function(next) {
3064
+ overlay.css({ left: '', top: '' });
3065
+ next();
3066
+ });
3067
+ }
3068
+
3069
+ return self;
3070
+ },
3071
+
3072
+ show: function(event, duration) { return self.toggle(event, TRUE, duration); },
3073
+ hide: function(event, duration) { return self.toggle(event, FALSE, duration); },
3074
+
3075
+ destroy: function()
3076
+ {
3077
+ var delBlanket = overlay;
3078
+
3079
+ if(delBlanket) {
3080
+ // Check if any other modal tooltips are present
3081
+ delBlanket = $('[' + attr + ']').not(tooltip).length < 1;
3082
+
3083
+ // Remove overlay if needed
3084
+ if(delBlanket) {
3085
+ elems.overlay.remove();
3086
+ $(window).unbind(globalNamespace);
3087
+ }
3088
+ else {
3089
+ elems.overlay.unbind(globalNamespace+api.id);
3090
+ }
3091
+
3092
+ // Undelegate focus handler
3093
+ docBody.undelegate('*', 'focusin'+namespace);
3094
+ }
3095
+
3096
+ // Remove bound events
3097
+ return tooltip.removeAttr(attr).unbind(globalNamespace);
3098
+ }
3099
+ });
3100
+
3101
+ self.init();
3102
+ }
3103
+
3104
+ PLUGINS.modal = function(api) {
3105
+ var self = api.plugins.modal;
3106
+
3107
+ return 'object' === typeof self ? self : (api.plugins.modal = new Modal(api));
3108
+ };
3109
+
3110
+ // Plugin needs to be initialized on render
3111
+ PLUGINS.modal.initialize = 'render';
3112
+
3113
+ // Setup sanitiztion rules
3114
+ PLUGINS.modal.sanitize = function(opts) {
3115
+ if(opts.show) {
3116
+ if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; }
3117
+ else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; }
3118
+ }
3119
+ };
3120
+
3121
+ // Base z-index for all modal tooltips (use qTip core z-index as a base)
3122
+ PLUGINS.modal.zindex = QTIP.zindex -= 200;
3123
+
3124
+ // Extend original api defaults
3125
+ $.extend(TRUE, QTIP.defaults, {
3126
+ show: {
3127
+ modal: {
3128
+ on: FALSE,
3129
+ effect: TRUE,
3130
+ blur: TRUE,
3131
+ escape: TRUE
3132
+ }
3133
+ }
3134
+ });
3135
+
3136
+ /*
3137
+ * BGIFrame adaption (http://plugins.jquery.com/project/bgiframe)
3138
+ * Special thanks to Brandon Aaron
3139
+ */
3140
+ function BGIFrame(api)
3141
+ {
3142
+ var self = this,
3143
+ elems = api.elements,
3144
+ tooltip = elems.tooltip,
3145
+ namespace = '.bgiframe-' + api.id;
3146
+
3147
+ $.extend(self, {
3148
+ init: function()
3149
+ {
3150
+ // Create the BGIFrame element
3151
+ elems.bgiframe = $('<iframe class="ui-tooltip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" ' +
3152
+ ' style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); ' +
3153
+ '-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>');
3154
+
3155
+ // Append the new element to the tooltip
3156
+ elems.bgiframe.appendTo(tooltip);
3157
+
3158
+ // Update BGIFrame on tooltip move
3159
+ tooltip.bind('tooltipmove'+namespace, self.adjust);
3160
+ },
3161
+
3162
+ adjust: function()
3163
+ {
3164
+ var dimensions = api.get('dimensions'), // Determine current tooltip dimensions
3165
+ plugin = api.plugins.tip,
3166
+ tip = elems.tip,
3167
+ tipAdjust, offset;
3168
+
3169
+ // Adjust border offset
3170
+ offset = parseInt(tooltip.css('border-left-width'), 10) || 0;
3171
+ offset = { left: -offset, top: -offset };
3172
+
3173
+ // Adjust for tips plugin
3174
+ if(plugin && tip) {
3175
+ tipAdjust = (plugin.corner.precedance === 'x') ? ['width', 'left'] : ['height', 'top'];
3176
+ offset[ tipAdjust[1] ] -= tip[ tipAdjust[0] ]();
3177
+ }
3178
+
3179
+ // Update bgiframe
3180
+ elems.bgiframe.css(offset).css(dimensions);
3181
+ },
3182
+
3183
+ destroy: function()
3184
+ {
3185
+ // Remove iframe
3186
+ elems.bgiframe.remove();
3187
+
3188
+ // Remove bound events
3189
+ tooltip.unbind(namespace);
3190
+ }
3191
+ });
3192
+
3193
+ self.init();
3194
+ }
3195
+
3196
+ PLUGINS.bgiframe = function(api)
3197
+ {
3198
+ var browser = $.browser,
3199
+ self = api.plugins.bgiframe;
3200
+
3201
+ // Proceed only if the browser is IE6 and offending elements are present
3202
+ if($('select, object').length < 1 || !(browser.msie && browser.version.charAt(0) === '6')) {
3203
+ return FALSE;
3204
+ }
3205
+
3206
+ return 'object' === typeof self ? self : (api.plugins.bgiframe = new BGIFrame(api));
3207
+ };
3208
+
3209
+ // Plugin needs to be initialized on render
3210
+ PLUGINS.bgiframe.initialize = 'render';
3211
+
3212
+
3213
+ }(jQuery, window));