jquery.qtip.js 84 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966
  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: Mon May 2 14:06:03 PDT 2011
  13. */
  14. "use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
  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 */
  17. (function($, window, undefined) {
  18. // Munge the primitives - Paul Irish tip
  19. var TRUE = true,
  20. FALSE = false,
  21. NULL = null,
  22. // Shortcut vars
  23. QTIP, PLUGINS, MOUSE,
  24. uitooltip = 'ui-tooltip',
  25. widget = 'ui-widget',
  26. disabled = 'ui-state-disabled',
  27. selector = 'div.qtip.'+uitooltip,
  28. focusClass = uitooltip + '-focus',
  29. hoverClass = uitooltip + '-hover',
  30. hideOffset = '-31000px',
  31. replaceSuffix = '_replacedByqTip',
  32. oldtitle = 'oldtitle';
  33. // Simple console.error wrapper
  34. function debug() {
  35. var c = window.console;
  36. return c && (c.error || c.log || $.noop).apply(c, arguments);
  37. }
  38. // Option object sanitizer
  39. function sanitizeOptions(opts)
  40. {
  41. var content;
  42. if(!opts || 'object' !== typeof opts) { return FALSE; }
  43. if('object' !== typeof opts.metadata) {
  44. opts.metadata = {
  45. type: opts.metadata
  46. };
  47. }
  48. if('content' in opts) {
  49. if('object' !== typeof opts.content || opts.content.jquery) {
  50. opts.content = {
  51. text: opts.content
  52. };
  53. }
  54. content = opts.content.text || FALSE;
  55. if(!$.isFunction(content) && ((!content && !content.attr) || content.length < 1 || ('object' === typeof content && !content.jquery))) {
  56. opts.content.text = FALSE;
  57. }
  58. if('title' in opts.content) {
  59. if('object' !== typeof opts.content.title) {
  60. opts.content.title = {
  61. text: opts.content.title
  62. };
  63. }
  64. content = opts.content.title.text || FALSE;
  65. if(!$.isFunction(content) && ((!content && !content.attr) || content.length < 1 || ('object' === typeof content && !content.jquery))) {
  66. opts.content.title.text = FALSE;
  67. }
  68. }
  69. }
  70. if('position' in opts) {
  71. if('object' !== typeof opts.position) {
  72. opts.position = {
  73. my: opts.position,
  74. at: opts.position
  75. };
  76. }
  77. }
  78. if('show' in opts) {
  79. if('object' !== typeof opts.show) {
  80. if(opts.show.jquery) {
  81. opts.show = { target: opts.show };
  82. }
  83. else {
  84. opts.show = { event: opts.show };
  85. }
  86. }
  87. }
  88. if('hide' in opts) {
  89. if('object' !== typeof opts.hide) {
  90. if(opts.hide.jquery) {
  91. opts.hide = { target: opts.hide };
  92. }
  93. else {
  94. opts.hide = { event: opts.hide };
  95. }
  96. }
  97. }
  98. if('style' in opts) {
  99. if('object' !== typeof opts.style) {
  100. opts.style = {
  101. classes: opts.style
  102. };
  103. }
  104. }
  105. // Sanitize plugin options
  106. $.each(PLUGINS, function() {
  107. if(this.sanitize) { this.sanitize(opts); }
  108. });
  109. return opts;
  110. }
  111. /*
  112. * Core plugin implementation
  113. */
  114. function QTip(target, options, id, attr)
  115. {
  116. // Declare this reference
  117. var self = this,
  118. docBody = document.body,
  119. tooltipID = uitooltip + '-' + id,
  120. isPositioning = 0,
  121. isDrawing = 0,
  122. tooltip = $(),
  123. elements, cache;
  124. // Setup class attributes
  125. self.id = id;
  126. self.rendered = FALSE;
  127. self.elements = elements = { target: target };
  128. self.timers = { img: [] };
  129. self.options = options;
  130. self.checks = {};
  131. self.plugins = {};
  132. self.cache = cache = {
  133. event: {},
  134. target: NULL,
  135. disabled: FALSE,
  136. attr: attr
  137. };
  138. /*
  139. * Private core functions
  140. */
  141. function convertNotation(notation)
  142. {
  143. var i = 0, obj, option = options,
  144. // Split notation into array
  145. levels = notation.split('.');
  146. // Loop through
  147. while( option = option[ levels[i++] ] ) {
  148. if(i < levels.length) { obj = option; }
  149. }
  150. return [obj || options, levels.pop()];
  151. }
  152. function setWidget() {
  153. var on = options.style.widget;
  154. tooltip.toggleClass(widget, on);
  155. elements.content.toggleClass(widget+'-content', on);
  156. if(elements.titlebar){
  157. elements.titlebar.toggleClass(widget+'-header', on);
  158. }
  159. if(elements.button){
  160. elements.button.toggleClass(uitooltip+'-icon', !on);
  161. }
  162. }
  163. function removeTitle()
  164. {
  165. if(elements.title) {
  166. elements.titlebar.remove();
  167. elements.titlebar = elements.title = elements.button = NULL;
  168. self.reposition();
  169. }
  170. }
  171. function createButton()
  172. {
  173. var button = options.content.title.button,
  174. isString = typeof button === 'string',
  175. close = isString ? button : 'Close tooltip';
  176. if(elements.button) { elements.button.remove(); }
  177. // Use custom button if one was supplied by user, else use default
  178. if(button.jquery) {
  179. elements.button = button;
  180. }
  181. else {
  182. elements.button = $('<a />', {
  183. 'class': 'ui-state-default ' + (options.style.widget ? '' : uitooltip+'-icon'),
  184. 'title': close,
  185. 'aria-label': close
  186. })
  187. .prepend(
  188. $('<span />', {
  189. 'class': 'ui-icon ui-icon-close',
  190. 'html': '&times;'
  191. })
  192. );
  193. }
  194. // Create button and setup attributes
  195. elements.button.appendTo(elements.titlebar)
  196. .attr('role', 'button')
  197. .hover(function(event){ $(this).toggleClass('ui-state-hover', event.type === 'mouseenter'); })
  198. .click(function(event) {
  199. if(!tooltip.hasClass(disabled)) { self.hide(event); }
  200. return FALSE;
  201. })
  202. .bind('mousedown keydown mouseup keyup mouseout', function(event) {
  203. $(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down');
  204. });
  205. // Redraw the tooltip when we're done
  206. self.redraw();
  207. }
  208. function createTitle()
  209. {
  210. var id = tooltipID+'-title';
  211. // Destroy previous title element, if present
  212. if(elements.titlebar) { removeTitle(); }
  213. // Create title bar and title elements
  214. elements.titlebar = $('<div />', {
  215. 'class': uitooltip + '-titlebar ' + (options.style.widget ? 'ui-widget-header' : '')
  216. })
  217. .append(
  218. elements.title = $('<div />', {
  219. 'id': id,
  220. 'class': uitooltip + '-title',
  221. 'aria-atomic': TRUE
  222. })
  223. )
  224. .insertBefore(elements.content);
  225. // Create button if enabled
  226. if(options.content.title.button) { createButton(); }
  227. // Redraw the tooltip dimensions if it's rendered
  228. else if(self.rendered){ self.redraw(); }
  229. }
  230. function updateButton(button)
  231. {
  232. var elem = elements.button,
  233. title = elements.title;
  234. // Make sure tooltip is rendered and if not, return
  235. if(!self.rendered) { return FALSE; }
  236. if(!button) {
  237. elem.remove();
  238. }
  239. else {
  240. if(!title) {
  241. createTitle();
  242. }
  243. createButton();
  244. }
  245. }
  246. function updateTitle(content)
  247. {
  248. var elem = elements.title;
  249. // Make sure tooltip is rendered and if not, return
  250. if(!self.rendered || !content) { return FALSE; }
  251. // Use function to parse content
  252. if($.isFunction(content)) {
  253. content = content.call(target, self) || '';
  254. }
  255. // Append new content if its a DOM array and show it if hidden
  256. if(content.jquery && content.length > 0) {
  257. elem.empty().append(content.css({ display: 'block' }));
  258. }
  259. // Content is a regular string, insert the new content
  260. else { elem.html(content); }
  261. // Redraw and reposition
  262. self.redraw();
  263. if(self.rendered && tooltip.is(':visible')) {
  264. self.reposition(cache.event);
  265. }
  266. }
  267. function updateContent(content, reposition)
  268. {
  269. var elem = elements.content;
  270. content = content || options.content.text;
  271. // Make sure tooltip is rendered and content is defined. If not return
  272. if(!self.rendered || !content) { return FALSE; }
  273. // Use function to parse content
  274. if($.isFunction(content)) {
  275. content = content.call(target, self) || '';
  276. }
  277. // Append new content if its a DOM array and show it if hidden
  278. if(content.jquery && content.length > 0) {
  279. elem.empty().append(content.css({ display: 'block' }));
  280. }
  281. // Content is a regular string, insert the new content
  282. else { elem.html(content); }
  283. // Image detection
  284. function detectImages(next) {
  285. var images;
  286. function imageLoad(event) {
  287. // If queue is empty after image removal, update tooltip and continue the queue
  288. if((images = images.not(this)).length === 0) {
  289. self.redraw();
  290. self.reposition(cache.event);
  291. next();
  292. }
  293. }
  294. // Find all content images without dimensions, and if no images were found, continue
  295. if((images = elem.find('img:not([height]):not([width])')).length === 0) { return imageLoad.call(images); }
  296. // Apply timer to each iamge to poll for dimensions
  297. images.each(function(i, elem) {
  298. (function timer(){
  299. var timers = self.timers.img;
  300. // When the dimensions are found, remove the image from the queue and stop timer
  301. if(elem.height && elem.width) {
  302. clearTimeout(timers[i]);
  303. return imageLoad.call(elem);
  304. }
  305. timers[i] = setTimeout(timer, 20);
  306. }());
  307. });
  308. }
  309. /*
  310. * If we're still rendering... insert into 'fx' queue our image dimension
  311. * checker which will halt the showing of the tooltip until image dimensions
  312. * can be detected properly.
  313. */
  314. if(self.rendered < 0) { tooltip.queue('fx', detectImages); }
  315. // We're fully rendered, so reset isDrawing flag and proceed without queue delay
  316. else { isDrawing = 0; detectImages($.noop); }
  317. return self;
  318. }
  319. function assignEvents(show, hide, tip, doc)
  320. {
  321. var namespace = '.qtip-'+id,
  322. posOptions = options.position,
  323. targets = {
  324. show: options.show.target,
  325. hide: options.hide.target,
  326. container: posOptions.container[0] === docBody ? $(document) : posOptions.container,
  327. doc: $(document)
  328. },
  329. events = {
  330. show: $.trim('' + options.show.event).split(' '),
  331. hide: $.trim('' + options.hide.event).split(' ')
  332. },
  333. IE6 = $.browser.msie && parseInt($.browser.version, 10) === 6,
  334. additional;
  335. // Define show event method
  336. function showMethod(event)
  337. {
  338. if(tooltip.hasClass(disabled)) { return FALSE; }
  339. // If set, hide tooltip when inactive for delay period
  340. targets.show.trigger('qtip-'+id+'-inactive');
  341. // Clear hide timers
  342. clearTimeout(self.timers.show);
  343. clearTimeout(self.timers.hide);
  344. // Start show timer
  345. var callback = function(){ self.show(event); };
  346. if(options.show.delay > 0) {
  347. self.timers.show = setTimeout(callback, options.show.delay);
  348. }
  349. else{ callback(); }
  350. }
  351. // Define hide method
  352. function hideMethod(event)
  353. {
  354. if(tooltip.hasClass(disabled)) { return FALSE; }
  355. // Check if new target was actually the tooltip element
  356. var relatedTarget = $(event.relatedTarget || event.target),
  357. ontoTooltip = relatedTarget.closest(selector)[0] === tooltip[0],
  358. ontoTarget = relatedTarget[0] === targets.show[0];
  359. // Clear timers and stop animation queue
  360. clearTimeout(self.timers.show);
  361. clearTimeout(self.timers.hide);
  362. // Prevent hiding if tooltip is fixed and event target is the tooltip. Or if mouse positioning is enabled and cursor momentarily overlaps
  363. if((posOptions.target === 'mouse' && ontoTooltip) || (options.hide.fixed && ((/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget))))
  364. {
  365. // Prevent default and popagation
  366. event.stopPropagation();
  367. event.preventDefault();
  368. return FALSE;
  369. }
  370. // If tooltip has displayed, start hide timer
  371. if(options.hide.delay > 0) {
  372. self.timers.hide = setTimeout(function(){ self.hide(event); }, options.hide.delay);
  373. }
  374. else{ self.hide(event); }
  375. }
  376. // Define inactive method
  377. function inactiveMethod(event)
  378. {
  379. if(tooltip.hasClass(disabled)) { return FALSE; }
  380. // Clear timer
  381. clearTimeout(self.timers.inactive);
  382. self.timers.inactive = setTimeout(function(){ self.hide(event); }, options.hide.inactive);
  383. }
  384. function repositionMethod(event) {
  385. if(tooltip.is(':visible')) { self.reposition(event); }
  386. }
  387. // Assign tooltip events
  388. if(tip) {
  389. // Enable hide.fixed
  390. if(options.hide.fixed) {
  391. // Add tooltip as a hide target
  392. targets.hide = targets.hide.add(tooltip);
  393. // Clear hide timer on tooltip hover to prevent it from closing
  394. tooltip.bind('mouseover'+namespace, function() {
  395. if(!tooltip.hasClass(disabled)) {
  396. clearTimeout(self.timers.hide);
  397. }
  398. });
  399. }
  400. // If mouse positioning is on, apply a mouseleave event so we don't get problems with overlapping
  401. if(posOptions.target === 'mouse' && posOptions.adjust.mouse && options.hide.event) {
  402. tooltip.bind('mouseleave'+namespace, function(event) {
  403. if((event.relatedTarget || event.target) !== targets.show[0]) { self.hide(event); }
  404. });
  405. }
  406. // Focus the tooltip on mouseenter (z-index stacking)
  407. tooltip.bind('mouseenter'+namespace, function(event) {
  408. self[ event.type === 'mouseenter' ? 'focus' : 'blur' ](event);
  409. });
  410. // Add hover class on mouseenter/mouseleave
  411. tooltip.bind('mouseenter'+namespace+' mouseleave'+namespace, function(event) {
  412. tooltip.toggleClass(hoverClass, event.type === 'mouseenter');
  413. });
  414. }
  415. // Assign hide events
  416. if(hide) {
  417. // Check if the tooltip hides when inactive
  418. if('number' === typeof options.hide.inactive) {
  419. // Bind inactive method to target as a custom event
  420. targets.show.bind('qtip-'+id+'-inactive', inactiveMethod);
  421. // Define events which reset the 'inactive' event handler
  422. $.each(QTIP.inactiveEvents, function(index, type){
  423. targets.hide.add(elements.tooltip).bind(type+namespace+'-inactive', inactiveMethod);
  424. });
  425. }
  426. /*
  427. * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
  428. * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
  429. */
  430. if(/mouse(over|enter)/i.test(options.show.event) && !/mouse(out|leave)/i.test(options.hide.event)) {
  431. targets.hide.bind('mouseleave'+namespace, function(event) {
  432. clearTimeout(self.timers.show);
  433. });
  434. }
  435. // Apply hide events
  436. $.each(events.hide, function(index, type) {
  437. var showIndex = $.inArray(type, events.show),
  438. targetHide = $(targets.hide);
  439. // Both events and targets are identical, apply events using a toggle
  440. if((showIndex > -1 && targetHide.add(targets.show).length === targetHide.length) || type === 'unfocus')
  441. {
  442. targets.show.bind(type+namespace, function(event)
  443. {
  444. if(tooltip.is(':visible')) { hideMethod(event); }
  445. else{ showMethod(event); }
  446. });
  447. // Don't bind the event again
  448. delete events.show[ showIndex ];
  449. }
  450. // Events are not identical, bind normally
  451. else{ targets.hide.bind(type+namespace, hideMethod); }
  452. });
  453. }
  454. // Apply show events
  455. if(show) {
  456. $.each(events.show, function(index, type) {
  457. targets.show.bind(type+namespace, showMethod);
  458. });
  459. // Check if the tooltip hides when mouse is moved a certain distance
  460. if('number' === typeof options.hide.distance) {
  461. // Bind mousemove to target to detect distance difference
  462. targets.show.bind('mousemove'+namespace, function(event) {
  463. var origin = cache.origin || {},
  464. limit = options.hide.distance,
  465. abs = Math.abs;
  466. // Check if the movement has gone beyond the limit, and hide it if so
  467. if(origin && (abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit)){
  468. self.hide(event);
  469. }
  470. });
  471. }
  472. }
  473. // Apply document events
  474. if(doc) {
  475. // Adjust positions of the tooltip on window resize if enabled
  476. if(posOptions.adjust.resize || posOptions.viewport) {
  477. $($.event.special.resize ? posOptions.viewport : window).bind('resize'+namespace, repositionMethod);
  478. }
  479. // Adjust tooltip position on scroll if screen adjustment is enabled
  480. if(posOptions.viewport || (IE6 && tooltip.css('position') === 'fixed')) {
  481. $(posOptions.viewport).bind('scroll'+namespace, repositionMethod);
  482. }
  483. // Hide tooltip on document mousedown if unfocus events are enabled
  484. if((/unfocus/i).test(options.hide.event)) {
  485. targets.doc.bind('mousedown'+namespace, function(event) {
  486. var $target = $(event.target);
  487. if($target.parents(selector).length === 0 && $target.add(target).length > 1 && tooltip.is(':visible') && !tooltip.hasClass(disabled)) {
  488. self.hide(event);
  489. }
  490. });
  491. }
  492. // Hide mouseleave/mouseout tooltips on window/frame blur/mouseleave
  493. if(options.hide.leave && (/mouseleave|mouseout/i).test(options.hide.event)) {
  494. $(window).bind(
  495. 'blur'+namespace+' mouse' + (options.hide.leave.indexOf('frame') > -1 ? 'out' : 'leave') + namespace,
  496. function(event) { if(!event.relatedTarget) { self.hide(event); } }
  497. );
  498. }
  499. // If mouse is the target, update tooltip position on document mousemove
  500. if(posOptions.target === 'mouse') {
  501. targets.doc.bind('mousemove'+namespace, function(event) {
  502. // Update the tooltip position only if the tooltip is visible and adjustment is enabled
  503. if(posOptions.adjust.mouse && !tooltip.hasClass(disabled) && tooltip.is(':visible')) {
  504. self.reposition(event || MOUSE);
  505. }
  506. });
  507. }
  508. }
  509. }
  510. function unassignEvents(show, hide, tooltip, doc)
  511. {
  512. doc = parseInt(doc, 10) !== 0;
  513. var namespace = '.qtip-'+id,
  514. targets = {
  515. show: show && options.show.target[0],
  516. hide: hide && options.hide.target[0],
  517. tooltip: tooltip && self.rendered && elements.tooltip[0],
  518. content: tooltip && self.rendered && elements.content[0],
  519. container: doc && options.position.container[0] === docBody ? document : options.position.container[0],
  520. window: doc && window
  521. };
  522. // Check if tooltip is rendered
  523. if(self.rendered)
  524. {
  525. $([]).pushStack(
  526. $.grep(
  527. [ targets.show, targets.hide, targets.tooltip, targets.container, targets.content, targets.window ],
  528. function(i){ return typeof i === 'object'; }
  529. )
  530. )
  531. .unbind(namespace);
  532. }
  533. // Tooltip isn't yet rendered, remove render event
  534. else if(show) { options.show.target.unbind(namespace+'-create'); }
  535. }
  536. // Setup builtin .set() option checks
  537. self.checks.builtin = {
  538. // Core checks
  539. '^id$': function(obj, o, v) {
  540. var id = v === TRUE ? QTIP.nextid : v,
  541. tooltipID = uitooltip + '-' + id;
  542. if(id !== FALSE && id.length > 0 && !$('#'+tooltipID).length) {
  543. tooltip[0].id = tooltipID;
  544. elements.content[0].id = tooltipID + '-content';
  545. elements.title[0].id = tooltipID + '-title';
  546. }
  547. },
  548. // Content checks
  549. '^content.text$': function(obj, o, v){ updateContent(v); },
  550. '^content.title.text$': function(obj, o, v) {
  551. // Remove title if content is null
  552. if(!v) { return removeTitle(); }
  553. // If title isn't already created, create it now and update
  554. if(!elements.title && v) { createTitle(); }
  555. updateTitle(v);
  556. },
  557. '^content.title.button$': function(obj, o, v){ updateButton(v); },
  558. // Position checks
  559. '^position.(my|at)$': function(obj, o, v){
  560. // Parse new corner value into Corner objecct
  561. if('string' === typeof v) {
  562. obj[o] = new PLUGINS.Corner(v);
  563. }
  564. },
  565. '^position.container$': function(obj, o, v){
  566. if(self.rendered) { tooltip.appendTo(v); }
  567. },
  568. // Show & hide checks
  569. '^(show|hide).(event|target|fixed|delay|inactive)$': function(obj, o, v, p, match) {
  570. // Setup arguments
  571. var args = [1,0,0];
  572. args[match[1] === 'show' ? 'push' : 'unshift'](0);
  573. unassignEvents.apply(self, args);
  574. assignEvents.apply(self, [1,1,0,0]);
  575. },
  576. '^show.ready$': function() {
  577. if(!self.rendered) { self.render(1); }
  578. else { self.show(); }
  579. },
  580. // Style checks
  581. '^style.classes$': function(obj, o, v) {
  582. $.attr(tooltip[0], 'class', uitooltip + ' qtip ui-helper-reset ' + v);
  583. },
  584. '^style.widget|content.title': setWidget,
  585. // Events check
  586. '^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) {
  587. tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v);
  588. }
  589. };
  590. /*
  591. * Public API methods
  592. */
  593. $.extend(self, {
  594. render: function(show)
  595. {
  596. if(self.rendered) { return self; } // If tooltip has already been rendered, exit
  597. var title = options.content.title.text,
  598. callback = $.Event('tooltiprender');
  599. // Add ARIA attributes to target
  600. $.attr(target[0], 'aria-describedby', tooltipID);
  601. // Create tooltip element
  602. tooltip = elements.tooltip = $('<div/>', {
  603. 'id': tooltipID,
  604. 'class': uitooltip + ' qtip ui-helper-reset ' + options.style.classes,
  605. 'width': options.style.width || '',
  606. /* ARIA specific attributes */
  607. 'role': 'alert',
  608. 'aria-live': 'polite',
  609. 'aria-atomic': FALSE,
  610. 'aria-describedby': tooltipID + '-content',
  611. 'aria-hidden': TRUE
  612. })
  613. .toggleClass(disabled, cache.disabled)
  614. .data('qtip', self)
  615. .appendTo(options.position.container)
  616. .append(
  617. // Create content element
  618. elements.content = $('<div />', {
  619. 'class': uitooltip + '-content',
  620. 'id': tooltipID + '-content',
  621. 'aria-atomic': TRUE
  622. })
  623. );
  624. // Set rendered flag and prevent redundant redraw calls for npw
  625. self.rendered = -1;
  626. isDrawing = 1;
  627. // Update title
  628. if(title) {
  629. createTitle();
  630. updateTitle(title);
  631. }
  632. // Set proper rendered flag and update content
  633. updateContent();
  634. self.rendered = TRUE;
  635. // Setup widget classes
  636. setWidget();
  637. // Assign passed event callbacks (before plugins!)
  638. $.each(options.events, function(name, callback) {
  639. if($.isFunction(callback)) {
  640. tooltip.bind(name === 'toggle' ? 'tooltipshow tooltiphide' : 'tooltip'+name, callback);
  641. }
  642. });
  643. // Initialize 'render' plugins
  644. $.each(PLUGINS, function() {
  645. if(this.initialize === 'render') { this(self); }
  646. });
  647. // Assign events
  648. assignEvents(1, 1, 1, 1);
  649. /* Queue this part of the render process in our fx queue so we can
  650. * load images before the tooltip renders fully.
  651. *
  652. * See: updateContent method
  653. */
  654. tooltip.queue('fx', function(next) {
  655. // Trigger tooltiprender event and pass original triggering event as original
  656. callback.originalEvent = cache.event;
  657. tooltip.trigger(callback, [self]);
  658. // Redraw the tooltip manually now we're fully rendered
  659. isDrawing = 0; self.redraw();
  660. // Update tooltip position and show tooltip if needed
  661. if(options.show.ready || show) {
  662. self.show(cache.event);
  663. }
  664. next(); // Move on to next method in queue
  665. });
  666. return self;
  667. },
  668. get: function(notation)
  669. {
  670. var result, o;
  671. switch(notation.toLowerCase())
  672. {
  673. case 'dimensions':
  674. result = {
  675. height: tooltip.outerHeight(), width: tooltip.outerWidth()
  676. };
  677. break;
  678. case 'offset':
  679. result = PLUGINS.offset(tooltip, options.position.container);
  680. break;
  681. default:
  682. o = convertNotation(notation.toLowerCase());
  683. result = o[0][ o[1] ];
  684. result = result.precedance ? result.string() : result;
  685. break;
  686. }
  687. return result;
  688. },
  689. set: function(option, value)
  690. {
  691. var rmove = /^position\.(my|at|adjust|target|container)|style|content|show\.ready/i,
  692. rdraw = /^content\.(title|attr)|style/i,
  693. reposition = FALSE,
  694. redraw = FALSE,
  695. checks = self.checks,
  696. name;
  697. function callback(notation, args) {
  698. var category, rule, match;
  699. for(category in checks) {
  700. for(rule in checks[category]) {
  701. if(match = (new RegExp(rule, 'i')).exec(notation)) {
  702. args.push(match);
  703. checks[category][rule].apply(self, args);
  704. }
  705. }
  706. }
  707. }
  708. // Convert singular option/value pair into object form
  709. if('string' === typeof option) {
  710. name = option; option = {}; option[name] = value;
  711. }
  712. else { option = $.extend(TRUE, {}, option); }
  713. // Set all of the defined options to their new values
  714. $.each(option, function(notation, value) {
  715. var obj = convertNotation( notation.toLowerCase() ), previous;
  716. // Set new obj value
  717. previous = obj[0][ obj[1] ];
  718. obj[0][ obj[1] ] = 'object' === typeof value && value.nodeType ? $(value) : value;
  719. // Set the new params for the callback
  720. option[notation] = [obj[0], obj[1], value, previous];
  721. // Also check if we need to reposition / redraw
  722. reposition = rmove.test(notation) || reposition;
  723. redraw = rdraw.test(notation) || redraw;
  724. });
  725. // Re-sanitize options
  726. sanitizeOptions(options);
  727. /*
  728. * Execute any valid callbacks for the set options
  729. * Also set isPositioning/isDrawing so we don't get loads of redundant repositioning
  730. * and redraw calls.
  731. */
  732. isPositioning = isDrawing = 1; $.each(option, callback); isPositioning = isDrawing = 0;
  733. // Update position / redraw if needed
  734. if(tooltip.is(':visible') && self.rendered) {
  735. if(reposition) {
  736. self.reposition( options.position.target === 'mouse' ? NULL : cache.event );
  737. }
  738. if(redraw) { self.redraw(); }
  739. }
  740. return self;
  741. },
  742. toggle: function(state, event)
  743. {
  744. // Make sure tooltip is rendered
  745. if(!self.rendered) {
  746. if(state) { self.render(1); } // Render the tooltip if showing and it isn't already
  747. else { return self; }
  748. }
  749. var type = state ? 'show' : 'hide',
  750. opts = options[type],
  751. visible = tooltip.is(':visible'),
  752. delay,
  753. callback;
  754. // Detect state if valid one isn't provided
  755. if((typeof state).search('boolean|number')) { state = !visible; }
  756. // Return if element is already in correct state
  757. if(visible === state) { return self; }
  758. // Try to prevent flickering when tooltip overlaps show element
  759. if(event) {
  760. if((/over|enter/).test(event.type) && (/out|leave/).test(cache.event.type) &&
  761. event.target === options.show.target[0] && tooltip.has(event.relatedTarget).length) {
  762. return self;
  763. }
  764. // Cache event
  765. cache.event = $.extend({}, event);
  766. }
  767. // Call API methods
  768. callback = $.Event('tooltip'+type);
  769. callback.originalEvent = event ? cache.event : NULL;
  770. tooltip.trigger(callback, [self, 90]);
  771. if(callback.isDefaultPrevented()){ return self; }
  772. // Set ARIA hidden status attribute
  773. $.attr(tooltip[0], 'aria-hidden', !!!state);
  774. // Execute state specific properties
  775. if(state) {
  776. // Store show origin coordinates
  777. cache.origin = $.extend({}, MOUSE);
  778. // Focus the tooltip
  779. self.focus(event);
  780. // Update tooltip content if it's a dynamic function
  781. if($.isFunction(options.content.text)) { updateContent(); }
  782. // Update the tooltip position
  783. self.reposition(event);
  784. // Hide other tooltips if tooltip is solo, using it as the context
  785. if(opts.solo) { $(selector, opts.solo).not(tooltip).qtip('hide', callback); }
  786. }
  787. else {
  788. // Clear show timer if we're hiding
  789. clearTimeout(self.timers.show);
  790. // Remove cached origin on hide
  791. delete cache.origin;
  792. // Blur the tooltip
  793. self.blur(event);
  794. }
  795. // Define post-animation state specific properties
  796. function after() {
  797. if(!state) {
  798. // Reset CSS states
  799. tooltip.css({
  800. display: '',
  801. visibility: '',
  802. width: '',
  803. opacity: '',
  804. left: '',
  805. top: ''
  806. });
  807. }
  808. else {
  809. // Prevent antialias from disappearing in IE by removing filter
  810. if($.browser.msie) { tooltip[0].style.removeAttribute('filter'); }
  811. // Remove overflow setting to prevent tip bugs
  812. tooltip.css('overflow', '');
  813. }
  814. }
  815. // Clear animation queue
  816. tooltip.stop(0, 1);
  817. // Use custom function if provided
  818. if($.isFunction(opts.effect)) {
  819. opts.effect.call(tooltip, self);
  820. tooltip.queue('fx', function(n){ after(); n(); });
  821. }
  822. // If no effect type is supplied, use a simple toggle
  823. else if(opts.effect === FALSE) {
  824. tooltip[ type ]();
  825. after.call(tooltip);
  826. }
  827. // Use basic fade function by default
  828. else { tooltip.fadeTo(90, state ? 1 : 0, after); }
  829. // If inactive hide method is set, active it
  830. if(state) { opts.target.trigger('qtip-'+id+'-inactive'); }
  831. return self;
  832. },
  833. show: function(event){ return self.toggle(TRUE, event); },
  834. hide: function(event){ return self.toggle(FALSE, event); },
  835. focus: function(event)
  836. {
  837. if(!self.rendered) { return self; }
  838. var qtips = $(selector),
  839. curIndex = parseInt(tooltip[0].style.zIndex, 10),
  840. newIndex = QTIP.zindex + qtips.length,
  841. cachedEvent = $.extend({}, event),
  842. focusedElem, callback;
  843. // Only update the z-index if it has changed and tooltip is not already focused
  844. if(!tooltip.hasClass(focusClass))
  845. {
  846. // Call API method
  847. callback = $.Event('tooltipfocus');
  848. callback.originalEvent = cachedEvent;
  849. tooltip.trigger(callback, [self, newIndex]);
  850. // If default action wasn't prevented...
  851. if(!callback.isDefaultPrevented()) {
  852. // Only update z-index's if they've changed
  853. if(curIndex !== newIndex) {
  854. // Reduce our z-index's and keep them properly ordered
  855. qtips.each(function() {
  856. if(this.style.zIndex > curIndex) {
  857. this.style.zIndex = this.style.zIndex - 1;
  858. }
  859. });
  860. // Fire blur event for focused tooltip
  861. qtips.filter('.' + focusClass).qtip('blur', cachedEvent);
  862. }
  863. // Set the new z-index
  864. tooltip.addClass(focusClass)[0].style.zIndex = newIndex;
  865. }
  866. }
  867. return self;
  868. },
  869. blur: function(event) {
  870. var cachedEvent = $.extend({}, event),
  871. callback;
  872. // Set focused status to FALSE
  873. tooltip.removeClass(focusClass);
  874. // Trigger blur event
  875. callback = $.Event('tooltipblur');
  876. callback.originalEvent = cachedEvent;
  877. tooltip.trigger(callback, [self]);
  878. return self;
  879. },
  880. reposition: function(event, effect)
  881. {
  882. if(!self.rendered || isPositioning) { return self; }
  883. // Set positioning flag
  884. isPositioning = 1;
  885. var target = options.position.target,
  886. posOptions = options.position,
  887. my = posOptions.my,
  888. at = posOptions.at,
  889. adjust = posOptions.adjust,
  890. method = adjust.method.split(' '),
  891. elemWidth = tooltip.outerWidth(),
  892. elemHeight = tooltip.outerHeight(),
  893. targetWidth = 0,
  894. targetHeight = 0,
  895. callback = $.Event('tooltipmove'),
  896. fixed = tooltip.css('position') === 'fixed',
  897. viewport = posOptions.viewport,
  898. position = { left: 0, top: 0 },
  899. tip = (self.plugins.tip || {}).corner,
  900. readjust = {
  901. // Repositioning method and axis detection
  902. horizontal: method[0],
  903. vertical: method[1] || method[0],
  904. tip: options.style.tip || {},
  905. // Reposition methods
  906. left: function(posLeft) {
  907. var isShift = readjust.horizontal === 'shift',
  908. viewportScroll = viewport.offset.left + viewport.scrollLeft,
  909. myWidth = my.x === 'left' ? elemWidth : my.x === 'right' ? -elemWidth : -elemWidth / 2,
  910. atWidth = at.x === 'left' ? targetWidth : at.x === 'right' ? -targetWidth : -targetWidth / 2,
  911. tipWidth = (readjust.tip.width + readjust.tip.border * 2) || 0,
  912. tipAdjust = tip && tip.precedance === 'x' && !isShift ? tipWidth : 0,
  913. overflowLeft = viewportScroll - posLeft - tipAdjust,
  914. overflowRight = posLeft + elemWidth - viewport.width - viewportScroll + tipAdjust,
  915. offset = myWidth - (my.precedance === 'x' || my.x === my.y ? atWidth : 0),
  916. isCenter = my.x === 'center';
  917. // Optional 'shift' style repositioning
  918. if(isShift) {
  919. tipAdjust = tip && tip.precedance === 'y' ? tipWidth : 0;
  920. offset = (my.x === 'left' ? 1 : -1) * myWidth - tipAdjust;
  921. // Adjust position but keep it within viewport dimensions
  922. position.left += overflowLeft > 0 ? overflowLeft : overflowRight > 0 ? -overflowRight : 0;
  923. position.left = Math.max(
  924. viewport.offset.left + (tipAdjust && tip.x === 'center' ? readjust.tip.offset : 0),
  925. posLeft - offset,
  926. Math.min(
  927. Math.max(viewport.offset.left + viewport.width, posLeft + offset),
  928. position.left
  929. )
  930. );
  931. }
  932. // Default 'flip' repositioning
  933. else {
  934. if(overflowLeft > 0 && (my.x !== 'left' || overflowRight > 0)) {
  935. position.left -= offset + (isCenter ? 0 : 2 * adjust.x);
  936. }
  937. else if(overflowRight > 0 && (my.x !== 'right' || overflowLeft > 0) ) {
  938. position.left -= isCenter ? -offset : offset + (2 * adjust.x);
  939. }
  940. if(position.left !== posLeft && isCenter) { position.left -= adjust.x; }
  941. // Make sure we haven't made things worse with the adjustment and return the adjusted difference
  942. if(position.left < viewportScroll && -position.left > overflowRight) { position.left = posLeft; }
  943. }
  944. return position.left - posLeft;
  945. },
  946. top: function(posTop) {
  947. var isShift = readjust.vertical === 'shift',
  948. viewportScroll = viewport.offset.top + viewport.scrollTop,
  949. myHeight = my.y === 'top' ? elemHeight : my.y === 'bottom' ? -elemHeight : -elemHeight / 2,
  950. atHeight = at.y === 'top' ? targetHeight : at.y === 'bottom' ? -targetHeight : -targetHeight / 2,
  951. tipHeight = (readjust.tip.height + readjust.tip.border * 2) || 0,
  952. tipAdjust = tip && tip.precedance === 'y' && !isShift ? tipHeight : 0,
  953. overflowTop = viewportScroll - posTop - tipAdjust,
  954. overflowBottom = posTop + elemHeight - viewport.height - viewportScroll + tipAdjust,
  955. offset = myHeight - (my.precedance === 'y' || my.x === my.y ? atHeight : 0),
  956. isCenter = my.y === 'center';
  957. // Optional 'shift' style repositioning
  958. if(isShift) {
  959. tipAdjust = tip && tip.precedance === 'x' ? tipHeight : 0;
  960. offset = (my.y === 'top' ? 1 : -1) * myHeight - tipAdjust;
  961. // Adjust position but keep it within viewport dimensions
  962. position.top += overflowTop > 0 ? overflowTop : overflowBottom > 0 ? -overflowBottom : 0;
  963. position.top = Math.max(
  964. viewport.offset.top + (tipAdjust && tip.x === 'center' ? readjust.tip.offset : 0),
  965. posTop - offset,
  966. Math.min(
  967. Math.max(viewport.offset.top + viewport.height, posTop + offset),
  968. position.top
  969. )
  970. );
  971. }
  972. // Default 'flip' repositioning
  973. else {
  974. if(overflowTop > 0 && (my.y !== 'top' || overflowBottom > 0)) {
  975. position.top -= offset + (isCenter ? 0 : 2 * adjust.y);
  976. }
  977. else if(overflowBottom > 0 && (my.y !== 'bottom' || overflowTop > 0) ) {
  978. position.top -= isCenter ? -offset : offset + (2 * adjust.y);
  979. }
  980. if(position.top !== posTop && isCenter) { position.top -= adjust.y; }
  981. // Make sure we haven't made things worse with the adjustment and return the adjusted difference
  982. if(position.top < 0 && -position.top > overflowBottom) { position.top = posTop; }
  983. }
  984. return position.top - posTop;
  985. }
  986. };
  987. // Check if mouse was the target
  988. if(target === 'mouse') {
  989. // Force left top to allow flipping
  990. at = { x: 'left', y: 'top' };
  991. // Use cached event if one isn't available for positioning
  992. event = event && (event.type === 'resize' || event.type === 'scroll') ? cache.event :
  993. !adjust.mouse && cache.origin ? cache.origin :
  994. MOUSE && (adjust.mouse || !event || !event.pageX) ? { pageX: MOUSE.pageX, pageY: MOUSE.pageY } :
  995. event;
  996. // Use event coordinates for position
  997. position = { top: event.pageY, left: event.pageX };
  998. }
  999. else {
  1000. // Check if event targetting is being used
  1001. if(target === 'event') {
  1002. if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') {
  1003. target = cache.target = $(event.target);
  1004. }
  1005. else {
  1006. target = cache.target;
  1007. }
  1008. }
  1009. // Parse the target into a jQuery object and make sure there's an element present
  1010. target = $(target).eq(0);
  1011. if(target.length === 0) { return self; }
  1012. // Check if window or document is the target
  1013. else if(target[0] === document || target[0] === window) {
  1014. targetWidth = PLUGINS.iOS ? window.innerWidth : target.width();
  1015. targetHeight = PLUGINS.iOS ? window.innerHeight : target.height();
  1016. if(target[0] === window) {
  1017. position = {
  1018. top: !fixed || PLUGINS.iOS ? viewport.scrollTop() : 0,
  1019. left: !fixed || PLUGINS.iOS ? viewport.scrollLeft() : 0
  1020. };
  1021. }
  1022. }
  1023. // Use Imagemap/SVG plugins if needed
  1024. else if(target.is('area') && PLUGINS.imagemap) {
  1025. position = PLUGINS.imagemap(target, at);
  1026. }
  1027. else if(target[0].namespaceURI === 'http://www.w3.org/2000/svg' && PLUGINS.svg) {
  1028. position = PLUGINS.svg(target, at);
  1029. }
  1030. else {
  1031. targetWidth = target.outerWidth();
  1032. targetHeight = target.outerHeight();
  1033. position = PLUGINS.offset(target, posOptions.container, fixed);
  1034. }
  1035. // Parse returned plugin values into proper variables
  1036. if(position.offset) {
  1037. targetWidth = position.width;
  1038. targetHeight = position.height;
  1039. position = position.offset;
  1040. }
  1041. // Adjust position relative to target
  1042. position.left += at.x === 'right' ? targetWidth : at.x === 'center' ? targetWidth / 2 : 0;
  1043. position.top += at.y === 'bottom' ? targetHeight : at.y === 'center' ? targetHeight / 2 : 0;
  1044. }
  1045. // Adjust position relative to tooltip
  1046. position.left += adjust.x + (my.x === 'right' ? -elemWidth : my.x === 'center' ? -elemWidth / 2 : 0);
  1047. position.top += adjust.y + (my.y === 'bottom' ? -elemHeight : my.y === 'center' ? -elemHeight / 2 : 0);
  1048. // Calculate collision offset values if viewport positioning is enabled
  1049. if(viewport.jquery && target[0] !== window && target[0] !== docBody &&
  1050. readjust.vertical+readjust.horizontal !== 'nonenone')
  1051. {
  1052. // Cache our viewport details
  1053. viewport = {
  1054. elem: viewport,
  1055. height: viewport[ (viewport[0] === window ? 'h' : 'outerH') + 'eight' ](),
  1056. width: viewport[ (viewport[0] === window ? 'w' : 'outerW') + 'idth' ](),
  1057. scrollLeft: viewport.scrollLeft(),
  1058. scrollTop: viewport.scrollTop(),
  1059. offset: viewport.offset() || { left: 0, top: 0 }
  1060. };
  1061. // Adjust position based onviewport and adjustment options
  1062. position.adjusted = {
  1063. left: readjust.horizontal !== 'none' ? readjust.left(position.left) : 0,
  1064. top: readjust.vertical !== 'none' ? readjust.top(position.top) : 0
  1065. };
  1066. }
  1067. //Viewport adjustment is disabled, set values to zero
  1068. else { position.adjusted = { left: 0, top: 0 }; }
  1069. // Set tooltip position class
  1070. tooltip.attr('class', function(i, val) {
  1071. return $.attr(this, 'class').replace(/ui-tooltip-pos-\w+/i, '');
  1072. })
  1073. .addClass(uitooltip + '-pos-' + my.abbreviation());
  1074. // Call API method
  1075. callback.originalEvent = $.extend({}, event);
  1076. tooltip.trigger(callback, [self, position, viewport.elem || viewport]);
  1077. if(callback.isDefaultPrevented()){ return self; }
  1078. delete position.adjusted;
  1079. // If effect is disabled, no animation is defined or positioning gives NaN out, set CSS directly
  1080. if(effect === FALSE || isNaN(position.left) || isNaN(position.top) || !$.isFunction(posOptions.effect)) {
  1081. tooltip.css(position);
  1082. }
  1083. // Use custom function if provided
  1084. else if($.isFunction(posOptions.effect)) {
  1085. posOptions.effect.call(tooltip, self, $.extend({}, position));
  1086. tooltip.queue(function(next) {
  1087. // Reset attributes to avoid cross-browser rendering bugs
  1088. $(this).css({ opacity: '', height: '' });
  1089. if($.browser.msie) { this.style.removeAttribute('filter'); }
  1090. next();
  1091. });
  1092. }
  1093. // Set positioning flag
  1094. isPositioning = 0;
  1095. return self;
  1096. },
  1097. // IMax/min width simulator function for all browsers.. yeaaah!
  1098. redraw: function()
  1099. {
  1100. if(self.rendered < 1 || options.style.width || isDrawing) { return self; }
  1101. var fluid = uitooltip + '-fluid',
  1102. container = options.position.container,
  1103. perc, width, max, min;
  1104. // Set drawing flag
  1105. isDrawing = 1;
  1106. // Reset width and add fluid class
  1107. tooltip.css('width', '').addClass(fluid);
  1108. // Grab our tooltip width (add 1 so we don't get wrapping problems in Gecko)
  1109. width = tooltip.width() + ($.browser.mozilla ? 1 : 0);
  1110. // Grab our max/min properties
  1111. max = tooltip.css('max-width') || '';
  1112. min = tooltip.css('min-width') || '';
  1113. // Parse into proper pixel values
  1114. perc = (max + min).indexOf('%') > -1 ? container.width() / 100 : 0;
  1115. max = ((max.indexOf('%') > -1 ? perc : 1) * parseInt(max, 10)) || width;
  1116. min = ((min.indexOf('%') > -1 ? perc : 1) * parseInt(min, 10)) || 0;
  1117. // Determine new dimension size based on max/min/current values
  1118. width = max + min ? Math.min(Math.max(width, min), max) : width;
  1119. // Set the newly calculated width and remvoe fluid class
  1120. tooltip.css('width', Math.round(width)).removeClass(fluid);
  1121. // Set drawing flag
  1122. isDrawing = 0;
  1123. return self;
  1124. },
  1125. disable: function(state)
  1126. {
  1127. var c = disabled;
  1128. if('boolean' !== typeof state) {
  1129. state = !(tooltip.hasClass(c) || cache.disabled);
  1130. }
  1131. if(self.rendered) {
  1132. tooltip.toggleClass(c, state);
  1133. $.attr(tooltip[0], 'aria-disabled', state);
  1134. }
  1135. else {
  1136. cache.disabled = !!state;
  1137. }
  1138. return self;
  1139. },
  1140. enable: function() { return self.disable(FALSE); },
  1141. destroy: function()
  1142. {
  1143. var t = target[0],
  1144. title = $.attr(t, oldtitle);
  1145. // Destroy tooltip and any associated plugins if rendered
  1146. if(self.rendered) {
  1147. tooltip.remove();
  1148. $.each(self.plugins, function() {
  1149. if(this.destroy) { this.destroy(); }
  1150. });
  1151. }
  1152. // Clear timers and remove bound events
  1153. clearTimeout(self.timers.show);
  1154. clearTimeout(self.timers.hide);
  1155. unassignEvents(1, 1, 1, 1);
  1156. // Remove api object
  1157. $.removeData(t, 'qtip');
  1158. // Reset old title attribute if removed
  1159. if(title) {
  1160. $.attr(t, 'title', title);
  1161. target.removeAttr(oldtitle);
  1162. }
  1163. // Remove ARIA attributes and bound qtip events
  1164. target.removeAttr('aria-describedby').unbind('.qtip');
  1165. return target;
  1166. }
  1167. });
  1168. }
  1169. // Initialization method
  1170. function init(id, opts)
  1171. {
  1172. var obj, posOptions, attr, config,
  1173. // Setup element references
  1174. elem = $(this),
  1175. docBody = $(document.body),
  1176. // Use document body instead of document element if needed
  1177. newTarget = this === document ? docBody : elem,
  1178. // Grab metadata from element if plugin is present
  1179. metadata = (elem.metadata) ? elem.metadata(opts.metadata) : NULL,
  1180. // If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise
  1181. metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL,
  1182. // Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,
  1183. html5 = elem.data(opts.metadata.name || 'qtipopts');
  1184. // If we don't get an object returned attempt to parse it manualyl without parseJSON
  1185. try { html5 = typeof html5 === 'string' ? (new Function("return " + html5))() : html5; }
  1186. catch(e) { debug('Unable to parse HTML5 attribute data: ' + html5); }
  1187. // Merge in and sanitize metadata
  1188. config = $.extend(TRUE, {}, QTIP.defaults, opts,
  1189. typeof html5 === 'object' ? sanitizeOptions(html5) : NULL,
  1190. sanitizeOptions(metadata5 || metadata));
  1191. // Remove metadata object so we don't interfere with other metadata calls
  1192. if(metadata) { $.removeData(this, 'metadata'); }
  1193. // Re-grab our positioning options now we've merged our metadata and set id to passed value
  1194. posOptions = config.position;
  1195. config.id = id;
  1196. // Setup missing content if none is detected
  1197. if('boolean' === typeof config.content.text) {
  1198. attr = elem.attr(config.content.attr);
  1199. // Grab from supplied attribute if available
  1200. if(config.content.attr !== FALSE && attr) { config.content.text = attr; }
  1201. // No valid content was found, abort render
  1202. else { return FALSE; }
  1203. }
  1204. // Setup target options
  1205. if(posOptions.container === FALSE) { posOptions.container = docBody; }
  1206. if(posOptions.target === FALSE) { posOptions.target = newTarget; }
  1207. if(config.show.target === FALSE) { config.show.target = newTarget; }
  1208. if(config.show.solo === TRUE) { config.show.solo = docBody; }
  1209. if(config.hide.target === FALSE) { config.hide.target = newTarget; }
  1210. if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; }
  1211. // Convert position corner values into x and y strings
  1212. posOptions.at = new PLUGINS.Corner(posOptions.at);
  1213. posOptions.my = new PLUGINS.Corner(posOptions.my);
  1214. // Destroy previous tooltip if overwrite is enabled, or skip element if not
  1215. if($.data(this, 'qtip')) {
  1216. if(config.overwrite) {
  1217. elem.qtip('destroy');
  1218. }
  1219. else if(config.overwrite === FALSE) {
  1220. return FALSE;
  1221. }
  1222. }
  1223. // Remove title attribute and store it if present
  1224. if($.attr(this, 'title')) {
  1225. $.attr(this, oldtitle, $.attr(this, 'title'));
  1226. this.removeAttribute('title');
  1227. }
  1228. // Initialize the tooltip and add API reference
  1229. obj = new QTip(elem, config, id, !!attr);
  1230. $.data(this, 'qtip', obj);
  1231. // Catch remove events on target element to destroy redundant tooltip
  1232. elem.bind('remove.qtip', function(){ obj.destroy(); });
  1233. return obj;
  1234. }
  1235. // jQuery $.fn extension method
  1236. QTIP = $.fn.qtip = function(options, notation, newValue)
  1237. {
  1238. var command = ('' + options).toLowerCase(), // Parse command
  1239. returned = NULL,
  1240. args = command === 'disable' ? [TRUE] : $.makeArray(arguments).slice(1, 10),
  1241. event = args[args.length - 1],
  1242. opts = this[0] ? $.data(this[0], 'qtip') : NULL;
  1243. // Check for API request
  1244. if((!arguments.length && opts) || command === 'api') {
  1245. return opts;
  1246. }
  1247. // Execute API command if present
  1248. else if('string' === typeof options)
  1249. {
  1250. this.each(function()
  1251. {
  1252. var api = $.data(this, 'qtip');
  1253. if(!api) { return TRUE; }
  1254. // Cache the event if possible
  1255. if(event && event.timeStamp) { api.cache.event = event; }
  1256. // Check for specific API commands
  1257. if((command === 'option' || command === 'options') && notation) {
  1258. if($.isPlainObject(notation) || newValue !== undefined) {
  1259. api.set(notation, newValue);
  1260. }
  1261. else {
  1262. returned = api.get(notation);
  1263. return FALSE;
  1264. }
  1265. }
  1266. // Execute API command
  1267. else if(api[command]) {
  1268. api[command].apply(api[command], args);
  1269. }
  1270. });
  1271. return returned !== NULL ? returned : this;
  1272. }
  1273. // No API commands. validate provided options and setup qTips
  1274. else if('object' === typeof options || !arguments.length)
  1275. {
  1276. opts = sanitizeOptions($.extend(TRUE, {}, options));
  1277. // Bind the qTips
  1278. return QTIP.bind.call(this, opts, event);
  1279. }
  1280. };
  1281. // $.fn.qtip Bind method
  1282. QTIP.bind = function(opts, event)
  1283. {
  1284. return this.each(function(i) {
  1285. var options, targets, events,
  1286. // Find next available ID, or use custom ID if provided
  1287. id = (!opts.id || opts.id === FALSE || opts.id.length < 1 || $('#'+uitooltip+'-'+opts.id).length) ? QTIP.nextid++ : opts.id,
  1288. // Setup events namespace
  1289. namespace = '.qtip-'+id+'-create',
  1290. // Initialize the qTip and re-grab newly sanitized options
  1291. api = init.call(this, id, opts);
  1292. if(api === FALSE) { return TRUE; }
  1293. options = api.options;
  1294. // Initialize plugins
  1295. $.each(PLUGINS, function() {
  1296. if(this.initialize === 'initialize') { this(api); }
  1297. });
  1298. // Determine hide and show targets
  1299. targets = { show: options.show.target, hide: options.hide.target };
  1300. events = {
  1301. show: $.trim('' + options.show.event).replace(/ /g, namespace+' ') + namespace,
  1302. hide: $.trim('' + options.hide.event).replace(/ /g, namespace+' ') + namespace
  1303. };
  1304. /*
  1305. * Make sure hoverIntent functions properly by using mouseleave as a hide event if
  1306. * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
  1307. */
  1308. if(/mouse(over|enter)/i.test(events.show) && !/mouse(out|leave)/i.test(events.hide)) {
  1309. events.hide += ' mouseleave' + namespace;
  1310. }
  1311. // Define hoverIntent function
  1312. function hoverIntent(event) {
  1313. function render() {
  1314. // Cache mouse coords,render and render the tooltip
  1315. api.render(typeof event === 'object' || options.show.ready);
  1316. // Unbind show and hide event
  1317. targets.show.unbind(events.show);
  1318. targets.hide.unbind(events.hide);
  1319. }
  1320. // Only continue if tooltip isn't disabled
  1321. if(api.cache.disabled) { return FALSE; }
  1322. // Cache the event data
  1323. api.cache.event = $.extend({}, event);
  1324. // Start the event sequence
  1325. if(options.show.delay > 0) {
  1326. clearTimeout(api.timers.show);
  1327. api.timers.show = setTimeout(render, options.show.delay);
  1328. if(events.show !== events.hide) {
  1329. targets.hide.bind(events.hide, function() { clearTimeout(api.timers.show); });
  1330. }
  1331. }
  1332. else { render(); }
  1333. }
  1334. // Bind show events to target
  1335. targets.show.bind(events.show, hoverIntent);
  1336. // Prerendering is enabled, create tooltip now
  1337. if(options.show.ready || options.prerender) { hoverIntent(event); }
  1338. });
  1339. };
  1340. // Setup base plugins
  1341. PLUGINS = QTIP.plugins = {
  1342. // Corner object parser
  1343. Corner: function(corner) {
  1344. corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, 'center').toLowerCase();
  1345. this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase();
  1346. this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase();
  1347. this.precedance = (corner.charAt(0).search(/^(t|b)/) > -1) ? 'y' : 'x';
  1348. this.string = function() { return this.precedance === 'y' ? this.y+this.x : this.x+this.y; };
  1349. this.abbreviation = function() {
  1350. var x = this.x.substr(0,1), y = this.y.substr(0,1);
  1351. return x === y ? x : (x === 'c' || (x !== 'c' && y !== 'c')) ? y + x : x + y;
  1352. };
  1353. },
  1354. // Custom (more correct for qTip!) offset calculator
  1355. offset: function(elem, container, fixed) {
  1356. var pos = elem.offset(),
  1357. parent = container,
  1358. deep = 0,
  1359. docBody = document.body,
  1360. coffset;
  1361. function scroll(e, i) {
  1362. pos.left += i * e.scrollLeft();
  1363. pos.top += i * e.scrollTop();
  1364. }
  1365. if(parent) {
  1366. // Compensate for non-static containers offset
  1367. do {
  1368. if(parent[0] === docBody) { break; }
  1369. else if(parent.css('position') !== 'static') {
  1370. coffset = parent.position();
  1371. pos.left -= coffset.left + (parseInt(parent.css('borderLeftWidth'), 10) || 0);
  1372. pos.top -= coffset.top + (parseInt(parent.css('borderTopWidth'), 10) || 0);
  1373. deep++;
  1374. }
  1375. }
  1376. while(parent = parent.offsetParent());
  1377. // Compensate for containers scroll if it also has an offsetParent
  1378. if(container[0] !== docBody || deep > 1) { scroll( container, 1 ); }
  1379. // Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2 - v4.0)
  1380. if((PLUGINS.iOS < 4.1 && PLUGINS.iOS > 3.1) || (!PLUGINS.iOS && fixed)) { scroll( $(window), -1 ); }
  1381. }
  1382. return pos;
  1383. },
  1384. /*
  1385. * iOS 3.2 - 4.0 scroll fix detection used in offset() function.
  1386. */
  1387. iOS: parseFloat(
  1388. ('' + (/CPU.*OS ([0-9_]{1,3})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1])
  1389. .replace('undefined', '3_2').replace('_','.')
  1390. ) || FALSE,
  1391. /*
  1392. * jQuery-secpfic $.fn overrides
  1393. */
  1394. fn: {
  1395. /* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
  1396. attr: function(attr, val) {
  1397. if(!this.length) { return; }
  1398. var self = this[0],
  1399. title = 'title',
  1400. api = $.data(self, 'qtip');
  1401. if(attr === title) {
  1402. if(arguments.length < 2) {
  1403. return $.attr(self, oldtitle);
  1404. }
  1405. else if(typeof api === 'object') {
  1406. // If qTip is rendered and title was originally used as content, update it
  1407. if(api && api.rendered && api.options.content.attr === title && api.cache.attr) {
  1408. api.set('content.text', val);
  1409. }
  1410. // Use the regular attr method to set, then cache the result
  1411. $.fn['attr'+replaceSuffix].apply(this, arguments);
  1412. $.attr(self, oldtitle, $.attr(self, title));
  1413. return this.removeAttr(title);
  1414. }
  1415. }
  1416. },
  1417. /* Allow clone to correctly retrieve cached title attributes */
  1418. clone: function(keepData) {
  1419. var titles = $([]), title = 'title', elem;
  1420. // Clone our element using the real clone method
  1421. elem = $.fn['clone'+replaceSuffix].apply(this, arguments)
  1422. // Grab all elements with an oldtitle set, and change it to regular title attribute
  1423. .filter('[oldtitle]').each(function() {
  1424. $.attr(this, title, $.attr(this, oldtitle));
  1425. this.removeAttribute(oldtitle);
  1426. })
  1427. .end();
  1428. return elem;
  1429. },
  1430. /*
  1431. * Taken directly from jQuery 1.8.2 widget source code
  1432. * Trigger 'remove' event on all elements on removal if jQuery UI isn't present
  1433. */
  1434. remove: $.ui ? NULL : function( selector, keepData ) {
  1435. $(this).each(function() {
  1436. if (!keepData) {
  1437. if (!selector || $.filter( selector, [ this ] ).length) {
  1438. $('*', this).add(this).each(function() {
  1439. $(this).triggerHandler('remove');
  1440. });
  1441. }
  1442. }
  1443. });
  1444. }
  1445. }
  1446. };
  1447. // Apply the fn overrides above
  1448. $.each(PLUGINS.fn, function(name, func) {
  1449. if(!func) { return TRUE; }
  1450. var old = $.fn[name+replaceSuffix] = $.fn[name];
  1451. $.fn[name] = function() {
  1452. return func.apply(this, arguments) || old.apply(this, arguments);
  1453. };
  1454. });
  1455. // Cache mousemove events for positioning purposes
  1456. $(document).bind('mousemove.qtip', function(event) {
  1457. MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' };
  1458. });
  1459. // Set global qTip properties
  1460. QTIP.version = 'nightly';
  1461. QTIP.nextid = 0;
  1462. QTIP.inactiveEvents = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' ');
  1463. QTIP.zindex = 15000;
  1464. // Define configuration defaults
  1465. QTIP.defaults = {
  1466. prerender: FALSE,
  1467. id: FALSE,
  1468. overwrite: TRUE,
  1469. content: {
  1470. text: TRUE,
  1471. attr: 'title',
  1472. title: {
  1473. text: FALSE,
  1474. button: FALSE
  1475. }
  1476. },
  1477. position: {
  1478. my: 'top left',
  1479. at: 'bottom right',
  1480. target: FALSE,
  1481. container: FALSE,
  1482. viewport: FALSE,
  1483. adjust: {
  1484. x: 0, y: 0,
  1485. mouse: TRUE,
  1486. resize: TRUE,
  1487. method: 'flip flip'
  1488. },
  1489. effect: TRUE
  1490. },
  1491. show: {
  1492. target: FALSE,
  1493. event: 'mouseenter',
  1494. effect: TRUE,
  1495. delay: 90,
  1496. solo: FALSE,
  1497. ready: FALSE
  1498. },
  1499. hide: {
  1500. target: FALSE,
  1501. event: 'mouseleave',
  1502. effect: TRUE,
  1503. delay: 0,
  1504. fixed: FALSE,
  1505. inactive: FALSE,
  1506. leave: 'window',
  1507. distance: FALSE
  1508. },
  1509. style: {
  1510. classes: '',
  1511. widget: FALSE,
  1512. width: FALSE
  1513. },
  1514. events: {
  1515. render: NULL,
  1516. move: NULL,
  1517. show: NULL,
  1518. hide: NULL,
  1519. toggle: NULL,
  1520. focus: NULL,
  1521. blur: NULL
  1522. }
  1523. };function Ajax(api)
  1524. {
  1525. var self = this,
  1526. tooltip = api.elements.tooltip,
  1527. opts = api.options.content.ajax,
  1528. namespace = '.qtip-ajax',
  1529. rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
  1530. first = TRUE;
  1531. api.checks.ajax = {
  1532. '^content.ajax': function(obj, name, v) {
  1533. // If content.ajax object was reset, set our local var
  1534. if(name === 'ajax') { opts = v; }
  1535. if(name === 'once') {
  1536. self.init();
  1537. }
  1538. else if(opts && opts.url) {
  1539. self.load();
  1540. }
  1541. else {
  1542. tooltip.unbind(namespace);
  1543. }
  1544. }
  1545. };
  1546. $.extend(self, {
  1547. init: function()
  1548. {
  1549. // Make sure ajax options are enabled and bind event
  1550. if(opts && opts.url) {
  1551. tooltip.unbind(namespace)[ opts.once ? 'one' : 'bind' ]('tooltipshow'+namespace, self.load);
  1552. }
  1553. return self;
  1554. },
  1555. load: function(event, first)
  1556. {
  1557. // Make sure default event hasn't been prevented
  1558. if(event && event.isDefaultPrevented()) { return self; }
  1559. var hasSelector = opts.url.indexOf(' '),
  1560. url = opts.url,
  1561. selector,
  1562. hideFirst = opts.once && !opts.loading && first;
  1563. // If loading option is disabled, hide the tooltip until content is retrieved (first time only)
  1564. if(hideFirst) { tooltip.css('visibility', 'hidden'); }
  1565. // Check if user delcared a content selector like in .load()
  1566. if(hasSelector > -1) {
  1567. selector = url.substr(hasSelector);
  1568. url = url.substr(0, hasSelector);
  1569. }
  1570. // Define common after callback for both success/error handlers
  1571. function after() {
  1572. // Re-display tip if loading and first time, and reset first flag
  1573. if(hideFirst) { tooltip.css('visibility', ''); first = FALSE; }
  1574. }
  1575. // Define success handler
  1576. function successHandler(content) {
  1577. if(selector) {
  1578. // Create a dummy div to hold the results and grab the selector element
  1579. content = $('<div/>')
  1580. // inject the contents of the document in, removing the scripts
  1581. // to avoid any 'Permission Denied' errors in IE
  1582. .append(content.replace(rscript, ""))
  1583. // Locate the specified elements
  1584. .find(selector);
  1585. }
  1586. // Set the content
  1587. api.set('content.text', content);
  1588. after(); // Call common callback
  1589. }
  1590. // Error handler
  1591. function errorHandler(xh, status, error){ api.set('content.text', status + ': ' + error); after(); }
  1592. function beforeSendHandler(XMLHttpRequest ){
  1593. var cToken = $.cookie(headtoken) || token;XMLHttpRequest.setRequestHeader(headtoken,cToken);
  1594. //createProgressBar();
  1595. }
  1596. // Setup $.ajax option object and process the request
  1597. $.ajax( $.extend({ success: successHandler, error: errorHandler, context: api ,beforeSend:beforeSendHandler}, opts, { url: url }) );
  1598. return self;
  1599. }
  1600. });
  1601. self.init();
  1602. }
  1603. PLUGINS.ajax = function(api)
  1604. {
  1605. var self = api.plugins.ajax;
  1606. return 'object' === typeof self ? self : (api.plugins.ajax = new Ajax(api));
  1607. };
  1608. PLUGINS.ajax.initialize = 'render';
  1609. // Setup plugin sanitization
  1610. PLUGINS.ajax.sanitize = function(options)
  1611. {
  1612. var content = options.content, opts;
  1613. if(content && 'ajax' in content) {
  1614. opts = content.ajax;
  1615. if(typeof opts !== 'object') { opts = options.content.ajax = { url: opts }; }
  1616. if('boolean' !== typeof opts.once && opts.once) { opts.once = !!opts.once; }
  1617. }
  1618. };
  1619. // Extend original api defaults
  1620. $.extend(TRUE, QTIP.defaults, {
  1621. content: {
  1622. ajax: {
  1623. loading: TRUE,
  1624. once: TRUE
  1625. }
  1626. }
  1627. });PLUGINS.imagemap = function(area, corner)
  1628. {
  1629. var shape = area.attr('shape').toLowerCase(),
  1630. baseCoords = area.attr('coords').split(','),
  1631. coords = [],
  1632. image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'),
  1633. imageOffset = image.offset(),
  1634. result = {
  1635. width: 0, height: 0,
  1636. offset: { top: 1e10, right: 0, bottom: 0, left: 1e10 }
  1637. },
  1638. i = 0, next = 0;
  1639. // POLY area coordinate calculator
  1640. // Special thanks to Ed Cradock for helping out with this.
  1641. // Uses a binary search algorithm to find suitable coordinates.
  1642. function polyCoordinates(result, coords)
  1643. {
  1644. var i = 0,
  1645. compareX = 1, compareY = 1,
  1646. realX = 0, realY = 0,
  1647. newWidth = result.width,
  1648. newHeight = result.height;
  1649. // Use a binary search algorithm to locate most suitable coordinate (hopefully)
  1650. while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0)
  1651. {
  1652. newWidth = Math.floor(newWidth / 2);
  1653. newHeight = Math.floor(newHeight / 2);
  1654. if(corner.x === 'left'){ compareX = newWidth; }
  1655. else if(corner.x === 'right'){ compareX = result.width - newWidth; }
  1656. else{ compareX += Math.floor(newWidth / 2); }
  1657. if(corner.y === 'top'){ compareY = newHeight; }
  1658. else if(corner.y === 'bottom'){ compareY = result.height - newHeight; }
  1659. else{ compareY += Math.floor(newHeight / 2); }
  1660. i = coords.length; while(i--)
  1661. {
  1662. if(coords.length < 2){ break; }
  1663. realX = coords[i][0] - result.offset.left;
  1664. realY = coords[i][1] - result.offset.top;
  1665. if((corner.x === 'left' && realX >= compareX) ||
  1666. (corner.x === 'right' && realX <= compareX) ||
  1667. (corner.x === 'center' && (realX < compareX || realX > (result.width - compareX))) ||
  1668. (corner.y === 'top' && realY >= compareY) ||
  1669. (corner.y === 'bottom' && realY <= compareY) ||
  1670. (corner.y === 'center' && (realY < compareY || realY > (result.height - compareY)))) {
  1671. coords.splice(i, 1);
  1672. }
  1673. }
  1674. }
  1675. return { left: coords[0][0], top: coords[0][1] };
  1676. }
  1677. // Make sure we account for padding and borders on the image
  1678. imageOffset.left += Math.ceil((image.outerWidth() - image.width()) / 2);
  1679. imageOffset.top += Math.ceil((image.outerHeight() - image.height()) / 2);
  1680. // Parse coordinates into proper array
  1681. if(shape === 'poly') {
  1682. i = baseCoords.length; while(i--)
  1683. {
  1684. next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ];
  1685. if(next[0] > result.offset.right){ result.offset.right = next[0]; }
  1686. if(next[0] < result.offset.left){ result.offset.left = next[0]; }
  1687. if(next[1] > result.offset.bottom){ result.offset.bottom = next[1]; }
  1688. if(next[1] < result.offset.top){ result.offset.top = next[1]; }
  1689. coords.push(next);
  1690. }
  1691. }
  1692. else {
  1693. coords = $.map(baseCoords, function(coord){ return parseInt(coord, 10); });
  1694. }
  1695. // Calculate details
  1696. switch(shape)
  1697. {
  1698. case 'rect':
  1699. result = {
  1700. width: Math.abs(coords[2] - coords[0]),
  1701. height: Math.abs(coords[3] - coords[1]),
  1702. offset: { left: coords[0], top: coords[1] }
  1703. };
  1704. break;
  1705. case 'circle':
  1706. result = {
  1707. width: coords[2] + 2,
  1708. height: coords[2] + 2,
  1709. offset: { left: coords[0], top: coords[1] }
  1710. };
  1711. break;
  1712. case 'poly':
  1713. $.extend(result, {
  1714. width: Math.abs(result.offset.right - result.offset.left),
  1715. height: Math.abs(result.offset.bottom - result.offset.top)
  1716. });
  1717. if(corner.string() === 'centercenter') {
  1718. result.offset = {
  1719. left: result.offset.left + (result.width / 2),
  1720. top: result.offset.top + (result.height / 2)
  1721. };
  1722. }
  1723. else {
  1724. result.offset = polyCoordinates(result, coords.slice());
  1725. }
  1726. result.width = result.height = 0;
  1727. break;
  1728. }
  1729. // Add image position to offset coordinates
  1730. result.offset.left += imageOffset.left;
  1731. result.offset.top += imageOffset.top;
  1732. return result;
  1733. };
  1734. // Tip coordinates calculator
  1735. function calculateTip(corner, width, height)
  1736. {
  1737. var width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2),
  1738. // Define tip coordinates in terms of height and width values
  1739. tips = {
  1740. bottomright: [[0,0], [width,height], [width,0]],
  1741. bottomleft: [[0,0], [width,0], [0,height]],
  1742. topright: [[0,height], [width,0], [width,height]],
  1743. topleft: [[0,0], [0,height], [width,height]],
  1744. topcenter: [[0,height], [width2,0], [width,height]],
  1745. bottomcenter: [[0,0], [width,0], [width2,height]],
  1746. rightcenter: [[0,0], [width,height2], [0,height]],
  1747. leftcenter: [[width,0], [width,height], [0,height2]]
  1748. };
  1749. // Set common side shapes
  1750. tips.lefttop = tips.bottomright; tips.righttop = tips.bottomleft;
  1751. tips.leftbottom = tips.topright; tips.rightbottom = tips.topleft;
  1752. return tips[ corner.string() ];
  1753. }
  1754. function Tip(qTip, command)
  1755. {
  1756. var self = this,
  1757. opts = qTip.options.style.tip,
  1758. elems = qTip.elements,
  1759. tooltip = elems.tooltip,
  1760. cache = {
  1761. top: 0,
  1762. left: 0,
  1763. corner: ''
  1764. },
  1765. size = {
  1766. width: opts.width,
  1767. height: opts.height
  1768. },
  1769. color = { },
  1770. border = opts.border || 0,
  1771. namespace = '.qtip-tip',
  1772. hasCanvas = $('<canvas />')[0].getContext;
  1773. self.corner = NULL;
  1774. self.mimic = NULL;
  1775. self.position = {};
  1776. // Add new option checks for the plugin
  1777. qTip.checks.tip = {
  1778. '^position.my|style.tip.(corner|mimic|border)$': function() {
  1779. // Make sure a tip can be drawn
  1780. if(!self.init()) {
  1781. self.destroy();
  1782. }
  1783. // Reposition the tooltip
  1784. qTip.reposition();
  1785. },
  1786. '^style.tip.(height|width)$': function() {
  1787. // Re-set dimensions and redraw the tip
  1788. size = {
  1789. width: opts.width,
  1790. height: opts.height
  1791. };
  1792. self.create();
  1793. self.update();
  1794. // Reposition the tooltip
  1795. qTip.reposition();
  1796. },
  1797. '^content.title.text|style.(classes|widget)$': function() {
  1798. if(elems.tip) {
  1799. self.update();
  1800. }
  1801. }
  1802. };
  1803. function reposition(event, api, pos, viewport) {
  1804. if(!elems.tip) { return; }
  1805. var newCorner = $.extend({}, self.corner),
  1806. adjust = pos.adjusted,
  1807. method = qTip.options.position.adjust.method.split(' '),
  1808. horizontal = method[0],
  1809. vertical = method[1] || method[0],
  1810. shift = { left: FALSE, top: FALSE, x: 0, y: 0 },
  1811. offset, css = {}, props;
  1812. // Make sure our tip position isn't fixed e.g. doesn't adjust with viewport
  1813. if(self.corner.fixed !== TRUE) {
  1814. // Horizontal - Shift or flip method
  1815. if(horizontal === 'shift' && newCorner.precedance === 'x' && adjust.left && newCorner.y !== 'center') {
  1816. newCorner.precedance = newCorner.precedance === 'x' ? 'y' : 'x';
  1817. }
  1818. else if(horizontal === 'flip' && adjust.left){
  1819. newCorner.x = newCorner.x === 'center' ? (adjust.left > 0 ? 'left' : 'right') : (newCorner.x === 'left' ? 'right' : 'left');
  1820. }
  1821. // Vertical - Shift or flip method
  1822. if(vertical === 'shift' && newCorner.precedance === 'y' && adjust.top && newCorner.x !== 'center') {
  1823. newCorner.precedance = newCorner.precedance === 'y' ? 'x' : 'y';
  1824. }
  1825. else if(vertical === 'flip' && adjust.top) {
  1826. newCorner.y = newCorner.y === 'center' ? (adjust.top > 0 ? 'top' : 'bottom') : (newCorner.y === 'top' ? 'bottom' : 'top');
  1827. }
  1828. // Update and redraw the tip if needed (check cached details of last drawn tip)
  1829. if(newCorner.string() !== cache.corner && (cache.top !== adjust.top || cache.left !== adjust.left)) {
  1830. self.update(newCorner, FALSE);
  1831. }
  1832. }
  1833. // Setup tip offset properties
  1834. offset = self.position(newCorner, adjust);
  1835. if(offset.right !== undefined) { offset.left = -offset.right; }
  1836. if(offset.bottom !== undefined) { offset.top = -offset.bottom; }
  1837. offset.user = Math.max(0, opts.offset);
  1838. // Viewport "shift" specific adjustments
  1839. if(shift.left = (horizontal === 'shift' && !!adjust.left)) {
  1840. if(newCorner.x === 'center') {
  1841. css['margin-left'] = shift.x = offset['margin-left'] - adjust.left;
  1842. }
  1843. else {
  1844. props = offset.right !== undefined ?
  1845. [ adjust.left, -offset.left ] : [ -adjust.left, offset.left ];
  1846. if( (shift.x = Math.max(props[0], props[1])) > props[0] ) {
  1847. pos.left -= adjust.left;
  1848. shift.left = FALSE;
  1849. }
  1850. css[ offset.right !== undefined ? 'right' : 'left' ] = shift.x;
  1851. }
  1852. }
  1853. if(shift.top = (vertical === 'shift' && !!adjust.top)) {
  1854. if(newCorner.y === 'center') {
  1855. css['margin-top'] = shift.y = offset['margin-top'] - adjust.top;
  1856. }
  1857. else {
  1858. props = offset.bottom !== undefined ?
  1859. [ adjust.top, -offset.top ] : [ -adjust.top, offset.top ];
  1860. if( (shift.y = Math.max(props[0], props[1])) > props[0] ) {
  1861. pos.top -= adjust.top;
  1862. shift.top = FALSE;
  1863. }
  1864. css[ offset.bottom !== undefined ? 'bottom' : 'top' ] = shift.y;
  1865. }
  1866. }
  1867. /*
  1868. * If the tip is adjusted in both dimensions, or in a
  1869. * direction that would cause it to be anywhere but the
  1870. * outer border, hide it!
  1871. */
  1872. elems.tip.css(css).toggle(
  1873. !((shift.x && shift.y) || (newCorner.x === 'center' && shift.y) || (newCorner.y === 'center' && shift.x))
  1874. );
  1875. // Adjust position to accomodate tip dimensions
  1876. pos.left -= offset.left.charAt ? offset.user : horizontal !== 'shift' || shift.top || !shift.left && !shift.top ? offset.left : 0;
  1877. pos.top -= offset.top.charAt ? offset.user : vertical !== 'shift' || shift.left || !shift.left && !shift.top ? offset.top : 0;
  1878. // Cache details
  1879. cache.left = adjust.left; cache.top = adjust.top;
  1880. cache.corner = newCorner.string();
  1881. }
  1882. /* border width calculator */
  1883. function borderWidth(corner, side, backup) {
  1884. side = !side ? corner[corner.precedance] : side;
  1885. var isTitleTop = elems.titlebar && corner.y === 'top',
  1886. elem = isTitleTop ? elems.titlebar : elems.content,
  1887. css = 'border-' + side + '-width',
  1888. val = parseInt(elem.css(css), 10);
  1889. return (backup ? val || parseInt(tooltip.css(css), 10) : val) || 0;
  1890. }
  1891. function borderRadius(corner) {
  1892. var isTitleTop = elems.titlebar && corner.y === 'top',
  1893. elem = isTitleTop ? elems.titlebar : elems.content,
  1894. moz = $.browser.mozilla,
  1895. prefix = moz ? '-moz-' : $.browser.webkit ? '-webkit-' : '',
  1896. side = corner.y + (moz ? '' : '-') + corner.x,
  1897. css = prefix + (moz ? 'border-radius-' + side : 'border-' + side + '-radius');
  1898. return parseInt(elem.css(css), 10) || parseInt(tooltip.css(css), 10) || 0;
  1899. }
  1900. function calculateSize(corner) {
  1901. var y = corner.precedance === 'y',
  1902. width = size [ y ? 'width' : 'height' ],
  1903. height = size [ y ? 'height' : 'width' ],
  1904. isCenter = corner.string().indexOf('center') > -1,
  1905. base = width * (isCenter ? 0.5 : 1),
  1906. pow = Math.pow,
  1907. round = Math.round,
  1908. bigHyp, ratio, result,
  1909. smallHyp = Math.sqrt( pow(base, 2) + pow(height, 2) ),
  1910. hyp = [
  1911. (border / base) * smallHyp, (border / height) * smallHyp
  1912. ];
  1913. hyp[2] = Math.sqrt( pow(hyp[0], 2) - pow(border, 2) );
  1914. hyp[3] = Math.sqrt( pow(hyp[1], 2) - pow(border, 2) );
  1915. bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]);
  1916. ratio = bigHyp / smallHyp;
  1917. result = [ round(ratio * height), round(ratio * width) ];
  1918. return { height: result[ y ? 0 : 1 ], width: result[ y ? 1 : 0 ] };
  1919. }
  1920. $.extend(self, {
  1921. init: function()
  1922. {
  1923. var enabled = self.detectCorner() && (hasCanvas || $.browser.msie);
  1924. // Determine tip corner and type
  1925. if(enabled) {
  1926. // Create a new tip and draw it
  1927. self.create();
  1928. self.update();
  1929. // Bind update events
  1930. tooltip.unbind(namespace).bind('tooltipmove'+namespace, reposition);
  1931. }
  1932. return enabled;
  1933. },
  1934. detectCorner: function()
  1935. {
  1936. var corner = opts.corner,
  1937. posOptions = qTip.options.position,
  1938. at = posOptions.at,
  1939. my = posOptions.my.string ? posOptions.my.string() : posOptions.my;
  1940. // Detect corner and mimic properties
  1941. if(corner === FALSE || (my === FALSE && at === FALSE)) {
  1942. return FALSE;
  1943. }
  1944. else {
  1945. if(corner === TRUE) {
  1946. self.corner = new PLUGINS.Corner(my);
  1947. }
  1948. else if(!corner.string) {
  1949. self.corner = new PLUGINS.Corner(corner);
  1950. self.corner.fixed = TRUE;
  1951. }
  1952. }
  1953. return self.corner.string() !== 'centercenter';
  1954. },
  1955. detectColours: function() {
  1956. var i, fill, border,
  1957. tip = elems.tip.css({ backgroundColor: '', border: '' }),
  1958. corner = self.corner,
  1959. precedance = corner[ corner.precedance ],
  1960. borderSide = 'border-' + precedance + '-color',
  1961. borderSideCamel = 'border' + precedance.charAt(0) + precedance.substr(1) + 'Color',
  1962. invalid = /rgba?\(0, 0, 0(, 0)?\)|transparent/i,
  1963. backgroundColor = 'background-color',
  1964. transparent = 'transparent',
  1965. fluid = 'ui-tooltip-fluid',
  1966. bodyBorder = $(document.body).css('color'),
  1967. contentColour = qTip.elements.content.css('color'),
  1968. useTitle = elems.titlebar && (corner.y === 'top' || (corner.y === 'center' && tip.position().top + (size.height / 2) + opts.offset < elems.titlebar.outerHeight(1))),
  1969. colorElem = useTitle ? elems.titlebar : elems.content;
  1970. // Apply the fluid class so we can see our CSS values properly
  1971. tooltip.addClass(fluid);
  1972. // Detect tip colours from CSS styles
  1973. fill = tip.css(backgroundColor) || transparent;
  1974. border = tip[0].style[ borderSideCamel ];
  1975. // Make sure colours are valid
  1976. if(!fill || invalid.test(fill)) {
  1977. color.fill = colorElem.css(backgroundColor);
  1978. if(invalid.test(color.fill)) {
  1979. color.fill = tooltip.css(backgroundColor) || fill;
  1980. }
  1981. }
  1982. if(!border || invalid.test(border)) {
  1983. color.border = tooltip.css(borderSide);
  1984. if(invalid.test(color.border) || color.border === bodyBorder) {
  1985. color.border = colorElem.css(borderSide);
  1986. if(color.border === contentColour) { color.border = border; }
  1987. }
  1988. }
  1989. // Reset background and border colours, and remove fluid class
  1990. $('*', tip).add(tip).css(backgroundColor, transparent).css('border', '');
  1991. tooltip.removeClass(fluid);
  1992. },
  1993. create: function()
  1994. {
  1995. var width = size.width,
  1996. height = size.height,
  1997. vml;
  1998. // Remove previous tip element if present
  1999. if(elems.tip) { elems.tip.remove(); }
  2000. // Create tip element and prepend to the tooltip
  2001. elems.tip = $('<div />', { 'class': 'ui-tooltip-tip' }).css({ width: width, height: height }).prependTo(tooltip);
  2002. // Create tip drawing element(s)
  2003. if(hasCanvas) {
  2004. // save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()!
  2005. $('<canvas />').appendTo(elems.tip)[0].getContext('2d').save();
  2006. }
  2007. else {
  2008. vml = '<vml:shape coordorigin="0,0" style="display:inline-block; position:absolute; behavior:url(#default#VML);"></vml:shape>';
  2009. elems.tip.html( border ? vml += vml : vml );
  2010. }
  2011. },
  2012. update: function(corner, position)
  2013. {
  2014. var tip = elems.tip,
  2015. inner = tip.children(),
  2016. width = size.width,
  2017. height = size.height,
  2018. regular = 'px solid ',
  2019. transparent = 'px dashed transparent', // Dashed IE6 border-transparency hack. Awesome!
  2020. mimic = opts.mimic,
  2021. round = Math.round,
  2022. precedance, context, coords, translate, newSize;
  2023. // Re-determine tip if not already set
  2024. if(!corner) { corner = self.corner; }
  2025. // Use corner property if we detect an invalid mimic value
  2026. if(mimic === FALSE) { mimic = corner; }
  2027. // Otherwise inherit mimic properties from the corner object as necessary
  2028. else {
  2029. mimic = new PLUGINS.Corner(mimic);
  2030. mimic.precedance = corner.precedance;
  2031. if(mimic.x === 'inherit') { mimic.x = corner.x; }
  2032. else if(mimic.y === 'inherit') { mimic.y = corner.y; }
  2033. else if(mimic.x === mimic.y) {
  2034. mimic[ corner.precedance ] = corner[ corner.precedance ];
  2035. }
  2036. }
  2037. precedance = mimic.precedance;
  2038. // Update our colours
  2039. self.detectColours();
  2040. // Detect border width, taking into account colours
  2041. border = color.border === 'transparent' || color.border === '#123456' ? 0 :
  2042. opts.border === TRUE ? borderWidth(corner, NULL, TRUE) : opts.border;
  2043. // Calculate coordinates
  2044. coords = calculateTip(mimic, width , height);
  2045. // Determine tip size
  2046. newSize = calculateSize(corner);
  2047. tip.css(newSize);
  2048. // Calculate tip translation
  2049. if(corner.precedance === 'y') {
  2050. translate = [
  2051. round(mimic.x === 'left' ? border : mimic.x === 'right' ? newSize.width - width - border : (newSize.width - width) / 2),
  2052. round(mimic.y === 'top' ? newSize.height - height : 0)
  2053. ];
  2054. }
  2055. else {
  2056. translate = [
  2057. round(mimic.x === 'left' ? newSize.width - width : 0),
  2058. round(mimic.y === 'top' ? border : mimic.y === 'bottom' ? newSize.height - height - border : (newSize.height - height) / 2)
  2059. ];
  2060. }
  2061. // Canvas drawing implementation
  2062. if(hasCanvas) {
  2063. // Set the canvas size using calculated size
  2064. inner.attr(newSize);
  2065. // Grab canvas context and clear/save it
  2066. context = inner[0].getContext('2d');
  2067. context.restore(); context.save();
  2068. context.clearRect(0,0,3000,3000);
  2069. // Translate origin
  2070. context.translate(translate[0], translate[1]);
  2071. // Draw the tip
  2072. context.beginPath();
  2073. context.moveTo(coords[0][0], coords[0][1]);
  2074. context.lineTo(coords[1][0], coords[1][1]);
  2075. context.lineTo(coords[2][0], coords[2][1]);
  2076. context.closePath();
  2077. context.fillStyle = color.fill;
  2078. context.strokeStyle = color.border;
  2079. context.lineWidth = border * 2;
  2080. context.lineJoin = 'miter';
  2081. context.miterLimit = 100;
  2082. context.stroke();
  2083. context.fill();
  2084. }
  2085. // VML (IE Proprietary implementation)
  2086. else {
  2087. // Setup coordinates string
  2088. coords = 'm' + coords[0][0] + ',' + coords[0][1] + ' l' + coords[1][0] +
  2089. ',' + coords[1][1] + ' ' + coords[2][0] + ',' + coords[2][1] + ' xe';
  2090. // Setup VML-specific offset for pixel-perfection
  2091. translate[2] = border && /^(r|b)/i.test(corner.string()) ?
  2092. parseFloat($.browser.version, 10) === 8 ? 2 : 1 : 0;
  2093. // Set initial CSS
  2094. inner.css({
  2095. antialias: ''+(mimic.string().indexOf('center') > -1),
  2096. left: translate[0] - (translate[2] * Number(precedance === 'x')),
  2097. top: translate[1] - (translate[2] * Number(precedance === 'y')),
  2098. width: width + border,
  2099. height: height + border
  2100. })
  2101. .each(function(i) {
  2102. var $this = $(this);
  2103. // Set shape specific attributes
  2104. $this.attr({
  2105. coordsize: (width+border) + ' ' + (height+border),
  2106. path: coords,
  2107. fillcolor: color.fill,
  2108. filled: !!i,
  2109. stroked: !!!i
  2110. })
  2111. .css({ display: border || i ? 'block' : 'none' });
  2112. // Check if border is enabled and add stroke element
  2113. if(!i && border > 0 && $this.html() === '') {
  2114. $this.html(
  2115. '<vml:stroke weight="'+(border*2)+'px" color="'+color.border+'" miterlimit="1000" joinstyle="miter" ' +
  2116. ' style="behavior:url(#default#VML); display:inline-block;" />'
  2117. );
  2118. }
  2119. });
  2120. }
  2121. // Position if needed
  2122. if(position !== FALSE) { self.position(corner); }
  2123. },
  2124. // Tip positioning method
  2125. position: function(corner)
  2126. {
  2127. var tip = elems.tip,
  2128. position = {},
  2129. userOffset = Math.max(0, opts.offset),
  2130. precedance, dimensions, corners;
  2131. // Return if tips are disabled or tip is not yet rendered
  2132. if(opts.corner === FALSE || !tip) { return FALSE; }
  2133. // Inherit corner if not provided
  2134. corner = corner || self.corner;
  2135. precedance = corner.precedance;
  2136. // Determine which tip dimension to use for adjustment
  2137. dimensions = calculateSize(corner);
  2138. // Setup corners and offset array
  2139. corners = [ corner.x, corner.y ];
  2140. if(precedance === 'x') { corners.reverse(); }
  2141. // Calculate tip position
  2142. $.each(corners, function(i, side) {
  2143. var b, br;
  2144. if(side === 'center') {
  2145. b = precedance === 'y' ? 'left' : 'top';
  2146. position[ b ] = '50%';
  2147. position['margin-' + b] = -Math.round(dimensions[ precedance === 'y' ? 'width' : 'height' ] / 2) + userOffset;
  2148. }
  2149. else {
  2150. b = borderWidth(corner, side, TRUE);
  2151. br = borderRadius(corner);
  2152. position[ side ] = i ?
  2153. borderWidth(corner, side) :
  2154. userOffset + (br > b ? br : 0);
  2155. }
  2156. });
  2157. // Adjust for tip dimensions
  2158. position[ corner[precedance] ] -= dimensions[ precedance === 'x' ? 'width' : 'height' ];
  2159. // Set and return new position
  2160. tip.css({ top: '', bottom: '', left: '', right: '', margin: '' }).css(position);
  2161. return position;
  2162. },
  2163. destroy: function()
  2164. {
  2165. // Remov tip and bound events
  2166. if(elems.tip) { elems.tip.remove(); }
  2167. tooltip.unbind(namespace);
  2168. }
  2169. });
  2170. self.init();
  2171. }
  2172. PLUGINS.tip = function(api)
  2173. {
  2174. var self = api.plugins.tip;
  2175. return 'object' === typeof self ? self : (api.plugins.tip = new Tip(api));
  2176. };
  2177. // Initialize tip on render
  2178. PLUGINS.tip.initialize = 'render';
  2179. // Setup plugin sanitization options
  2180. PLUGINS.tip.sanitize = function(options)
  2181. {
  2182. var style = options.style, opts;
  2183. if(style && 'tip' in style) {
  2184. opts = options.style.tip;
  2185. if(typeof opts !== 'object'){ options.style.tip = { corner: opts }; }
  2186. if(!(/string|boolean/i).test(typeof opts.corner)) { opts.corner = TRUE; }
  2187. if(typeof opts.width !== 'number'){ delete opts.width; }
  2188. if(typeof opts.height !== 'number'){ delete opts.height; }
  2189. if(typeof opts.border !== 'number' && opts.border !== TRUE){ delete opts.border; }
  2190. if(typeof opts.offset !== 'number'){ delete opts.offset; }
  2191. }
  2192. };
  2193. // Extend original qTip defaults
  2194. $.extend(TRUE, QTIP.defaults, {
  2195. style: {
  2196. tip: {
  2197. corner: TRUE,
  2198. mimic: FALSE,
  2199. width: 6,
  2200. height: 6,
  2201. border: TRUE,
  2202. offset: 0
  2203. }
  2204. }
  2205. });PLUGINS.svg = function(svg, corner)
  2206. {
  2207. var doc = $(document),
  2208. elem = svg[0],
  2209. result = {
  2210. width: 0, height: 0,
  2211. offset: { top: 1e10, left: 1e10 }
  2212. },
  2213. box, mtx, root, point, tPoint;
  2214. if (elem.getBBox && elem.parentNode) {
  2215. box = elem.getBBox();
  2216. mtx = elem.getScreenCTM();
  2217. root = elem.farthestViewportElement || elem;
  2218. // Return if no method is found
  2219. if(!root.createSVGPoint) { return result; }
  2220. // Create our point var
  2221. point = root.createSVGPoint();
  2222. // Adjust top and left
  2223. point.x = box.x;
  2224. point.y = box.y;
  2225. tPoint = point.matrixTransform(mtx);
  2226. result.offset.left = tPoint.x;
  2227. result.offset.top = tPoint.y;
  2228. // Adjust width and height
  2229. point.x += box.width;
  2230. point.y += box.height;
  2231. tPoint = point.matrixTransform(mtx);
  2232. result.width = tPoint.x - result.offset.left;
  2233. result.height = tPoint.y - result.offset.top;
  2234. // Adjust by scroll offset
  2235. result.offset.left += doc.scrollLeft();
  2236. result.offset.top += doc.scrollTop();
  2237. }
  2238. return result;
  2239. };
  2240. function Modal(api)
  2241. {
  2242. var self = this,
  2243. options = api.options.show.modal,
  2244. elems = api.elements,
  2245. tooltip = elems.tooltip,
  2246. selector = '#qtip-overlay',
  2247. globalNamespace = '.qtipmodal',
  2248. namespace = globalNamespace + api.id,
  2249. attr = 'is-modal-qtip',
  2250. overlay;
  2251. // Setup option set checks
  2252. api.checks.modal = {
  2253. '^show.modal.(on|blur)$': function() {
  2254. // Initialise
  2255. self.init();
  2256. // Show the modal if not visible already and tooltip is visible
  2257. elems.overlay.toggle( tooltip.is(':visible') );
  2258. }
  2259. };
  2260. $.extend(self, {
  2261. init: function()
  2262. {
  2263. // If modal is disabled... return
  2264. if(!options.on) { return self; }
  2265. // Create the overlay if needed
  2266. overlay = self.create();
  2267. // Add unique attribute so we can grab modal tooltips easily via a selector
  2268. tooltip.attr(attr, TRUE)
  2269. // Remove previous bound events in globalNamespace
  2270. .unbind(globalNamespace).unbind(namespace)
  2271. // Apply our show/hide/focus modal events
  2272. .bind('tooltipshow'+globalNamespace+' tooltiphide'+globalNamespace, function(event, api, duration) {
  2273. self[ event.type.replace('tooltip', '') ](event, duration);
  2274. })
  2275. // Adjust modal z-index on tooltip focus
  2276. .bind('tooltipfocus'+globalNamespace, function(event, api, zIndex) {
  2277. overlay[0].style.zIndex = zIndex - 1;
  2278. })
  2279. // Focus any other visible modals when this one blurs
  2280. .bind('tooltipblur'+globalNamespace, function(event) {
  2281. $('[' + attr + ']:visible').not(tooltip).last().qtip('focus', event);
  2282. });
  2283. // Apply keyboard "Escape key" close handler
  2284. if(options.escape) {
  2285. $(window).unbind(namespace).bind('keydown'+namespace, function(event) {
  2286. if(event.keyCode === 27 && tooltip.hasClass(focusClass)) {
  2287. api.hide(event);
  2288. }
  2289. });
  2290. }
  2291. // Apply click handler for blur option
  2292. if(options.blur) {
  2293. elems.overlay.unbind(namespace).bind('click'+namespace, function(event) {
  2294. if(tooltip.hasClass(focusClass)) { api.hide(event); }
  2295. });
  2296. }
  2297. return self;
  2298. },
  2299. create: function()
  2300. {
  2301. var elem = $(selector);
  2302. // Return if overlay is already rendered
  2303. if(elem.length) { elems.overlay = elem; return elem; }
  2304. // Create document overlay
  2305. overlay = elems.overlay = $('<div />', {
  2306. id: selector.substr(1),
  2307. css: {
  2308. position: 'absolute',
  2309. top: 0,
  2310. left: 0,
  2311. display: 'none'
  2312. },
  2313. mousedown: function() { return FALSE; }
  2314. })
  2315. .appendTo(document.body);
  2316. // Update position on window resize or scroll
  2317. $(window).unbind(globalNamespace).bind('resize'+globalNamespace, function() {
  2318. overlay.css({
  2319. height: Math.max( $(window).height(), $(document).height() ),
  2320. width: Math.max( $(window).width(), $(document).width() )
  2321. });
  2322. })
  2323. .trigger('resize');
  2324. return overlay;
  2325. },
  2326. toggle: function(event, state, duration)
  2327. {
  2328. // Make sure default event hasn't been prevented
  2329. if(event && event.isDefaultPrevented()) { return self; }
  2330. var effect = options.effect,
  2331. type = state ? 'show': 'hide',
  2332. modals = $('[' + attr + ']:visible').not(tooltip),
  2333. zindex;
  2334. // Create our overlay if it isn't present already
  2335. if(!overlay) { overlay = self.create(); }
  2336. // Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible
  2337. if((overlay.is(':animated') && !state) || (!state && modals.length)) { return self; }
  2338. // Toggle backdrop cursor style on show
  2339. if(state) {
  2340. elems.overlay.css('cursor', options.blur ? 'pointer' : '');
  2341. }
  2342. // Setop all animations
  2343. overlay.stop(TRUE, FALSE);
  2344. // Use custom function if provided
  2345. if($.isFunction(effect)) {
  2346. effect.call(overlay, state);
  2347. }
  2348. // If no effect type is supplied, use a simple toggle
  2349. else if(effect === FALSE) {
  2350. overlay[ type ]();
  2351. }
  2352. // Use basic fade function
  2353. else {
  2354. overlay.fadeTo( parseInt(duration, 10) || 90, state ? 0.7 : 0, function() {
  2355. if(!state) { $(this).hide(); }
  2356. });
  2357. }
  2358. return self;
  2359. },
  2360. show: function(event, duration) { return self.toggle(event, TRUE, duration); },
  2361. hide: function(event, duration) { return self.toggle(event, FALSE, duration); },
  2362. destroy: function()
  2363. {
  2364. var delBlanket = overlay;
  2365. if(delBlanket) {
  2366. // Check if any other modal tooltips are present
  2367. delBlanket = $('[' + attr + ']').not(tooltip).length < 1;
  2368. // Remove overlay if needed
  2369. if(delBlanket) {
  2370. elems.overlay.remove();
  2371. $(window).unbind(globalNamespace);
  2372. }
  2373. else {
  2374. elems.overlay.unbind(globalNamespace+api.id);
  2375. }
  2376. }
  2377. // Remove bound events
  2378. return tooltip.removeAttr(attr).unbind(globalNamespace);
  2379. }
  2380. });
  2381. self.init();
  2382. }
  2383. PLUGINS.modal = function(api)
  2384. {
  2385. var self = api.plugins.modal;
  2386. return 'object' === typeof self ? self : (api.plugins.modal = new Modal(api));
  2387. };
  2388. // Plugin needs to be initialized on render
  2389. PLUGINS.modal.initialize = 'render';
  2390. // Setup sanitiztion rules
  2391. PLUGINS.modal.sanitize = function(opts) {
  2392. if(opts.show) {
  2393. if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; }
  2394. else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; }
  2395. }
  2396. };
  2397. // Extend original api defaults
  2398. $.extend(TRUE, QTIP.defaults, {
  2399. show: {
  2400. modal: {
  2401. on: FALSE,
  2402. effect: TRUE,
  2403. blur: TRUE,
  2404. escape: TRUE
  2405. }
  2406. }
  2407. });/*
  2408. * BGIFrame adaption (http://plugins.jquery.com/project/bgiframe)
  2409. * Special thanks to Brandon Aaron
  2410. */
  2411. function BGIFrame(api)
  2412. {
  2413. var self = this,
  2414. elems = api.elements,
  2415. tooltip = elems.tooltip,
  2416. namespace = '.bgiframe-' + api.id;
  2417. $.extend(self, {
  2418. init: function()
  2419. {
  2420. // Create the BGIFrame element
  2421. elems.bgiframe = $('<iframe class="ui-tooltip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" ' +
  2422. ' style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); ' +
  2423. '-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>');
  2424. // Append the new element to the tooltip
  2425. elems.bgiframe.appendTo(tooltip);
  2426. // Update BGIFrame on tooltip move
  2427. tooltip.bind('tooltipmove'+namespace, self.adjust);
  2428. },
  2429. adjust: function()
  2430. {
  2431. var dimensions = api.get('dimensions'), // Determine current tooltip dimensions
  2432. plugin = api.plugins.tip,
  2433. tip = elems.tip,
  2434. tipAdjust, offset;
  2435. // Adjust border offset
  2436. offset = parseInt(tooltip.css('border-left-width'), 10) || 0;
  2437. offset = { left: -offset, top: -offset };
  2438. // Adjust for tips plugin
  2439. if(plugin && tip) {
  2440. tipAdjust = (plugin.corner.precedance === 'x') ? ['width', 'left'] : ['height', 'top'];
  2441. offset[ tipAdjust[1] ] -= tip[ tipAdjust[0] ]();
  2442. }
  2443. // Update bgiframe
  2444. elems.bgiframe.css(offset).css(dimensions);
  2445. },
  2446. destroy: function()
  2447. {
  2448. // Remove iframe
  2449. elems.bgiframe.remove();
  2450. // Remove bound events
  2451. tooltip.unbind(namespace);
  2452. }
  2453. });
  2454. self.init();
  2455. }
  2456. PLUGINS.bgiframe = function(api)
  2457. {
  2458. var browser = $.browser,
  2459. self = api.plugins.bgiframe;
  2460. // Proceed only if the browser is IE6 and offending elements are present
  2461. if($('select, object').length < 1 || !(browser.msie && browser.version.charAt(0) === '6')) {
  2462. return FALSE;
  2463. }
  2464. return 'object' === typeof self ? self : (api.plugins.bgiframe = new BGIFrame(api));
  2465. };
  2466. // Plugin needs to be initialized on render
  2467. PLUGINS.bgiframe.initialize = 'render';
  2468. }(jQuery, window));