iscjs 0.1

Sign up to get free protection for your applications and to get access to all the features.
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));