jquery.qtip.js 88 KB

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