1 | /*! |
---|
2 | * jQuery clueTip plugin v1.2.6 |
---|
3 | * |
---|
4 | * Date: Sun Sep 09 22:07:58 2012 EDT |
---|
5 | * Requires: jQuery v1.3+ |
---|
6 | * |
---|
7 | * Copyright 2012, Karl Swedberg |
---|
8 | * Dual licensed under the MIT and GPL licenses: |
---|
9 | * http://www.opensource.org/licenses/mit-license.php |
---|
10 | * http://www.gnu.org/licenses/gpl.html |
---|
11 | * |
---|
12 | * |
---|
13 | * Examples can be found at http://plugins.learningjquery.com/cluetip/demo/ |
---|
14 | * |
---|
15 | */ |
---|
16 | |
---|
17 | (function($) { |
---|
18 | |
---|
19 | $.cluetip = { |
---|
20 | version: '1.2.6', |
---|
21 | |
---|
22 | // the HTML that will be used for the tooltip |
---|
23 | template: ['<div>', |
---|
24 | '<div class="cluetip-outer">', |
---|
25 | '<h3 class="cluetip-title ui-widget-header ui-cluetip-header"></h3>', |
---|
26 | '<div class="cluetip-inner ui-widget-content ui-cluetip-content"></div>', |
---|
27 | '</div>', |
---|
28 | '<div class="cluetip-extra"></div>', |
---|
29 | '</div>'].join(''), |
---|
30 | |
---|
31 | /* clueTip setup |
---|
32 | * the setup options are applied each time .cluetip() is called, |
---|
33 | * BUT only if <div id="cluetip"> is not already in the document |
---|
34 | */ |
---|
35 | setup: { |
---|
36 | // method to be used for inserting the clueTip into the DOM. |
---|
37 | // Permitted values are 'appendTo', 'prependTo', 'insertBefore', and 'insertAfter' |
---|
38 | insertionType: 'appendTo', |
---|
39 | // element in the DOM the plugin will reference when inserting the clueTip. |
---|
40 | insertionElement: 'body' |
---|
41 | }, |
---|
42 | |
---|
43 | /* |
---|
44 | * clueTip options |
---|
45 | * |
---|
46 | * each one can be explicitly overridden by changing its value. |
---|
47 | * for example: $.cluetip.defaults.width = 200; |
---|
48 | * or: $.fn.cluetip.defaults.width = 200; // for compatibility with previous clueTip versions |
---|
49 | * would change the default width for all clueTips to 200. |
---|
50 | * |
---|
51 | * each one can also be overridden by passing an options map to the cluetip method. |
---|
52 | * for example: $('a.example').cluetip({width: 200}); |
---|
53 | * would change the default width to 200 for clueTips invoked by a link with class of "example" |
---|
54 | * |
---|
55 | */ |
---|
56 | defaults: { |
---|
57 | multiple: false, // Allow a new tooltip to be created for each .cluetip() call |
---|
58 | width: 275, // The width of the clueTip |
---|
59 | height: 'auto', // The height of the clueTip |
---|
60 | cluezIndex: 97, // Sets the z-index style property of the clueTip |
---|
61 | positionBy: 'auto', // Sets the type of positioning: 'auto', 'mouse','bottomTop', 'topBottom', fixed' |
---|
62 | topOffset: 15, // Number of px to offset clueTip from top of invoking element |
---|
63 | leftOffset: 15, // Number of px to offset clueTip from left of invoking element |
---|
64 | snapToEdge: false, // For bottomTop and topBottom, snap to the top or bottom of the element. |
---|
65 | local: false, // Whether to use content from the same page for the clueTip's body |
---|
66 | localPrefix: null, // string to be prepended to the tip attribute if local is true |
---|
67 | localIdSuffix: null, // string to be appended to the cluetip content element's id if local is true |
---|
68 | hideLocal: true, // If local option is set to true, this determines whether local content |
---|
69 | // to be shown in clueTip should be hidden at its original location |
---|
70 | attribute: 'rel', // the attribute to be used for fetching the clueTip's body content |
---|
71 | titleAttribute: 'title', // the attribute to be used for fetching the clueTip's title |
---|
72 | splitTitle: '', // A character used to split the title attribute into the clueTip title and divs |
---|
73 | // within the clueTip body. more info below [6] |
---|
74 | escapeTitle: false, // whether to html escape the title attribute |
---|
75 | showTitle: true, // show title bar of the clueTip, even if title attribute not set |
---|
76 | cluetipClass: 'default',// class added to outermost clueTip div in the form of 'cluetip-' + clueTipClass. |
---|
77 | hoverClass: '', // class applied to the invoking element onmouseover and removed onmouseout |
---|
78 | waitImage: true, // whether to show a "loading" img, which is set in jquery.cluetip.css |
---|
79 | cursor: 'help', |
---|
80 | arrows: false, // if true, displays arrow on appropriate side of clueTip |
---|
81 | dropShadow: true, // set to false if you don't want the drop-shadow effect on the clueTip |
---|
82 | dropShadowSteps: 6, // adjusts the size of the drop shadow |
---|
83 | sticky: false, // keep visible until manually closed |
---|
84 | mouseOutClose: false, // close when clueTip is moused out: false, 'cluetip', 'link', 'both' |
---|
85 | delayedClose: 50, // close clueTip on a timed delay |
---|
86 | activation: 'hover', // set to 'click' to force user to click to show clueTip |
---|
87 | // set to 'focus' to show on focus of a form element and hide on blur |
---|
88 | clickThrough: true, // if true, and activation is not 'click', then clicking on link will take user to the link's href, |
---|
89 | // even if href and tipAttribute are equal |
---|
90 | tracking: false, // if true, clueTip will track mouse movement (experimental) |
---|
91 | closePosition: 'top', // location of close text for sticky cluetips; can be 'top', 'bottom', 'title' or 'none' |
---|
92 | closeText: 'Close', // text (or HTML) to to be clicked to close sticky clueTips |
---|
93 | truncate: 0, // number of characters to truncate clueTip's contents. if 0, no truncation occurs |
---|
94 | |
---|
95 | // effect and speed for opening clueTips |
---|
96 | fx: { |
---|
97 | open: 'show', // can be 'show' or 'slideDown' or 'fadeIn' |
---|
98 | openSpeed: '' |
---|
99 | }, |
---|
100 | |
---|
101 | // settings for when hoverIntent plugin is used |
---|
102 | hoverIntent: { |
---|
103 | sensitivity: 3, |
---|
104 | interval: 50, |
---|
105 | timeout: 0 |
---|
106 | }, |
---|
107 | |
---|
108 | // short-circuit function to run just before clueTip is shown. |
---|
109 | onActivate: function(e) {return true;}, |
---|
110 | // function to run just after clueTip is shown. |
---|
111 | onShow: function(ct, ci){}, |
---|
112 | // function to run just after clueTip is hidden. |
---|
113 | onHide: function(ct, ci){}, |
---|
114 | // whether to cache results of ajax request to avoid unnecessary hits to server |
---|
115 | ajaxCache: true, |
---|
116 | |
---|
117 | // process data retrieved via xhr before it's displayed |
---|
118 | ajaxProcess: function(data) { |
---|
119 | data = data.replace(/<(script|style|title)[^<]+<\/(script|style|title)>/gm, '').replace(/<(link|meta)[^>]+>/g,''); |
---|
120 | return data; |
---|
121 | }, |
---|
122 | |
---|
123 | // can pass in standard $.ajax() parameters. Callback functions, such as beforeSend, |
---|
124 | // will be queued first within the default callbacks. |
---|
125 | // The only exception is error, which overrides the default |
---|
126 | ajaxSettings: { |
---|
127 | // error: function(ct, ci) { /* override default error callback */ }, |
---|
128 | // beforeSend: function(ct, ci) { /* called first within default beforeSend callback */ }, |
---|
129 | dataType: 'html' |
---|
130 | }, |
---|
131 | debug: false |
---|
132 | |
---|
133 | } |
---|
134 | }; |
---|
135 | var $cluetipWait, |
---|
136 | standardClasses = 'cluetip ui-widget ui-widget-content ui-cluetip', |
---|
137 | caches = {}, |
---|
138 | counter = 0, |
---|
139 | imgCount = 0; |
---|
140 | |
---|
141 | // use $.fn.prop() if available (jQuery 1.6+); otherwise, $.fn.attr() |
---|
142 | $.fn.attrProp = $.fn.prop || $.fn.attr; |
---|
143 | |
---|
144 | // .cluetip() method |
---|
145 | $.fn.cluetip = function(js, options) { |
---|
146 | var $cluetip, $cluetipInner, $cluetipOuter, $cluetipTitle, $cluetipArrows, $dropShadow; |
---|
147 | if (typeof js == 'object') { |
---|
148 | options = js; |
---|
149 | js = null; |
---|
150 | } |
---|
151 | if (js == 'destroy') { |
---|
152 | var data = this.data('cluetip'); |
---|
153 | if ( data ) { |
---|
154 | $(data.selector).remove(); |
---|
155 | $.removeData(this, 'title'); |
---|
156 | $.removeData(this, 'cluetip'); |
---|
157 | $.removeData(this, 'cluetipMoc'); |
---|
158 | } |
---|
159 | $(document).unbind('.cluetip'); |
---|
160 | return this.unbind('.cluetip'); |
---|
161 | } |
---|
162 | |
---|
163 | // merge per-call options with defaults |
---|
164 | options = $.extend(true, {}, $.cluetip.defaults, options || {}); |
---|
165 | |
---|
166 | /** =create cluetip divs **/ |
---|
167 | counter++; |
---|
168 | var cluezIndex, |
---|
169 | cluetipId = $.cluetip.backCompat || !options.multiple ? 'cluetip' : 'cluetip-' + counter, |
---|
170 | cluetipSelector = '#' + cluetipId, |
---|
171 | prefix = $.cluetip.backCompat ? '#' : '.', |
---|
172 | insertionType = $.cluetip.setup.insertionType, |
---|
173 | insertionElement = $.cluetip.setup.insertionElement || 'body'; |
---|
174 | |
---|
175 | insertionType = (/appendTo|prependTo|insertBefore|insertAfter/).test(insertionType) ? insertionType : 'appendTo'; |
---|
176 | $cluetip = $(cluetipSelector); |
---|
177 | if (!$cluetip.length) { |
---|
178 | |
---|
179 | $cluetip = $($.cluetip.template) |
---|
180 | [insertionType](insertionElement) |
---|
181 | .attr('id', cluetipId) |
---|
182 | .css({position: 'absolute', display: 'none'}); |
---|
183 | |
---|
184 | cluezIndex = +options.cluezIndex; |
---|
185 | $cluetipOuter = $cluetip.find(prefix + 'cluetip-outer').css({position: 'relative', zIndex: cluezIndex}); |
---|
186 | $cluetipInner = $cluetip.find(prefix + 'cluetip-inner'); |
---|
187 | $cluetipTitle = $cluetip.find(prefix + 'cluetip-title'); |
---|
188 | |
---|
189 | $cluetip.bind('mouseenter mouseleave', function(event) { |
---|
190 | $(this).data('entered', event.type === 'mouseenter'); |
---|
191 | }); |
---|
192 | } |
---|
193 | |
---|
194 | $cluetipWait = $('#cluetip-waitimage'); |
---|
195 | if (!$cluetipWait.length && options.waitImage) { |
---|
196 | $cluetipWait = $('<div></div>').attr('id', 'cluetip-waitimage').css({position: 'absolute'}); |
---|
197 | $cluetipWait.insertBefore($cluetip).hide(); |
---|
198 | } |
---|
199 | |
---|
200 | |
---|
201 | var cluetipPadding = (parseInt($cluetip.css('paddingLeft'), 10) || 0) + (parseInt($cluetip.css('paddingRight'), 10) || 0); |
---|
202 | |
---|
203 | |
---|
204 | this.each(function(index) { |
---|
205 | var link = this, |
---|
206 | $link = $(this), |
---|
207 | // support metadata plugin (v1.0 and 2.0) |
---|
208 | opts = $.extend(true, {}, options, $.metadata ? $link.metadata() : $.meta ? $link.data() : $link.data('cluetip') || {}), |
---|
209 | // start out with no contents (for ajax activation) |
---|
210 | cluetipContents = false, |
---|
211 | isActive = false, |
---|
212 | closeOnDelay = null, |
---|
213 | tipAttribute = opts[opts.attribute] || |
---|
214 | ( opts.attribute == 'href' ? $link.attr(opts.attribute) : $link.attrProp(opts.attribute) || $link.attr(opts.attribute) ), |
---|
215 | ctClass = opts.cluetipClass; |
---|
216 | |
---|
217 | cluezIndex = +opts.cluezIndex; |
---|
218 | $link.data('cluetip', {title: link.title, zIndex: cluezIndex, selector: cluetipSelector}); |
---|
219 | |
---|
220 | if (opts.arrows && !$cluetip.find('.cluetip-arrows').length) { |
---|
221 | $cluetip.append('<div class="cluetip-arrows ui-state-default"></div>'); |
---|
222 | } |
---|
223 | |
---|
224 | if (!tipAttribute && !opts.splitTitle && !js) { |
---|
225 | return true; |
---|
226 | } |
---|
227 | // if hideLocal is set to true, on DOM ready hide the local content that will be displayed in the clueTip |
---|
228 | if (opts.local && opts.localPrefix) {tipAttribute = opts.localPrefix + tipAttribute;} |
---|
229 | if (opts.local && opts.hideLocal && tipAttribute) { $(tipAttribute + ':first').hide(); } |
---|
230 | |
---|
231 | var tOffset = parseInt(opts.topOffset, 10), lOffset = parseInt(opts.leftOffset, 10); |
---|
232 | // vertical measurement variables |
---|
233 | var tipHeight, wHeight, |
---|
234 | defHeight = isNaN(parseInt(opts.height, 10)) ? 'auto' : (/\D/g).test(opts.height) ? opts.height : opts.height + 'px'; |
---|
235 | var sTop, linkTop, linkBottom, posY, tipY, mouseY, baseline; |
---|
236 | // horizontal measurement variables |
---|
237 | var tipInnerWidth = parseInt(opts.width, 10) || 275, |
---|
238 | tipWidth = tipInnerWidth + cluetipPadding + opts.dropShadowSteps, |
---|
239 | linkWidth = this.offsetWidth, |
---|
240 | linkLeft, posX, tipX, mouseX, winWidth; |
---|
241 | |
---|
242 | // parse the title |
---|
243 | var tipParts; |
---|
244 | var tipTitle = (opts.attribute != 'title') ? $link.attrProp(opts.titleAttribute) || '' : ''; |
---|
245 | if (opts.splitTitle) { |
---|
246 | tipParts = tipTitle.split(opts.splitTitle); |
---|
247 | tipTitle = opts.showTitle || tipParts[0] === '' ? tipParts.shift() : ''; |
---|
248 | } |
---|
249 | if (opts.escapeTitle) { |
---|
250 | tipTitle = tipTitle.replace(/&/g,'&').replace(/>/g,'>').replace(/</g,'<'); |
---|
251 | } |
---|
252 | |
---|
253 | var localContent; |
---|
254 | function returnFalse() { return false; } |
---|
255 | |
---|
256 | // Keep track of mouse entered state on link |
---|
257 | $link.bind('mouseenter mouseleave', function(event) { |
---|
258 | var data = $link.data('cluetip'); |
---|
259 | data.entered = event.type === 'entered'; |
---|
260 | $link.data('cluetip', data); |
---|
261 | }); |
---|
262 | |
---|
263 | /*************************************** |
---|
264 | * ACTIVATION |
---|
265 | ****************************************/ |
---|
266 | |
---|
267 | //activate clueTip |
---|
268 | var activate = function(event) { |
---|
269 | var pY, ajaxMergedSettings, cacheKey, |
---|
270 | continueOn = opts.onActivate.call(link, event); |
---|
271 | |
---|
272 | if (continueOn === false) { |
---|
273 | return false; |
---|
274 | } |
---|
275 | |
---|
276 | isActive = true; |
---|
277 | |
---|
278 | // activate function may get called after an initialization of a |
---|
279 | // different target so need to re-get the Correct Cluetip object here |
---|
280 | $cluetip = $(cluetipSelector).css({position: 'absolute'}); |
---|
281 | $cluetipOuter = $cluetip.find(prefix + 'cluetip-outer'); |
---|
282 | $cluetipInner = $cluetip.find(prefix + 'cluetip-inner'); |
---|
283 | $cluetipTitle = $cluetip.find(prefix + 'cluetip-title'); |
---|
284 | $cluetipArrows = $cluetip.find(prefix + 'cluetip-arrows'); |
---|
285 | $cluetip.removeClass().css({width: tipInnerWidth}); |
---|
286 | if (tipAttribute == $link.attr('href')) { |
---|
287 | $link.css('cursor', opts.cursor); |
---|
288 | } |
---|
289 | if (opts.hoverClass) { |
---|
290 | $link.addClass(opts.hoverClass); |
---|
291 | } |
---|
292 | linkTop = posY = $link.offset().top; |
---|
293 | linkBottom = linkTop + $link.innerHeight(); |
---|
294 | linkLeft = $link.offset().left; |
---|
295 | |
---|
296 | // FIX: (bug 4412) |
---|
297 | linkWidth = $link.innerWidth(); |
---|
298 | if ( event.type == focus ) { |
---|
299 | // in focus event, no mouse position is available; this is needed with bottomTop: |
---|
300 | mouseX = linkLeft + ( linkWidth / 2 ) + lOffset; |
---|
301 | $cluetip.css({left: posX}); |
---|
302 | mouseY = posY + tOffset; |
---|
303 | } else { |
---|
304 | mouseX = event.pageX; |
---|
305 | mouseY = event.pageY; |
---|
306 | } |
---|
307 | //END OF FIX |
---|
308 | |
---|
309 | if (link.tagName.toLowerCase() != 'area') { |
---|
310 | sTop = $(document).scrollTop(); |
---|
311 | winWidth = $(window).width(); |
---|
312 | } |
---|
313 | // position clueTip horizontally |
---|
314 | if (opts.positionBy == 'fixed') { |
---|
315 | posX = linkWidth + linkLeft + lOffset; |
---|
316 | $cluetip.css({left: posX}); |
---|
317 | } else { |
---|
318 | posX = (linkWidth > linkLeft && linkLeft > tipWidth) || |
---|
319 | linkLeft + linkWidth + tipWidth + lOffset > winWidth ? |
---|
320 | linkLeft - tipWidth - lOffset : |
---|
321 | linkWidth + linkLeft + lOffset; |
---|
322 | if (link.tagName.toLowerCase() == 'area' || opts.positionBy == 'mouse' || linkWidth + tipWidth > winWidth) { // position by mouse |
---|
323 | if (mouseX + 20 + tipWidth > winWidth) { |
---|
324 | $cluetip.addClass('cluetip-' + ctClass); |
---|
325 | posX = (mouseX - tipWidth - lOffset) >= 0 ? mouseX - tipWidth - lOffset - parseInt($cluetip.css('marginLeft'),10) + parseInt($cluetipInner.css('marginRight'),10) : mouseX - (tipWidth/2); |
---|
326 | } else { |
---|
327 | posX = mouseX + lOffset; |
---|
328 | } |
---|
329 | } |
---|
330 | pY = posX < 0 ? event.pageY + tOffset : event.pageY; |
---|
331 | if (posX < 0 || opts.positionBy == 'bottomTop' || opts.positionBy == 'topBottom') { |
---|
332 | posX = (mouseX + (tipWidth/2) > winWidth) ? winWidth/2 - tipWidth/2 : Math.max(mouseX - (tipWidth/2),0); |
---|
333 | } |
---|
334 | } |
---|
335 | |
---|
336 | $cluetipArrows.css({zIndex: $link.data('cluetip').zIndex+1}); |
---|
337 | $cluetip.css({ |
---|
338 | left: posX, |
---|
339 | zIndex: $link.data('cluetip').zIndex |
---|
340 | }); |
---|
341 | wHeight = $(window).height(); |
---|
342 | |
---|
343 | /*************************************** |
---|
344 | * load a string from cluetip method's first argument |
---|
345 | ***************************************/ |
---|
346 | if (js) { |
---|
347 | if (typeof js == 'function') { |
---|
348 | js = js.call(link); |
---|
349 | } |
---|
350 | $cluetipInner.html(js); |
---|
351 | cluetipShow(pY); |
---|
352 | } |
---|
353 | /*************************************** |
---|
354 | * load the title attribute only (or user-selected attribute). |
---|
355 | * clueTip title is the string before the first delimiter |
---|
356 | * subsequent delimiters place clueTip body text on separate lines |
---|
357 | ***************************************/ |
---|
358 | |
---|
359 | else if (tipParts) { |
---|
360 | var tpl = tipParts.length; |
---|
361 | $cluetipInner.html(tpl ? tipParts[0] : ''); |
---|
362 | if (tpl > 1) { |
---|
363 | for (var i=1; i < tpl; i++){ |
---|
364 | $cluetipInner.append('<div class="split-body">' + tipParts[i] + '</div>'); |
---|
365 | } |
---|
366 | } |
---|
367 | cluetipShow(pY); |
---|
368 | } |
---|
369 | /*************************************** |
---|
370 | * load external file via ajax |
---|
371 | ***************************************/ |
---|
372 | |
---|
373 | else if ( !opts.local && tipAttribute.indexOf('#') !== 0 ) { |
---|
374 | if (/\.(jpe?g|tiff?|gif|png)(?:\?.*)?$/i.test(tipAttribute)) { |
---|
375 | $cluetipInner.html('<img src="' + tipAttribute + '" alt="' + tipTitle + '" />'); |
---|
376 | cluetipShow(pY); |
---|
377 | } else { |
---|
378 | var optionBeforeSend = opts.ajaxSettings.beforeSend, |
---|
379 | optionError = opts.ajaxSettings.error, |
---|
380 | optionSuccess = opts.ajaxSettings.success, |
---|
381 | optionComplete = opts.ajaxSettings.complete; |
---|
382 | |
---|
383 | cacheKey = getCacheKey(tipAttribute, opts.ajaxSettings.data); |
---|
384 | |
---|
385 | var ajaxSettings = { |
---|
386 | cache: opts.ajaxCache, // force requested page not to be cached by browser |
---|
387 | url: tipAttribute, |
---|
388 | beforeSend: function(xhr, settings) { |
---|
389 | if (optionBeforeSend) {optionBeforeSend.call(link, xhr, $cluetip, $cluetipInner, settings);} |
---|
390 | $cluetipOuter.children().empty(); |
---|
391 | if (opts.waitImage) { |
---|
392 | $cluetipWait |
---|
393 | .css({top: mouseY+20, left: mouseX+20, zIndex: $link.data('cluetip').zIndex-1}) |
---|
394 | .show(); |
---|
395 | } |
---|
396 | }, |
---|
397 | error: function(xhr, textStatus) { |
---|
398 | if ( options.ajaxCache && !caches[cacheKey] ) { |
---|
399 | caches[cacheKey] = {status: 'error', textStatus: textStatus, xhr: xhr}; |
---|
400 | } |
---|
401 | |
---|
402 | if (isActive) { |
---|
403 | if (optionError) { |
---|
404 | optionError.call(link, xhr, textStatus, $cluetip, $cluetipInner); |
---|
405 | } else { |
---|
406 | $cluetipInner.html('<i>sorry, the contents could not be loaded</i>'); |
---|
407 | } |
---|
408 | } |
---|
409 | }, |
---|
410 | success: function(data, textStatus, xhr) { |
---|
411 | if ( options.ajaxCache && !caches[cacheKey] ) { |
---|
412 | caches[cacheKey] = {status: 'success', data: data, textStatus: textStatus, xhr: xhr}; |
---|
413 | } |
---|
414 | |
---|
415 | cluetipContents = opts.ajaxProcess.call(link, data); |
---|
416 | |
---|
417 | // allow for changing the title based on data returned by xhr |
---|
418 | if ( typeof cluetipContents == 'object' && cluetipContents !== null ) { |
---|
419 | tipTitle = cluetipContents.title; |
---|
420 | cluetipContents = cluetipContents.content; |
---|
421 | } |
---|
422 | |
---|
423 | if (isActive) { |
---|
424 | if (optionSuccess) { |
---|
425 | optionSuccess.call(link, data, textStatus, $cluetip, $cluetipInner); |
---|
426 | } |
---|
427 | $cluetipInner.html(cluetipContents); |
---|
428 | |
---|
429 | } |
---|
430 | }, |
---|
431 | complete: function(xhr, textStatus) { |
---|
432 | if (optionComplete) { |
---|
433 | optionComplete.call(link, xhr, textStatus, $cluetip, $cluetipInner); |
---|
434 | } |
---|
435 | var imgs = $cluetipInner[0].getElementsByTagName('img'); |
---|
436 | imgCount = imgs.length; |
---|
437 | for (var i=0, l = imgs.length; i < l; i++) { |
---|
438 | if (imgs[i].complete) { |
---|
439 | imgCount--; |
---|
440 | } |
---|
441 | } |
---|
442 | if (imgCount && !$.browser.opera) { |
---|
443 | $(imgs).bind('load.ct error.ct', function() { |
---|
444 | imgCount--; |
---|
445 | if (imgCount === 0) { |
---|
446 | $cluetipWait.hide(); |
---|
447 | $(imgs).unbind('.ct'); |
---|
448 | if (isActive) { cluetipShow(pY); } |
---|
449 | } |
---|
450 | }); |
---|
451 | } else { |
---|
452 | $cluetipWait.hide(); |
---|
453 | if (isActive) { cluetipShow(pY); } |
---|
454 | } |
---|
455 | } |
---|
456 | }; |
---|
457 | |
---|
458 | ajaxMergedSettings = $.extend(true, {}, opts.ajaxSettings, ajaxSettings); |
---|
459 | |
---|
460 | if ( caches[cacheKey] ) { |
---|
461 | cachedAjax( caches[cacheKey], ajaxMergedSettings ); |
---|
462 | } else { |
---|
463 | $.ajax(ajaxMergedSettings); |
---|
464 | } |
---|
465 | } |
---|
466 | } |
---|
467 | /*************************************** |
---|
468 | * load an element from the same page |
---|
469 | ***************************************/ |
---|
470 | else if (opts.local) { |
---|
471 | var $localContent = $(tipAttribute + (/^#\S+$/.test(tipAttribute) ? '' : ':eq(' + index + ')')).clone(true).show(); |
---|
472 | if (opts.localIdSuffix) { |
---|
473 | $localContent.attr('id', $localContent[0].id + opts.localIdSuffix); |
---|
474 | } |
---|
475 | $cluetipInner.html($localContent); |
---|
476 | cluetipShow(pY); |
---|
477 | } |
---|
478 | }; |
---|
479 | |
---|
480 | // get dimensions and options for cluetip and prepare it to be shown |
---|
481 | var cluetipShow = function(bpY) { |
---|
482 | var $closeLink, dynamicClasses, heightDiff, |
---|
483 | titleHTML = tipTitle || opts.showTitle && ' ', |
---|
484 | bgY = '', direction = '', insufficientX = false; |
---|
485 | var stickyClose = { |
---|
486 | bottom: function($cLink) { |
---|
487 | $cLink.appendTo($cluetipInner); |
---|
488 | }, |
---|
489 | top: function($cLink) { |
---|
490 | $cLink.prependTo($cluetipInner); |
---|
491 | }, |
---|
492 | title: function($cLink) { |
---|
493 | $cLink.prependTo($cluetipTitle); |
---|
494 | } |
---|
495 | }; |
---|
496 | |
---|
497 | $cluetip.addClass('cluetip-' + ctClass); |
---|
498 | if (opts.truncate) { |
---|
499 | var $truncloaded = $cluetipInner.text().slice(0,opts.truncate) + '...'; |
---|
500 | $cluetipInner.html($truncloaded); |
---|
501 | } |
---|
502 | |
---|
503 | if (titleHTML) { |
---|
504 | $cluetipTitle.show().html(titleHTML); |
---|
505 | } else { |
---|
506 | $cluetipTitle.hide(); |
---|
507 | } |
---|
508 | |
---|
509 | if (opts.sticky) { |
---|
510 | if (stickyClose[opts.closePosition]) { |
---|
511 | $closeLink = $('<div class="cluetip-close"><a href="#">' + opts.closeText + '</a></div>'); |
---|
512 | stickyClose[opts.closePosition]( $closeLink ); |
---|
513 | $closeLink.bind('click.cluetip', function() { |
---|
514 | cluetipClose(); |
---|
515 | return false; |
---|
516 | }); |
---|
517 | } |
---|
518 | if (opts.mouseOutClose) { |
---|
519 | $link.unbind('mouseleave.cluetipMoc'); |
---|
520 | $cluetip.unbind('mouseleave.cluetipMoc'); |
---|
521 | if (opts.mouseOutClose == 'both' || opts.mouseOutClose == 'cluetip' || opts.mouseOutClose === true) { // true implies 'cluetip' for backwards compatability |
---|
522 | $cluetip.bind('mouseleave.cluetipMoc', mouseOutClose); |
---|
523 | } |
---|
524 | if (opts.mouseOutClose == 'both' || opts.mouseOutClose == 'link') { |
---|
525 | $link.bind('mouseleave.cluetipMoc', mouseOutClose); |
---|
526 | } |
---|
527 | } |
---|
528 | } |
---|
529 | |
---|
530 | // now that content is loaded, finish the positioning |
---|
531 | $cluetipOuter.css({zIndex: $link.data('cluetip').zIndex, overflow: defHeight == 'auto' ? 'visible' : 'auto', height: defHeight}); |
---|
532 | tipHeight = defHeight == 'auto' ? Math.max($cluetip.outerHeight(),$cluetip.height()) : parseInt(defHeight,10); |
---|
533 | tipY = posY; |
---|
534 | baseline = sTop + wHeight; |
---|
535 | insufficientX = (posX < mouseX && (Math.max(posX, 0) + tipWidth > mouseX)); |
---|
536 | if (opts.positionBy == 'fixed') { |
---|
537 | tipY = posY - opts.dropShadowSteps + tOffset; |
---|
538 | } else if (opts.positionBy == 'topBottom' || opts.positionBy == 'bottomTop' || insufficientX) { |
---|
539 | if (opts.positionBy == 'topBottom') { |
---|
540 | if (posY + tipHeight + tOffset < baseline && mouseY - sTop < tipHeight + tOffset) { |
---|
541 | direction = 'bottom'; |
---|
542 | } else { |
---|
543 | direction = 'top'; |
---|
544 | } |
---|
545 | } else if (opts.positionBy == 'bottomTop' || insufficientX) { |
---|
546 | if (posY + tipHeight + tOffset > baseline && mouseY - sTop > tipHeight + tOffset) { |
---|
547 | direction = 'top'; |
---|
548 | } else { |
---|
549 | direction = 'bottom'; |
---|
550 | } |
---|
551 | } |
---|
552 | // We should now have a direction. Compute tipY |
---|
553 | if (opts.snapToEdge) { |
---|
554 | if (direction == 'top') { |
---|
555 | tipY = linkTop - tipHeight - tOffset; |
---|
556 | } else if (direction == 'bottom') { |
---|
557 | tipY = linkBottom + tOffset; |
---|
558 | } |
---|
559 | } else { |
---|
560 | if (direction == 'top') { |
---|
561 | tipY = mouseY - tipHeight - tOffset; |
---|
562 | } else if (direction == 'bottom') { |
---|
563 | tipY = mouseY + tOffset; |
---|
564 | } |
---|
565 | } |
---|
566 | } else if ( posY + tipHeight + tOffset > baseline ) { |
---|
567 | tipY = (tipHeight >= wHeight) ? sTop : baseline - tipHeight - tOffset; |
---|
568 | } else if ($link.css('display') == 'block' || link.tagName.toLowerCase() == 'area' || opts.positionBy == "mouse") { |
---|
569 | tipY = bpY - tOffset; |
---|
570 | } else { |
---|
571 | tipY = posY - opts.dropShadowSteps; |
---|
572 | } |
---|
573 | if (direction === '') { |
---|
574 | direction = posX < linkLeft ? 'left' : 'right'; |
---|
575 | } |
---|
576 | // add classes |
---|
577 | dynamicClasses = ' clue-' + direction + '-' + ctClass + ' cluetip-' + ctClass; |
---|
578 | if (ctClass == 'rounded') { |
---|
579 | dynamicClasses += ' ui-corner-all'; |
---|
580 | } |
---|
581 | $cluetip.css({top: tipY + 'px'}).attrProp({'className': standardClasses + dynamicClasses}); |
---|
582 | // set up arrow positioning to align with element |
---|
583 | if (opts.arrows) { |
---|
584 | if ( /(left|right)/.test(direction) ) { |
---|
585 | heightDiff = $cluetip.height() - $cluetipArrows.height(); |
---|
586 | bgY = posX >= 0 && bpY > 0 ? (posY - tipY - opts.dropShadowSteps) : 0; |
---|
587 | bgY = heightDiff > bgY ? bgY : heightDiff; |
---|
588 | bgY += 'px'; |
---|
589 | } |
---|
590 | $cluetipArrows.css({top: bgY}).show(); |
---|
591 | } else { |
---|
592 | $cluetipArrows.hide(); |
---|
593 | } |
---|
594 | |
---|
595 | // (first hide, then) ***SHOW THE CLUETIP*** |
---|
596 | // handle dropshadow divs first |
---|
597 | $dropShadow = createDropShadows($cluetip, opts); |
---|
598 | if ($dropShadow && $dropShadow.length) { |
---|
599 | $dropShadow.hide().css({height: tipHeight, width: tipInnerWidth, zIndex: $link.data('cluetip').zIndex-1}).show(); |
---|
600 | } |
---|
601 | |
---|
602 | if (!closeOnDelay) { |
---|
603 | $cluetip.hide(); |
---|
604 | } |
---|
605 | clearTimeout(closeOnDelay); |
---|
606 | closeOnDelay = null; |
---|
607 | |
---|
608 | // show the cluetip |
---|
609 | $cluetip[opts.fx.open](opts.fx.openSpeed || 0); |
---|
610 | |
---|
611 | if ($.fn.bgiframe) { $cluetip.bgiframe(); } |
---|
612 | |
---|
613 | // trigger the optional onShow function |
---|
614 | opts.onShow.call(link, $cluetip, $cluetipInner); |
---|
615 | }; |
---|
616 | |
---|
617 | /*************************************** |
---|
618 | =INACTIVATION |
---|
619 | -------------------------------------- */ |
---|
620 | var inactivate = function(event) { |
---|
621 | isActive = false; |
---|
622 | $cluetipWait.hide(); |
---|
623 | if (!opts.sticky || (/click|toggle/).test(opts.activation) ) { |
---|
624 | // delayed close (not fully tested) |
---|
625 | if (opts.delayedClose > 0) { |
---|
626 | clearTimeout(closeOnDelay); |
---|
627 | closeOnDelay = null; |
---|
628 | closeOnDelay = setTimeout(cluetipClose, opts.delayedClose); |
---|
629 | } |
---|
630 | } |
---|
631 | |
---|
632 | if (opts.hoverClass) { |
---|
633 | $link.removeClass(opts.hoverClass); |
---|
634 | } |
---|
635 | }; |
---|
636 | |
---|
637 | // close cluetip and reset some things |
---|
638 | var cluetipClose = function(el) { |
---|
639 | var $closer = el && el.data('cluetip') ? el : $link, |
---|
640 | ct = $closer.data('cluetip') && $closer.data('cluetip').selector, |
---|
641 | ctSelector = ct || 'div.cluetip', |
---|
642 | $cluetip = $(ctSelector), |
---|
643 | $cluetipInner = $cluetip.find(prefix + 'cluetip-inner'), |
---|
644 | $cluetipArrows = $cluetip.find(prefix + 'cluetip-arrows'); |
---|
645 | |
---|
646 | $cluetip.hide().removeClass(); |
---|
647 | opts.onHide.call($closer[0], $cluetip, $cluetipInner); |
---|
648 | |
---|
649 | if (ct) { |
---|
650 | $closer.removeClass('cluetip-clicked'); |
---|
651 | $closer.css('cursor',''); |
---|
652 | } |
---|
653 | if (ct && tipTitle) { |
---|
654 | $closer.attrProp(opts.titleAttribute, tipTitle); |
---|
655 | } |
---|
656 | |
---|
657 | if (opts.arrows) { |
---|
658 | $cluetipArrows.css({top: ''}); |
---|
659 | } |
---|
660 | }; |
---|
661 | |
---|
662 | // Check to see if we should be closing by checking where the user is hovering. |
---|
663 | // We do a short 50ms delay for two reasons: to prevent flicker, and to allow the user time to hover on other element |
---|
664 | var mouseOutClose = function() { |
---|
665 | var el = this; |
---|
666 | clearTimeout(closeOnDelay); |
---|
667 | closeOnDelay = setTimeout(function() { |
---|
668 | var linkOver = $link.data('cluetip').entered, |
---|
669 | cluetipOver = $cluetip.data('entered'), |
---|
670 | entered = false; |
---|
671 | |
---|
672 | if ( opts.mouseOutClose == 'both' && (linkOver || cluetipOver) ) { |
---|
673 | entered = true; |
---|
674 | } |
---|
675 | // true implies 'cluetip' for backwards compatibility |
---|
676 | else if ( (opts.mouseOutClose === true || opts.mouseOutClose == 'cluetip') && cluetipOver) { |
---|
677 | entered = true; |
---|
678 | } |
---|
679 | else if (opts.mouseOutClose == 'link' && linkOver) { |
---|
680 | entered = true; |
---|
681 | } |
---|
682 | |
---|
683 | if ( !entered ) { |
---|
684 | // All checks pass, close the cluetip |
---|
685 | cluetipClose.call(el); |
---|
686 | } |
---|
687 | |
---|
688 | }, opts.delayedClose); |
---|
689 | }; |
---|
690 | |
---|
691 | $(document).unbind('hideCluetip.cluetip').bind('hideCluetip.cluetip', function(e) { |
---|
692 | cluetipClose( $(e.target) ); |
---|
693 | }); |
---|
694 | /*************************************** |
---|
695 | =BIND EVENTS |
---|
696 | -------------------------------------- */ |
---|
697 | // activate by click |
---|
698 | if ( (/click|toggle/).test(opts.activation) ) { |
---|
699 | $link.bind('click.cluetip', function(event) { |
---|
700 | if ($cluetip.is(':hidden') || !$link.is('.cluetip-clicked')) { |
---|
701 | activate(event); |
---|
702 | $('.cluetip-clicked').removeClass('cluetip-clicked'); |
---|
703 | $link.addClass('cluetip-clicked'); |
---|
704 | } else { |
---|
705 | inactivate(event); |
---|
706 | } |
---|
707 | return false; |
---|
708 | }); |
---|
709 | // activate by focus; inactivate by blur |
---|
710 | } else if (opts.activation == 'focus') { |
---|
711 | $link.bind('focus.cluetip', function(event) { |
---|
712 | $link.attrProp('title',''); |
---|
713 | activate(event); |
---|
714 | }); |
---|
715 | $link.bind('blur.cluetip', function(event) { |
---|
716 | $link.attrProp('title', $link.data('cluetip').title); |
---|
717 | inactivate(event); |
---|
718 | }); |
---|
719 | // activate by hover |
---|
720 | } else { |
---|
721 | |
---|
722 | // clicking is returned false if clickThrough option is set to false |
---|
723 | $link[opts.clickThrough ? 'unbind' : 'bind']('click.cluetip', returnFalse); |
---|
724 | |
---|
725 | //set up mouse tracking |
---|
726 | var mouseTracks = function(evt) { |
---|
727 | if (opts.tracking) { |
---|
728 | var trackX = posX - evt.pageX; |
---|
729 | var trackY = tipY ? tipY - evt.pageY : posY - evt.pageY; |
---|
730 | $link.bind('mousemove.cluetip', function(evt) { |
---|
731 | $cluetip.css({left: evt.pageX + trackX, top: evt.pageY + trackY }); |
---|
732 | }); |
---|
733 | } |
---|
734 | }; |
---|
735 | |
---|
736 | if ($.fn.hoverIntent && opts.hoverIntent) { |
---|
737 | $link.hoverIntent({ |
---|
738 | sensitivity: opts.hoverIntent.sensitivity, |
---|
739 | interval: opts.hoverIntent.interval, |
---|
740 | over: function(event) { |
---|
741 | activate(event); |
---|
742 | mouseTracks(event); |
---|
743 | }, |
---|
744 | timeout: opts.hoverIntent.timeout, |
---|
745 | out: function(event) { |
---|
746 | inactivate(event); |
---|
747 | $link.unbind('mousemove.cluetip'); |
---|
748 | } |
---|
749 | }); |
---|
750 | } else { |
---|
751 | $link.bind('mouseenter.cluetip', function(event) { |
---|
752 | activate(event); |
---|
753 | mouseTracks(event); |
---|
754 | }) |
---|
755 | .bind('mouseleave.cluetip', function(event) { |
---|
756 | inactivate(event); |
---|
757 | $link.unbind('mousemove.cluetip'); |
---|
758 | }); |
---|
759 | } |
---|
760 | |
---|
761 | $link.bind('mouseover.cluetip', function(event) { |
---|
762 | $link.attrProp('title',''); |
---|
763 | }).bind('mouseleave.cluetip', function(event) { |
---|
764 | $link.attrProp('title', $link.data('cluetip').title); |
---|
765 | }); |
---|
766 | } |
---|
767 | |
---|
768 | // trigger a cached Ajax response |
---|
769 | function cachedAjax(info, settings) { |
---|
770 | var status = info.status; |
---|
771 | settings.beforeSend(info.xhr, settings); |
---|
772 | if ( status == 'error' ) { |
---|
773 | settings[status](info.xhr, info.textStatus); |
---|
774 | } else if (status == 'success') { |
---|
775 | settings[status](info.data, info.textStatus, info.xhr); |
---|
776 | } |
---|
777 | settings.complete(info.xhr, settings.textStatus); |
---|
778 | } |
---|
779 | |
---|
780 | }); // end this.each |
---|
781 | |
---|
782 | /** =private functions |
---|
783 | ************************************************************/ |
---|
784 | //empty function |
---|
785 | function doNothing() {} |
---|
786 | |
---|
787 | // create a string to be used as an identifier for ajax caches |
---|
788 | function getCacheKey(url, data) { |
---|
789 | var cacheKey = url || ''; |
---|
790 | data = data || ''; |
---|
791 | |
---|
792 | if (typeof data == 'object') { |
---|
793 | $.each(data, function(key, val) { |
---|
794 | cacheKey += '-' + key + '-' + val; |
---|
795 | }); |
---|
796 | } else if (typeof data == 'string') { |
---|
797 | cacheKey += data; |
---|
798 | } |
---|
799 | |
---|
800 | return cacheKey; |
---|
801 | } |
---|
802 | |
---|
803 | /** =create dropshadow divs **/ |
---|
804 | |
---|
805 | function createDropShadows($cluetip, options, newDropShadow) { |
---|
806 | var dsStyle = '', |
---|
807 | dropShadowSteps = (options.dropShadow && options.dropShadowSteps) ? +options.dropShadowSteps : 0; |
---|
808 | |
---|
809 | if ($.support.boxShadow) { |
---|
810 | if ( dropShadowSteps ) { |
---|
811 | dsStyle = '1px 1px ' + dropShadowSteps + 'px rgba(0,0,0,0.5)'; |
---|
812 | } |
---|
813 | var dsOffsets = dropShadowSteps === 0 ? '0 0 ' : '1px 1px '; |
---|
814 | $cluetip.css($.support.boxShadow, dsStyle); |
---|
815 | return false; |
---|
816 | } |
---|
817 | var oldDropShadow = $cluetip.find('.cluetip-drop-shadow'); |
---|
818 | if (dropShadowSteps == oldDropShadow.length) { |
---|
819 | return oldDropShadow; |
---|
820 | } |
---|
821 | oldDropShadow.remove(); |
---|
822 | var dropShadows = []; |
---|
823 | for (var i=0; i < dropShadowSteps;) { |
---|
824 | dropShadows[i++] = '<div style="top:' + i + 'px;left:' + i + 'px;"></div>'; |
---|
825 | } |
---|
826 | |
---|
827 | newDropShadow = $(dropShadows.join('')) |
---|
828 | .css({ |
---|
829 | position: 'absolute', |
---|
830 | backgroundColor: '#000', |
---|
831 | zIndex: cluezIndex -1, |
---|
832 | opacity: 0.1 |
---|
833 | }) |
---|
834 | .addClass('cluetip-drop-shadow') |
---|
835 | .prependTo($cluetip); |
---|
836 | return newDropShadow; |
---|
837 | |
---|
838 | } |
---|
839 | |
---|
840 | return this; |
---|
841 | }; |
---|
842 | |
---|
843 | (function() { |
---|
844 | $.support = $.support || {}; |
---|
845 | // check support for CSS3 properties (currently only boxShadow) |
---|
846 | var div = document.createElement('div'), |
---|
847 | divStyle = div.style, |
---|
848 | styleProps = ['boxShadow'], |
---|
849 | prefixes = ['moz', 'Moz', 'webkit', 'o']; |
---|
850 | |
---|
851 | for (var i=0, sl = styleProps.length; i < sl; i++) { |
---|
852 | var prop = styleProps[i], |
---|
853 | uProp = prop.charAt(0).toUpperCase() + prop.slice(1); |
---|
854 | |
---|
855 | if ( typeof divStyle[ prop ] !== 'undefined' ) { |
---|
856 | $.support[ prop ] = prop; |
---|
857 | } else { |
---|
858 | for (var j=0, pl = prefixes.length; j < pl; j++) { |
---|
859 | |
---|
860 | if (typeof divStyle[ prefixes[j] + uProp ] !== 'undefined') { |
---|
861 | $.support[ prop ] = prefixes[j] + uProp; |
---|
862 | break; |
---|
863 | } |
---|
864 | } |
---|
865 | } |
---|
866 | } |
---|
867 | div = null; |
---|
868 | })(); |
---|
869 | |
---|
870 | $.fn.cluetip.defaults = $.cluetip.defaults; |
---|
871 | |
---|
872 | })(jQuery); |
---|