1 2/*! 3 * jQuery UI 1.8.7 4 * 5 * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 6 * Dual licensed under the MIT or GPL Version 2 licenses. 7 * http://jquery.org/license 8 * 9 * http://docs.jquery.com/UI 10 */ 11(function(c,j){function k(a){return!c(a).parents().andSelf().filter(function(){return c.curCSS(this,"visibility")==="hidden"||c.expr.filters.hidden(this)}).length}c.ui=c.ui||{};if(!c.ui.version){c.extend(c.ui,{version:"1.8.7",keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106, 12NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91}});c.fn.extend({_focus:c.fn.focus,focus:function(a,b){return typeof a==="number"?this.each(function(){var d=this;setTimeout(function(){c(d).focus();b&&b.call(d)},a)}):this._focus.apply(this,arguments)},scrollParent:function(){var a;a=c.browser.msie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(c.curCSS(this, 13"position",1))&&/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(c.curCSS(this,"overflow",1)+c.curCSS(this,"overflow-y",1)+c.curCSS(this,"overflow-x",1))}).eq(0);return/fixed/.test(this.css("position"))||!a.length?c(document):a},zIndex:function(a){if(a!==j)return this.css("zIndex",a);if(this.length){a=c(this[0]);for(var b;a.length&&a[0]!==document;){b=a.css("position"); 14if(b==="absolute"||b==="relative"||b==="fixed"){b=parseInt(a.css("zIndex"),10);if(!isNaN(b)&&b!==0)return b}a=a.parent()}}return 0},disableSelection:function(){return this.bind((c.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(a){a.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}});c.each(["Width","Height"],function(a,b){function d(f,g,l,m){c.each(e,function(){g-=parseFloat(c.curCSS(f,"padding"+this,true))||0;if(l)g-=parseFloat(c.curCSS(f, 15"border"+this+"Width",true))||0;if(m)g-=parseFloat(c.curCSS(f,"margin"+this,true))||0});return g}var e=b==="Width"?["Left","Right"]:["Top","Bottom"],h=b.toLowerCase(),i={innerWidth:c.fn.innerWidth,innerHeight:c.fn.innerHeight,outerWidth:c.fn.outerWidth,outerHeight:c.fn.outerHeight};c.fn["inner"+b]=function(f){if(f===j)return i["inner"+b].call(this);return this.each(function(){c(this).css(h,d(this,f)+"px")})};c.fn["outer"+b]=function(f,g){if(typeof f!=="number")return i["outer"+b].call(this,f);return this.each(function(){c(this).css(h, 16d(this,f,true,g)+"px")})}});c.extend(c.expr[":"],{data:function(a,b,d){return!!c.data(a,d[3])},focusable:function(a){var b=a.nodeName.toLowerCase(),d=c.attr(a,"tabindex");if("area"===b){b=a.parentNode;d=b.name;if(!a.href||!d||b.nodeName.toLowerCase()!=="map")return false;a=c("img[usemap=#"+d+"]")[0];return!!a&&k(a)}return(/input|select|textarea|button|object/.test(b)?!a.disabled:"a"==b?a.href||!isNaN(d):!isNaN(d))&&k(a)},tabbable:function(a){var b=c.attr(a,"tabindex");return(isNaN(b)||b>=0)&&c(a).is(":focusable")}}); 17c(function(){var a=document.body,b=a.appendChild(b=document.createElement("div"));c.extend(b.style,{minHeight:"100px",height:"auto",padding:0,borderWidth:0});c.support.minHeight=b.offsetHeight===100;c.support.selectstart="onselectstart"in b;a.removeChild(b).style.display="none"});c.extend(c.ui,{plugin:{add:function(a,b,d){a=c.ui[a].prototype;for(var e in d){a.plugins[e]=a.plugins[e]||[];a.plugins[e].push([b,d[e]])}},call:function(a,b,d){if((b=a.plugins[b])&&a.element[0].parentNode)for(var e=0;e<b.length;e++)a.options[b[e][0]]&& 18b[e][1].apply(a.element,d)}},contains:function(a,b){return document.compareDocumentPosition?a.compareDocumentPosition(b)&16:a!==b&&a.contains(b)},hasScroll:function(a,b){if(c(a).css("overflow")==="hidden")return false;b=b&&b==="left"?"scrollLeft":"scrollTop";var d=false;if(a[b]>0)return true;a[b]=1;d=a[b]>0;a[b]=0;return d},isOverAxis:function(a,b,d){return a>b&&a<b+d},isOver:function(a,b,d,e,h,i){return c.ui.isOverAxis(a,d,h)&&c.ui.isOverAxis(b,e,i)}})}})(jQuery); 19; 20 21/*! 22 * jQuery UI Widget 1.8.7 23 * 24 * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 25 * Dual licensed under the MIT or GPL Version 2 licenses. 26 * http://jquery.org/license 27 * 28 * http://docs.jquery.com/UI/Widget 29 */ 30(function(b,j){if(b.cleanData){var k=b.cleanData;b.cleanData=function(a){for(var c=0,d;(d=a[c])!=null;c++)b(d).triggerHandler("remove");k(a)}}else{var l=b.fn.remove;b.fn.remove=function(a,c){return this.each(function(){if(!c)if(!a||b.filter(a,[this]).length)b("*",this).add([this]).each(function(){b(this).triggerHandler("remove")});
30return l.call(b(this),a,c)})}}b.widget=function(a,c,d){var e=a.split(".")[0],f;a=a.split(".")[1];f=e+"-"+a;if(!d){d=c;c=b.Widget}b.expr[":"][f]=function(h){return!!b.data(h, 31a)};b[e]=b[e]||{};b[e][a]=function(h,g){arguments.length&&this._createWidget(h,g)};c=new c;c.options=b.extend(true,{},c.options);b[e][a].prototype=b.extend(true,c,{namespace:e,widgetName:a,widgetEventPrefix:b[e][a].prototype.widgetEventPrefix||a,widgetBaseClass:f},d);b.widget.bridge(a,b[e][a])};b.widget.bridge=function(a,c){b.fn[a]=function(d){var e=typeof d==="string",f=Array.prototype.slice.call(arguments,1),h=this;d=!e&&f.length?b.extend.apply(null,[true,d].concat(f)):d;if(e&&d.charAt(0)==="_")return h; 32e?this.each(function(){var g=b.data(this,a),i=g&&b.isFunction(g[d])?g[d].apply(g,f):g;if(i!==g&&i!==j){h=i;return false}}):this.each(function(){var g=b.data(this,a);g?g.option(d||{})._init():b.data(this,a,new c(d,this))});return h}};b.Widget=function(a,c){arguments.length&&this._createWidget(a,c)};b.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",options:{disabled:false},_createWidget:function(a,c){b.data(c,this.widgetName,this);this.element=b(c);this.options=b.extend(true,{},this.options, 33this._getCreateOptions(),a);var d=this;this.element.bind("remove."+this.widgetName,function(){d.destroy()});this._create();this._trigger("create");this._init()},_getCreateOptions:function(){return b.metadata&&b.metadata.get(this.element[0])[this.widgetName]},_create:function(){},_init:function(){},destroy:function(){this.element.unbind("."+this.widgetName).removeData(this.widgetName);this.widget().unbind("."+this.widgetName).removeAttr("aria-disabled").removeClass(this.widgetBaseClass+"-disabled ui-state-disabled")}, 34widget:function(){return this.element},option:function(a,c){var d=a;if(arguments.length===0)return b.extend({},this.options);if(typeof a==="string"){if(c===j)return this.options[a];d={};d[a]=c}this._setOptions(d);return this},_setOptions:function(a){var c=this;b.each(a,function(d,e){c._setOption(d,e)});return this},_setOption:function(a,c){this.options[a]=c;if(a==="disabled")this.widget()[c?"addClass":"removeClass"](this.widgetBaseClass+"-disabled ui-state-disabled").attr("aria-disabled",c);return this}, 35enable:function(){return this._setOption("disabled",false)},disable:function(){return this._setOption("disabled",true)},_trigger:function(a,c,d){var e=this.options[a];c=b.Event(c);c.type=(a===this.widgetEventPrefix?a:this.widgetEventPrefix+a).toLowerCase();d=d||{};if(c.originalEvent){a=b.event.props.length;for(var f;a;){f=b.event.props[--a];c[f]=c.originalEvent[f]}}this.element.trigger(c,d);return!(b.isFunction(e)&&e.call(this.element[0],c,d)===false||c.isDefaultPrevented())}}})(jQuery); 36; 37/* 38* qTip2 - Pretty powerful tooltips 39* http://craigsworks.com/projects/qtip2/ 40* 41* Version: 2.0.0pre 42* Copyright 2009-2010 Craig Michael Thompson - http://craigsworks.com 43* 44* Dual licensed under MIT or GPLv2 licenses 45* http://en.wikipedia.org/wiki/MIT_License 46* http://en.wikipedia.org/wiki/GNU_General_Public_License 47* 48* Date: Fri Aug 19 08:14:39 2011 +0100 49*/ 50 51/*jslint browser: true, onevar: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: true */ 52/*global window: false, jQuery: false, console: false */ 53 54 55(function($, window, undefined) { 56 57 "use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/ 58 59 // Munge the primitives - Paul Irish tip 60 var TRUE = true, 61 FALSE = false, 62 NULL = null, 63 64 // Shortcut vars 65 QTIP, PLUGINS, MOUSE, 66 usedIDs = {}, 67 uitooltip = 'ui-tooltip', 68 widget = 'ui-widget', 69 disabled = 'ui-state-disabled', 70 selector = 'div.qtip.'+uitooltip, 71 defaultClass = uitooltip + '-default', 72 focusClass = uitooltip + '-focus', 73 hoverClass = uitooltip + '-hover', 74 fluidClass = uitooltip + '-fluid', 75 hideOffset = '-31000px', 76 replaceSuffix = '_replacedByqTip', 77 oldtitle = 'oldtitle', 78 trackingBound; 79 80 /* Thanks to Paul Irish for this one: http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/ */ 81 function log() { 82 log.history = log.history || []; 83 log.history.push(arguments); 84 85 // Make sure console is present 86 if('object' === typeof console) { 87 88 // Setup console and arguments 89 var c = console[ console.warn ? 'warn' : 'log' ], 90 args = Array.prototype.slice.call(arguments), a; 91 92 // Add qTip2 marker to first argument if it's a string 93 if(typeof arguments[0] === 'string') { args[0] = 'qTip2: ' + args[0]; } 94 95 // Apply console.warn or .log if not supported 96 a = c.apply ? c.apply(console, args) : c(args); 97 } 98 } 99 100// Option object sanitizer 101function sanitizeOptions(opts) 102{ 103 var content; 104
105 if(!opts || 'object' !== typeof opts) { return FALSE; } 106 107 if('object' !== typeof opts.metadata) { 108 opts.metadata = { 109 type: opts.metadata 110 }; 111 } 112 113 if('content' in opts) { 114 if('object' !== typeof opts.content || opts.content.jquery) { 115 opts.content = { 116 text: opts.content 117 }; 118 } 119 120 content = opts.content.text || FALSE; 121 if(!$.isFunction(content) && ((!content && !content.attr) || content.length < 1 || ('object' === typeof content && !content.jquery))) { 122 opts.content.text = FALSE; 123 } 124 125 if('title' in opts.content) { 126 if('object' !== typeof opts.content.title) { 127 opts.content.title = { 128 text: opts.content.title 129 }; 130 } 131 132 content = opts.content.title.text || FALSE; 133 if(!$.isFunction(content) && ((!content && !content.attr) || content.length < 1 || ('object' === typeof content && !content.jquery))) { 134 opts.content.title.text = FALSE; 135 } 136 } 137 } 138 139 if('position' in opts) { 140 if('object' !== typeof opts.position) { 141 opts.position = { 142 my: opts.position, 143 at: opts.position 144 }; 145 } 146 } 147 148 if('show' in opts) { 149 if('object' !== typeof opts.show) { 150 if(opts.show.jquery) { 151 opts.show = { target: opts.show }; 152 } 153 else { 154 opts.show = { event: opts.show }; 155 } 156 } 157 } 158 159 if('hide' in opts) { 160 if('object' !== typeof opts.hide) { 161 if(opts.hide.jquery) { 162 opts.hide = { target: opts.hide }; 163 } 164 else { 165 opts.hide = { event: opts.hide }; 166 } 167 } 168 } 169 170 if('style' in opts) { 171 if('object' !== typeof opts.style) { 172 opts.style = { 173 classes: opts.style 174 }; 175 } 176 } 177 178 // Sanitize plugin options 179 $.each(PLUGINS, function() { 180 if(this.sanitize) { this.sanitize(opts); } 181 }); 182 183 return opts; 184} 185 186/* 187* Core plugin implementation 188*/ 189function QTip(target, options, id, attr) 190{ 191 // Declare this reference 192 var self = this, 193 docBody = document.body, 194 tooltipID = uitooltip + '-' + id, 195 isPositioning = 0, 196 isDrawing = 0, 197 tooltip = $(), 198 namespace = '.qtip-' + id, 199 elements, cache; 200 201 // Setup class attributes 202 self.id = id; 203 self.rendered = FALSE; 204 self.elements = elements = { target: target }; 205 self.timers = { img: {} }; 206 self.options = options; 207 self.checks = {}; 208 self.plugins = {}; 209 self.cache = cache = { 210 event: {}, 211 target: $(), 212 disabled: FALSE, 213 attr: attr 214 }; 215 216 /* 217 * Private core functions 218 */ 219 function convertNotation(notation) 220 { 221 var i = 0, obj, option = options, 222 223 // Split notation into array 224 levels = notation.split('.'); 225 226 // Loop through 227 while( option = option[ levels[i++] ] ) { 228 if(i < levels.length) { obj = option; } 229 } 230 231 return [obj || options, levels.pop()]; 232 } 233 234 function setWidget() { 235 var on = options.style.widget; 236 237 tooltip.toggleClass(widget, on).toggleClass(defaultClass, !on); 238 elements.content.toggleClass(widget+'-content', on); 239 240 if(elements.titlebar){ 241 elements.titlebar.toggleClass(widget+'-header', on); 242 } 243 if(elements.button){ 244 elements.button.toggleClass(uitooltip+'-icon', !on); 245 } 246 } 247 248 function removeTitle(reposition) 249 { 250 if(elements.title) { 251 elements.titlebar.remove(); 252 elements.titlebar = elements.title = elements.button = NULL; 253 254 // Reposition if enabled 255 if(reposition !== FALSE) { self.reposition(); } 256 } 257 } 258 259 function createButton() 260 { 261 var button = options.content.title.button, 262 isString = typeof button === 'string', 263 close = isString ? button : 'Close tooltip'; 264 265 if(elements.button) { elements.button.remove(); } 266 267 // Use custom button if one was supplied by user, else use default 268 if(button.jquery) { 269 elements.button = button; 270 } 271 else { 272 elements.button = $('<a />', { 273 'class': 'ui-state-default ' + (options.style.widget ? '' : uitooltip+'-icon'), 274 'title': close, 275 'aria-label': close 276 }) 277 .prepend( 278 $('<span />', { 279 'class': 'ui-icon ui-icon-close', 280 'html': '×' 281 }) 282 ); 283 } 284 285 // Create button and setup attributes 286 elements.button.appendTo(elements.titlebar) 287 .attr('role', 'button') 288 .hover(function(event){ $(this).toggleClass('ui-state-hover', event.type === 'mouseenter'); }) 289 .click(function(event) { 290 if(!tooltip.hasClass(disabled)) { self.hide(event); } 291 return FALSE; 292 }) 293 .bind('mousedown keydown mouseup keyup mouseout', function(event) { 294 $(this).toggleClass('ui-state-active ui-state-focus', event.type.substr(-4) === 'down'); 295 }); 296 297 // Redraw the tooltip when we're done 298 self.redraw(); 299 } 300 301 function createTitle() 302 { 303 var id = tooltipID+'-title'; 304 305 // Destroy previous title element, if present 306 if(elements.titlebar) { removeTitle(); } 307 308 // Create title bar and title elements 309 elements.titlebar = $('<div />', { 310 'class': uitooltip + '-titlebar ' + (options.style.widget ? 'ui-widget-header' : '') 311 }) 312 .append( 313 elements.title = $('<div />', { 314 'id': id, 315 'class': uitooltip + '-title', 316 'aria-atomic': TRUE 317 }) 318 ) 319 .insertBefore(elements.content); 320 321 // Create button if enabled 322 if(options.content.title.button) { createButton(); } 323 324 // Redraw the tooltip dimensions if it's rendered 325 else if(self.rendered){ self.redraw(); } 326 } 327 328 function updateButton(button) 329 { 330 var elem = elements.button, 331 title = elements.title; 332 333 // Make sure tooltip is rendered and if not, return 334 if(!self.rendered) { return FALSE; } 335 336 if(!button) { 337 elem.remove(); 338 } 339 else { 340 if(!title) { 341 createTitle(); 342 } 343 createButton(); 344 } 345 } 346 347 function updateTitle(content, reposition) 348 { 349 var elem = elements.title; 350 351 // Make sure tooltip is rendered and if not, return 352 if(!self.rendered || !content) { return FALSE; } 353 354 // Use function to parse content 355 if($.isFunction(content)) { 356 content = content.call(target, cache.event, self); 357 } 358 359 // Remove title if callback returns false 360 if(content === FALSE) { return removeTitle(FALSE); } 361 362 // Append new content if its a DOM array and show it if hidden 363 else if(content.jquery && content.length > 0) { 364 elem.empty().append(content.css({ display: 'block' })); 365 } 366 367 // Content is a regular string, insert the new content 368 else { elem.html(content); } 369 370 // Redraw and reposition 371 self.redraw(); 372 if(reposition !== FALSE && self.rendered && tooltip.is(':visible')) { 373 self.reposition(cache.event); 374 } 375 } 376 377 function updateContent(content, reposition) 378 { 379 var elem = elements.content; 380 381 // Make sure tooltip is rendered and content is defined. If not return 382 if(!self.rendered || !content) { return FALSE; } 383 384 // Use function to parse content 385 if($.isFunction(content)) { 386 content = content.call(target, cache.event, self) || ''; 387 } 388 389 // Append new content if its a DOM array and show it if hidden 390 if(content.jquery && content.length > 0) { 391 elem.empty().append(content.css({ display: 'block' })); 392 } 393 394 // Content is a regular string, insert the new content 395 else { elem.html(content); } 396 397 // Image detection 398 function detectImages(next) { 399 var images, srcs = {}; 400 401 function imageLoad(image) { 402 // Clear src from object and any timers and events associated with the image 403 if(image) { 404 delete srcs[image.src]; 405 clearTimeout(self.timers.img[image.src]); 406 $(image).unbind(namespace); 407 } 408 409 // If queue is empty after image removal, update tooltip and continue the queue 410 if($.isEmptyObject(srcs)) { 411 self.redraw(); 412 if(reposition !== FALSE) { 413 self.reposition(cache.event); 414 } 415 416 next(); 417 } 418 } 419 420 // Find all content images without dimensions, and if no images were found, continue 421 if((images = elem.find('img:not([height]):not([width])')).length === 0) { return imageLoad(); } 422 423 // Apply timer to each image to poll for dimensions 424 images.each(function(i, elem) { 425 // Skip if the src is already present 426 if(srcs[elem.src] !== undefined) { return; } 427 428 (function timer(){ 429 // When the dimensions are found, remove the image from the queue 430 if(elem.height || elem.width) { return imageLoad(elem); } 431 432 // Restart timer 433 self.timers.img[elem.src] = setTimeout(timer, 700); 434 }()); 435 436 // Also apply regular load/error event handlers 437 $(elem).bind('error'+namespace+' load'+namespace, function(){ imageLoad(this); }); 438 439 // Store the src and element in our object 440 srcs[elem.src] = elem; 441 }); 442 } 443 444 /* 445 * If we're still rendering... insert into 'fx' queue our image dimension 446 * checker which will halt the showing of the tooltip until image dimensions 447 * can be detected properly. 448 */ 449 if(self.rendered < 0) { tooltip.queue('fx', detectImages); } 450 451 // We're fully rendered, so reset isDrawing flag and proceed without queue delay 452 else { isDrawing = 0; detectImages($.noop); } 453 454 return self; 455 } 456 457 function assignEvents() 458 { 459 var posOptions = options.position, 460 targets = { 461 show: options.show.target, 462 hide: options.hide.target, 463 viewport: $(posOptions.viewport), 464 document: $(document), 465 window: $(window) 466 }, 467 events = { 468 show: $.trim('' + options.show.event).split(' '), 469 hide: $.trim('' + options.hide.event).split(' ') 470 }, 471 IE6 = $.browser.msie && parseInt($.browser.version, 10) === 6; 472 473 // Define show event method 474 function showMethod(event) 475 { 476 if(tooltip.hasClass(disabled)) { return FALSE; } 477 478 // If set, hide tooltip when inactive for delay period 479 targets.show.trigger('qtip-'+id+'-inactive'); 480 481 // Clear hide timers 482 clearTimeout(self.timers.show); 483 clearTimeout(self.timers.hide); 484 485 // Start show timer 486 var callback = function(){ self.toggle(TRUE, event); }; 487 if(options.show.delay > 0) { 488 self.timers.show = setTimeout(callback, options.show.delay); 489 } 490 else{ callback(); } 491 } 492 493 // Define hide method 494 function hideMethod(event) 495 { 496 if(tooltip.hasClass(disabled) || isPositioning || isDrawing) { return FALSE; } 497 498 // Check if new target was actually the tooltip element 499 var relatedTarget = $(event.relatedTarget || event.target), 500 ontoTooltip = relatedTarget.closest(selector)[0] === tooltip[0], 501 ontoTarget = relatedTarget[0] === targets.show[0]; 502 503 // Clear timers and stop animation queue 504 clearTimeout(self.timers.show); 505 clearTimeout(self.timers.hide); 506 507 // Prevent hiding if tooltip is fixed and event target is the tooltip. Or if mouse positioning is enabled and curs
507or momentarily overlaps 508 if((posOptions.target === 'mouse' && ontoTooltip) || (options.hide.fixed && ((/mouse(out|leave|move)/).test(event.type) && (ontoTooltip || ontoTarget)))) { 509 event.preventDefault(); event.stopImmediatePropagation(); return; 510 } 511 512 // If tooltip has displayed, start hide timer 513 if(options.hide.delay > 0) { 514 self.timers.hide = setTimeout(function(){ self.hide(event); }, options.hide.delay); 515 } 516 else{ self.hide(event); } 517 } 518 519 // Define inactive method 520 function inactiveMethod(event) 521 { 522 if(tooltip.hasClass(disabled)) { return FALSE; } 523 524 // Clear timer 525 clearTimeout(self.timers.inactive); 526 self.timers.inactive = setTimeout(function(){ self.hide(event); }, options.hide.inactive); 527 } 528 529 function repositionMethod(event) { 530 if(tooltip.is(':visible')) { self.reposition(event); } 531 } 532 533 // On mouseenter/mouseleave... 534 tooltip.bind('mouseenter'+namespace+' mouseleave'+namespace, function(event) { 535 var state = event.type === 'mouseenter'; 536 537 // Focus the tooltip on mouseenter (z-index stacking) 538 if(state) { self.focus(event); } 539 540 // Add hover class 541 tooltip.toggleClass(hoverClass, state); 542 }); 543 544 // Enable hide.fixed 545 if(options.hide.fixed) { 546 // Add tooltip as a hide target 547 targets.hide = targets.hide.add(tooltip); 548 549 // Clear hide timer on tooltip hover to prevent it from closing 550 tooltip.bind('mouseover'+namespace, function() { 551 if(!tooltip.hasClass(disabled)) { clearTimeout(self.timers.hide); } 552 }); 553 } 554 555 // If using mouseout/mouseleave as a hide event... 556 if(/mouse(out|leave)/i.test(options.hide.event)) { 557 // Hide tooltips when leaving current window/frame (but not select/option elements) 558 if(options.hide.leave === 'window') { 559 targets.window.bind('mouseout' + namespace, function(event) { 560 if(/select|option/.test(event.target) && !event.relatedTarget) { self.hide(event); } 561 }); 562 } 563 } 564 565 /* 566 * Make sure hoverIntent functions properly by using mouseleave to clear show timer if 567 * mouseenter/mouseout is used for show.event, even if it isn't in the users options. 568 */ 569 else if(/mouse(over|enter)/i.test(options.show.event)) { 570 targets.hide.bind('mouseleave'+namespace, function(event) { 571 clearTimeout(self.timers.show); 572 }); 573 } 574 575 // Hide tooltip on document mousedown if unfocus events are enabled 576 if(('' + options.hide.event).indexOf('unfocus') > -1) { 577 targets.document.bind('mousedown'+namespace, function(event) { 578 var $target = $(event.target), 579 enabled = !tooltip.hasClass(disabled) && tooltip.is(':visible'); 580 581 if($target[0] !== tooltip[0] && $target.parents(selector).length === 0 && $target.add(target).length > 1) { 582 self.hide(event); 583 } 584 }); 585 } 586 587 // Check if the tooltip hides when inactive 588 if('number' === typeof options.hide.inactive) { 589 // Bind inactive method to target as a custom event 590 targets.show.bind('qtip-'+id+'-inactive', inactiveMethod); 591 592 // Define events which reset the 'inactive' event handler 593 $.each(QTIP.inactiveEvents, function(index, type){ 594 targets.hide.add(elements.tooltip).bind(type+namespace+'-inactive', inactiveMethod); 595 }); 596 } 597 598 // Apply hide events 599 $.each(events.hide, function(index, type) { 600 var showIndex = $.inArray(type, events.show), 601 targetHide = $(targets.hide); 602 603 // Both events and targets are identical, apply events using a toggle 604 if((showIndex > -1 && targetHide.add(targets.show).length === targetHide.length) || type === 'unfocus') 605 { 606 targets.show.bind(type+namespace, function(event) { 607 if(tooltip.is(':visible')) { hideMethod(event); } 608 else { showMethod(event); } 609 }); 610 611 // Don't bind the event again 612 delete events.show[ showIndex ]; 613 } 614 615 // Events are not identical, bind normally 616 else { targets.hide.bind(type+namespace, hideMethod); } 617 }); 618 619 // Apply show events 620 $.each(events.show, function(index, type) { 621 targets.show.bind(type+namespace, showMethod); 622 }); 623 624 // Check if the tooltip hides when mouse is moved a certain distance 625 if('number' === typeof options.hide.distance) { 626 // Bind mousemove to target to detect distance difference 627 targets.show.add(tooltip).bind('mousemove'+namespace, function(event) { 628 var origin = cache.origin || {}, 629 limit = options.hide.distance, 630 abs = Math.abs; 631 632 // Check if the movement has gone beyond the limit, and hide it if so 633 if(abs(event.pageX - origin.pageX) >= limit || abs(event.pageY - origin.pageY) >= limit) { 634 self.hide(event); 635 } 636 }); 637 } 638 639 // Mouse positioning events 640 if(posOptions.target === 'mouse') { 641 // Cache mousemove coords on show targets 642 targets.show.bind('mousemove'+namespace, function(event) { 643 MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' }; 644 }); 645 646 // If mouse adjustment is on... 647 if(posOptions.adjust.mouse) { 648 // Apply a mouseleave event so we don't get problems with overlapping 649 if(options.hide.event) { 650 tooltip.bind('mouseleave'+namespace, function(event) { 651 if((event.relatedTarget || event.target) !== targets.show[0]) { self.hide(event); } 652 }); 653 } 654 655 // Update tooltip position on mousemove 656 targets.document.bind('mousemove'+namespace, function(event) { 657 // Update the tooltip position only if the tooltip is visible and adjustment is enabled 658 if(!tooltip.hasClass(disabled) && tooltip.is(':visible')) { 659 self.reposition(event || MOUSE); 660 } 661 }); 662 } 663 } 664 665 // Adjust positions of the tooltip on window resize if enabled 666 if(posOptions.adjust.resize || targets.viewport.length) { 667 ($.event.special.resize ? targets.viewport : targets.window).bind('resize'+namespace, repositionMethod); 668 } 669 670 // Adjust tooltip position on scroll if screen adjustment is enabled 671 if(targets.viewport.length || (IE6 && tooltip.css('position') === 'fixed')) { 672 targets.viewport.bind('scroll'+namespace, repositionMethod); 673 } 674 } 675 676 function unassignEvents() 677 { 678 var targets = [ 679 options.show.target[0], 680 options.hide.target[0], 681 self.rendered && elements.tooltip[0], 682 options.position.container[0], 683 options.position.viewport[0], 684 window, 685 document 686 ]; 687 688 // Check if tooltip is rendered 689 if(self.rendered) { 690 $([]).pushStack( $.grep(targets, function(i){ return typeof i === 'object'; }) ).unbind(namespace); 691 } 692 693 // Tooltip isn't yet rendered, remove render event 694 else { options.show.target.unbind(namespace+'-create'); } 695 } 696 697 // Setup builtin .set() option checks 698 self.checks.builtin = { 699 // Core checks 700 '^id$': function(obj, o, v) { 701 var id = v === TRUE ? QTIP.nextid : v, 702 tooltipID = uitooltip + '-' + id; 703 704 if(id !== FALSE && id.length > 0 && !$('#'+tooltipID).length) { 705 tooltip[0].id = tooltipID; 706 elements.content[0].id = tooltipID + '-content'; 707 elements.title[0].id = tooltipID + '-title'; 708 } 709 }, 710 711 // Content checks 712 '^content.text$': function(obj, o, v){ updateContent(v); }, 713 '^content.title.text$': function(obj, o, v) { 714 // Remove title if content is null 715 if(!v) { return removeTitle(); } 716 717 // If title isn't already created, create it now and update 718 if(!elements.title && v) { createTitle(); } 719 updateTitle(v); 720 }, 721 '^content.title.button$': function(obj, o, v){ updateButton(v); }, 722 723 // Position checks
724 '^position.(my|at)$': function(obj, o, v){ 725 // Parse new corner value into Corner objecct 726 if('string' === typeof v) { 727 obj[o] = new PLUGINS.Corner(v); 728 } 729 }, 730 '^position.container$': function(obj, o, v){ 731 if(self.rendered) { tooltip.appendTo(v); } 732 }, 733 734 // Show checks 735 '^show.ready$': function() { 736 if(!self.rendered) { self.render(1); } 737 else { self.toggle(TRUE); } 738 }, 739 740 // Style checks 741 '^style.classes$': function(obj, o, v) { 742 tooltip.attr('class', uitooltip + ' qtip ui-helper-reset ' + v); 743 }, 744 '^style.widget|content.title': setWidget, 745 746 // Events check 747 '^events.(render|show|move|hide|focus|blur)$': function(obj, o, v) { 748 tooltip[($.isFunction(v) ? '' : 'un') + 'bind']('tooltip'+o, v); 749 }, 750 751 // Properties which require event reassignment 752 '^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)': function() { 753 var posOptions = options.position; 754 755 // Set tracking flag 756 tooltip.attr('tracking', posOptions.target === 'mouse' && posOptions.adjust.mouse); 757 758 // Reassign events 759 unassignEvents(); assignEvents(); 760 } 761 }; 762 763 /* 764 * Public API methods 765 */ 766 $.extend(self, { 767 render: function(show) 768 { 769 if(self.rendered) { return self; } // If tooltip has already been rendered, exit 770 771 var title = options.content.title.text, 772 posOptions = options.position, 773 callback = $.Event('tooltiprender'); 774 775 // Add ARIA attributes to target 776 $.attr(target[0], 'aria-describedby', tooltipID); 777 778 // Create tooltip element 779 tooltip = elements.tooltip = $('<div/>', { 780 'id': tooltipID, 781 'class': uitooltip + ' qtip ui-helper-reset ' + defaultClass + ' ' + options.style.classes, 782 'width': options.style.width || '', 783 'height': options.style.height || '', 784 'tracking': posOptions.target === 'mouse' && posOptions.adjust.mouse, 785 786 /* ARIA specific attributes */ 787 'role': 'alert', 788 'aria-live': 'polite', 789 'aria-atomic': FALSE, 790 'aria-describedby': tooltipID + '-content', 791 'aria-hidden': TRUE 792 }) 793 .toggleClass(disabled, cache.disabled) 794 .data('qtip', self) 795 .appendTo(options.position.container) 796 .append( 797 // Create content element 798 elements.content = $('<div />', { 799 'class': uitooltip + '-content', 800 'id': tooltipID + '-content', 801 'aria-atomic': TRUE 802 }) 803 ); 804 805 // Set rendered flag and prevent redundant redraw/reposition calls for now 806 self.rendered = -1; 807 isDrawing = 1; isPositioning = 1; 808 809 // Update title 810 if(title) { 811 createTitle(); 812 updateTitle(title, FALSE); 813 } 814 815 // Set proper rendered flag and update content 816 updateContent(options.content.text, FALSE); 817 self.rendered = TRUE; 818 819 // Setup widget classes 820 setWidget(); 821 822 // Assign passed event callbacks (before plugins!) 823 $.each(options.events, function(name, callback) { 824 if($.isFunction(callback)) { 825 tooltip.bind(name === 'toggle' ? 'tooltipshow tooltiphide' : 'tooltip'+name, callback); 826 } 827 }); 828 829 // Initialize 'render' plugins 830 $.each(PLUGINS, function() { 831 if(this.initialize === 'render') { this(self); } 832 }); 833 834 // Assign events 835 assignEvents(); 836 837 /* Queue this part of the render process in our fx queue so we can 838 * load images before the tooltip renders fully. 839 * 840 * See: updateContent method 841 */ 842 tooltip.queue('fx', function(next) { 843 // Trigger tooltiprender event and pass original triggering event as original 844 callback.originalEvent = cache.event; 845 tooltip.trigger(callback, [self]); 846 847 // Reset flags 848 isDrawing = 0; isPositioning = 0; 849 850 // Redraw the tooltip manually now we're fully rendered 851 self.redraw(); 852 853 // Show tooltip if needed 854 if(options.show.ready || show) { 855 self.toggle(TRUE, cache.event); 856 } 857 858 next(); // Move on to next method in queue 859 }); 860 861 return self; 862 }, 863 864 get: function(notation) 865 { 866 var result, o; 867 868 switch(notation.toLowerCase()) 869 { 870 case 'dimensions': 871 result = { 872 height: tooltip.outerHeight(), width: tooltip.outerWidth() 873 }; 874 break; 875 876 case 'offset': 877 result = PLUGINS.offset(tooltip, options.position.container); 878 break; 879 880 default: 881 o = convertNotation(notation.toLowerCase()); 882 result = o[0][ o[1] ]; 883 result = result.precedance ? result.string() : result; 884 break; 885 } 886 887 return result; 888 }, 889 890 set: function(option, value) 891 { 892 var rmove = /^position\.(my|at|adjust|target|container)|style|content|show\.ready/i,
893 rdraw = /^content\.(title|attr)|style/i, 894 reposition = FALSE, 895 redraw = FALSE, 896 checks = self.checks, 897 name; 898 899 function callback(notation, args) { 900 var category, rule, match; 901 902 for(category in checks) { 903 for(rule in checks[category]) { 904 if(match = (new RegExp(rule, 'i')).exec(notation)) { 905 args.push(match); 906 checks[category][rule].apply(self, args); 907 } 908 } 909 } 910 } 911 912 // Convert singular option/value pair into object form 913 if('string' === typeof option) { 914 name = option; option = {}; option[name] = value; 915 } 916 else { option = $.extend(TRUE, {}, option); } 917 918 // Set all of the defined options to their new values 919 $.each(option, function(notation, value) { 920 var obj = convertNotation( notation.toLowerCase() ), previous; 921 922 // Set new obj value 923 previous = obj[0][ obj[1] ]; 924 obj[0][ obj[1] ] = 'object' === typeof value && value.nodeType ? $(value) : value; 925 926 // Set the new params for the callback 927 option[notation] = [obj[0], obj[1], value, previous]; 928 929 // Also check if we need to reposition / redraw 930 reposition = rmove.test(notation) || reposition; 931 redraw = rdraw.test(notation) || redraw; 932 }); 933 934 // Re-sanitize options 935 sanitizeOptions(options); 936 937 /* 938 * Execute any valid callbacks for the set options 939 * Also set isPositioning/isDrawing so we don't get loads of redundant repositioning 940 * and redraw calls. 941 */ 942 isPositioning = isDrawing = 1; $.each(option, callback); isPositioning = isDrawing = 0; 943 944 // Update position / redraw if needed 945 if(tooltip.is(':visible') && self.rendered) { 946 if(reposition) { 947 self.reposition( options.position.target === 'mouse' ? NULL : cac
947he.event ); 948 } 949 if(redraw) { self.redraw(); } 950 } 951 952 return self; 953 }, 954 955 toggle: function(state, event) 956 { 957 // Make sure tooltip is rendered 958 if(!self.rendered) { 959 if(state) { self.render(1); } // Render the tooltip if showing and it isn't already 960 else { return self; } 961 } 962 963 var type = state ? 'show' : 'hide', 964 opts = options[type], 965 visible = tooltip.is(':visible'), 966 sameTarget = !event || options[type].target.length < 2 || cache.target[0] === event.target, 967 posOptions = options.position, 968 contentOptions = options.content, 969 delay, 970 callback; 971 972 // Detect state if valid one isn't provided 973 if((typeof state).search('boolean|number')) { state = !visible; } 974 975 // Return if element is already in correct state 976 if(!tooltip.is(':animated') && visible === state && sameTarget) { return self; } 977 978 // Try to prevent flickering when tooltip overlaps show element 979 if(event) { 980 if((/over|enter/).test(event.type) && (/out|leave/).test(cache.event.type) && 981 event.target === options.show.target[0] && tooltip.has(event.relatedTarget).length) { 982 return self; 983 } 984 985 // Cache event 986 cache.event = $.extend({}, event); 987 } 988 989 // Call API methods 990 callback = $.Event('tooltip'+type); 991 callback.originalEvent = event ? cache.event : NULL; 992 tooltip.trigger(callback, [self, 90]); 993 if(callback.isDefaultPrevented()){ return self; } 994 995 // Set ARIA hidden status attribute 996 $.attr(tooltip[0], 'aria-hidden', !!!state); 997 998 // Execute state specific properties 999 if(state) { 1000 // Store show origin coordinates
1001 cache.origin = $.extend({}, MOUSE); 1002 1003 // Focus the tooltip 1004 self.focus(event); 1005 1006 // Update tooltip content & title if it's a dynamic function 1007 if($.isFunction(contentOptions.text)) { updateContent(contentOptions.text, FALSE); } 1008 if($.isFunction(contentOptions.title.text)) { updateTitle(contentOptions.title.text, FALSE); } 1009 1010 // Cache mousemove events for positioning purposes (if not already tracking) 1011 if(!trackingBound && posOptions.target === 'mouse' && posOptions.adjust.mouse) { 1012 $(document).bind('mousemove.qtip', function(event) { 1013 MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' }; 1014 }); 1015 trackingBound = TRUE; 1016 } 1017 1018 // Update the tooltip position 1019 self.reposition(event); 1020 1021 // Hide other tooltips if tooltip is solo, using it as the context 1022 if(opts.solo) { $(selector, opts.solo).not(tooltip).qtip('hide', callback); } 1023 } 1024 else { 1025 // Clear show timer if we're hiding 1026 clearTimeout(self.timers.show); 1027 1028 // Remove cached origin on hide 1029 delete cache.origin; 1030 1031 // Remove mouse tracking event if not needed (all tracking qTips are hidden) 1032 if(trackingBound && !$(selector+'[tracking="true"]:visible', opts.solo).not(tooltip).length) { 1033 $(document).unbind('mousemove.qtip'); 1034 trackingBound = FALSE; 1035 } 1036 1037 // Blur the tooltip 1038 self.blur(event); 1039 } 1040 1041 // Define post-animation, state specific properties 1042 function after() { 1043 if(state) { 1044 // Prevent antialias from disappearing in IE by removing filter 1045 if($.browser.msie) { tooltip[0].style.removeAttribute('filter'); } 1046 1047 // Remove overflow setting to prevent tip bugs 1048 tooltip.css('overflow', ''); 1049 1050 // Autofocus elements if enabled 1051 if('string' === typeof opts.autofocus) { 1052 $(opts.autofocus, tooltip).focus(); 1053 } 1054 1055 // Call API method 1056 callback = $.Event('tooltipvisible'); 1057 callback.originalEvent = event ? cache.event : NULL; 1058 tooltip.trigger(callback, [self]); 1059 } 1060 else { 1061 // Reset CSS states 1062 tooltip.css({ 1063 display: '', 1064 visibility: '', 1065 opacity: '', 1066 left: '', 1067 top: '' 1068 }); 1069 } 1070 } 1071 1072 // Clear animation queue if same target 1073 if(sameTarget) { tooltip.stop(0, 1); } 1074 1075 // If no effect type is supplied, use a simple toggle 1076 if(opts.effect === FALSE) { 1077 tooltip[ type ](); 1078 after.call(tooltip); 1079 } 1080 1081 // Use custom function if provided 1082 else if($.isFunction(opts.effect)) { 1083 opts.effect.call(tooltip, self); 1084 tooltip.queue('fx', function(n){ after(); n(); }); 1085 } 1086 1087 // Use basic fade function by default 1088 else { tooltip.fadeTo(90, state ? 1 : 0, after); } 1089 1090 // If inactive hide method is set, active it 1091 if(state) { opts.target.trigger('qtip-'+id+'-inactive'); } 1092 1093 return self; 1094 }, 1095 1096 show: function(event){ return self.toggle(TRUE, event); }, 1097 1098 hide: function(event){ return self.toggle(FALSE, event); }, 1099 1100 focus: function(event) 1101 { 1102 if(!self.rendered) { return self; } 1103 1104 var qtips = $(selector), 1105 curIndex = parseInt(tooltip[0].style.zIndex, 10), 1106 newIndex = QTIP.zindex + qtips.length, 1107 cachedEvent = $.extend({}, event), 1108 focusedElem, callback; 1109 1110 // Only update the z-index if it has changed and tooltip is not already focused 1111 if(!tooltip.hasClass(focusClass)) 1112 { 1113 // Call API method 1114 callback = $.Event('tooltipfocus'); 1115 callback.originalEvent = cachedEvent; 1116 tooltip.trigger(callback, [self, newIndex]); 1117 1118 // If default action wasn't prevented... 1119 if(!callback.isDefaultPrevented()) { 1120 // Only update z-index's if they've changed 1121 if(curIndex !== newIndex) { 1122 // Reduce our z-index's and keep them properly ordered 1123 qtips.each(function() { 1124 if(this.style.zIndex > curIndex) { 1125 this.style.zIndex = this.style.zIndex - 1; 1126 } 1127 }); 1128 1129 // Fire blur event for focused tooltip 1130 qtips.filter('.' + focusClass).qtip('blur', cachedEvent); 1131 } 1132 1133 // Set the new z-index 1134 tooltip.addClass(focusClass)[0].style.zIndex = newIndex; 1135 } 1136 } 1137 1138 return self; 1139 }, 1140 1141 blur: function(event) { 1142 var cachedEvent = $.extend({}, event), 1143 callback; 1144 1145 // Set focused status to FALSE 1146 tooltip.removeClass(focusClass); 1147 1148 // Trigger blur event 1149 callback = $.Event('tooltipblur'); 1150 callback.originalEvent = cachedEvent; 1151 tooltip.trigger(callback, [self]); 1152 1153 return self; 1154 }, 1155 1156 reposition: function(event, effect) 1157 { 1158 if(!self.rendered || isPositioning) { return self; } 1159 1160 // Set positioning flag 1161 isPositioning = 1; 1162 1163 var target = options.position.target, 1164 posOptions = options.position, 1165 my = posOptions.my, 1166 at = posOptions.at, 1167 adjust = posOptions.adjust, 1168 method = adjust.method.split(' '), 1169 elemWidth = tooltip.outerWidth(), 1170 elemHeight = tooltip.outerHeight(), 1171 targetWidth = 0, 1172 targetHeight = 0, 1173 callback = $.Event('tooltipmove'), 1174 fixed = tooltip.css('position') === 'fixed', 1175 viewport = posOptions.viewport, 1176 position = { left: 0, top: 0 }, 1177 tip = self.plugins.tip, 1178 readjust = { 1179 // Repositioning method and axis detection 1180 horizontal: method[0], 1181 vertical: method[1] || method[0], 1182 1183 // Reposition methods 1184 left: function(posLeft) { 1185 var isShift = readjust.horizontal === 'shift', 1186 viewportScroll = viewport.offset.left + viewport.scrollLeft, 1187 myWidth = my.x === 'left' ? elemWidth : my.x === 'right' ? -elemWidth : -elemWidth / 2, 1188 atWidth = at.x === 'left' ? targetWidth : at.x === 'right' ? -targetWidth : -targetWidth / 2, 1189 tipWidth = tip && tip.size ? tip.size.width || 0 : 0, 1190 tipAdjust = tip && tip.corner && tip.corner.precedance === 'x' && !isShift ? tipWidth : 0, 1191 overflowLeft = viewportScroll - posLeft + tipAdjust, 1192 overflowRight = posLeft + elemWidth - viewport.width - viewportScroll + tipAdjust, 1193 offset = myWidth - (my.precedance === 'x' || my.x === my.y ? atWidth : 0), 1194 isCenter = my.x === 'center'; 1195 1196 // Optional 'shift' style repositioning 1197 if(isShift) { 1198 tipAdjust = tip && tip.corner && tip.corner.precedance === 'y' ? tipWidth : 0; 1199 offset = (my.x === 'left' ? 1 : -1) * myWidth - tipAdjust; 1200 1201 // Adjust position but keep it within viewport dimensions 1202 position.left += overflowLeft > 0 ? overflowLeft : overflowRight > 0 ? -overflowRight : 0; 1203 position.left = Math.max( 1204 viewport.offset.left + (tipAdjust && tip.corner.x === 'center' ? tip.offset : 0), 1205 posLeft - offset, 1206 Math.min( 1207 Math.max(viewport.offset.left + viewport.width, posLeft + offset), 1208 position.left 1209 ) 1210 ); 1211 } 1212 1213 // Default 'flip' repositioning 1214 else { 1215 if(overflowLeft > 0 && (my.x !== 'left' || overflowRight > 0)) { 1216 position.left -= offset + (isCenter ? 0 : 2 * adjust.x); 1217 } 1218 else if(overflowRight > 0 && (my.x !== 'right' || overflowLeft > 0) ) { 1219 position.left -= isCenter ? -offset : offset + (2 * adjust.x); 1220 } 1221 if(position.left !== posLeft && isCenter) { position.left -= adjust.x; } 1222 1223 // Make sure we haven't made things worse with the adjustment and return the adjusted difference 1224 if(position.left < viewportScroll && -position.left > overflowRight) { position.left = posLeft; } 1225 } 1226 1227 return position.left - posLeft; 1228 }, 1229 top: function(posTop) { 1230 var isShift = readjust.vertical === 'shift', 1231 viewportScroll = viewport.offset.top + viewport.scrollTop, 1232 myHeight = my.y === 'top' ? elemHeight : my.y === 'bottom' ? -elemHeight : -elemHeight / 2, 1233 atHeight = at.y === 'top' ? targetHeight : at.y === 'bottom' ? -targetHeight : -targetHeight / 2, 1234 tipHeight = tip && tip.size ? tip.size.height || 0 : 0, 1235 tipAdjust = tip && tip.corner && tip.corner.precedance === 'y' && !isShift ? tipHeight : 0,
1236 overflowTop = viewportScroll - posTop + tipAdjust, 1237 overflowBottom = posTop + elemHeight - viewport.height - viewportScroll + tipAdjust, 1238 offset = myHeight - (my.precedance === 'y' || my.x === my.y ? atHeight : 0), 1239 isCenter = my.y === 'center'; 1240 1241 // Optional 'shift' style repositioning 1242 if(isShift) { 1243 tipAdjust = tip && tip.corner && tip.corner.precedance === 'x' ? tipHeight : 0; 1244 offset = (my.y === 'top' ? 1 : -1) * myHeight - tipAdjust; 1245 1246 // Adjust position but keep it within viewport dimensions 1247 position.top += overflowTop > 0 ? overflowTop : overflowBottom > 0 ? -overflowBottom : 0; 1248 position.top = Math.max( 1249 viewport.offset.top + (tipAdjust && tip.corner.x === 'center' ? tip.offset : 0), 1250 posTop - offset, 1251 Math.min( 1252 Math.max(viewport.offset.top + viewport.height, posTop + offset), 1253 position.top 1254 ) 1255 ); 1256 } 1257 1258 // Default 'flip' repositioning 1259 else { 1260 if(overflowTop > 0 && (my.y !== 'top' || overflowBottom > 0)) { 1261 position.top -= offset + (isCenter ? 0 : 2 * adjust.y); 1262 } 1263 else if(overflowBottom > 0 && (my.y !== 'bottom' || overflowTop > 0) ) { 1264 position.top -= isCenter ? -offset : offset + (2 * adjust.y); 1265 } 1266 if(position.top !== posTop && isCenter) { position.top -= adjust.y; } 1267 1268 // Make sure we haven't made things worse with the adjustment and return the adjusted difference 1269 if(position.top < 0 && -position.top > overflowBottom) { position.top = posTop; } 1270 } 1271 1272 return position.top - posTop; 1273 } 1274 }; 1275 1276 // Check if absolute position was passed 1277 if($.isArray(target) && target.length === 2) { 1278 // Force left top and set position 1279 at = { x: 'left', y: 'top' }; 1280 position = { left: target[0], top: target[1] }; 1281 } 1282 1283 // Check if mouse was the target 1284 else if(target === 'mouse' && ((event && event.pageX) || cache.event.pageX)) { 1285 // Force left top to allow flipping 1286 at = { x: 'left', y: 'top' }; 1287 1288 // Use cached event if one isn't available for positioning 1289 event = (event && (event.type === 'resize' || event.type === 'scroll') ? cache.event : 1290 event && event.pageX && event.type === 'mousemove' ? event : 1291 MOUSE && MOUSE.pageX && (adjust.mouse || !event || !event.pageX) ? { pageX: MOUSE.pageX, pageY: MOUSE.pageY } : 1292 !adjust.mouse && cache.origin && cache.origin.pageX ? cache.origin : 1293 event) || event || cache.event || MOUSE || {}; 1294 1295 // Use event coordinates for position 1296 position = { top: event.pageY, left: event.pageX }; 1297 } 1298 1299 // Target wasn't mouse or absolute... 1300 else { 1301 // Check if event targetting is being used 1302 if(target === 'event') { 1303 if(event && event.target && event.type !== 'scroll' && event.type !== 'resize') { 1304 target = cache.target = $(event.target); 1305 } 1306 else { 1307 target = cache.target; 1308 } 1309 } 1310 else { cache.target = $(target); } 1311 1312 // Parse the target into a jQuery object and make sure there's an element present 1313 target = $(target).eq(0); 1314 if(target.length === 0) { return self; } 1315 1316 // Check if window or document is the target 1317 else if(target[0] === document || target[0] === window) { 1318 targetWidth = PLUGINS.iOS ? window.innerWidth : target.width(); 1319 targetHeight = PLUGINS.iOS ? window.innerHeight : target.height(); 1320 1321 if(target[0] === window) { 1322 position = { 1323 top: !fixed || PLUGINS.iOS ? (viewport || target).scrollTop() : 0, 1324 left: !fixed || PLUGINS.iOS ? (viewport || target).scrollLeft() : 0 1325 }; 1326 } 1327 } 1328 1329 // Use Imagemap/SVG plugins if needed 1330 else if(target.is('area') && PLUGINS.imagemap) { 1331 position = PLUGINS.imagemap(target, at); 1332 } 1333 else if(target[0].namespaceURI === 'http://www.w3.org/2000/svg' && PLUGINS.svg) { 1334 position = PLUGINS.svg(target, at); 1335 } 1336 1337 else { 1338 targetWidth = target.outerWidth(); 1339 targetHeight = target.outerHeight(); 1340 1341 position = PLUGINS.offset(target, posOptions.container, fixed); 1342 } 1343 1344 // Parse returned plugin values into proper variables 1345 if(position.offset) { 1346 targetWidth = position.width; 1347 targetHeight = position.height; 1348 position = position.offset; 1349 } 1350 1351 // Adjust position relative to target 1352 position.left += at.x === 'right' ? targetWidth : at.x === 'center' ? targetWidth / 2 : 0; 1353 position.top += at.y === 'bottom' ? targetHeight : at.y === 'center' ? targetHeight / 2 : 0; 1354 } 1355 1356 // Adjust position relative to tooltip 1357 position.left += adjust.x + (my.x === 'right' ? -elemWidth : my.x === 'center' ? -elemWidth / 2 : 0); 1358 position.top += adjust.y + (my.y === 'bottom' ? -elemHeight : my.y === 'center' ? -elemHeight / 2 : 0); 1359 1360 // Calculate collision offset values if viewport positioning is enabled 1361 if(viewport.jquery && target[0] !== window && target[0] !== docBody &&
1362 readjust.vertical+readjust.horizontal !== 'nonenone') 1363 { 1364 // Cache our viewport details 1365 viewport = { 1366 elem: viewport, 1367 height: viewport[ (viewport[0] === window ? 'h' : 'outerH') + 'eight' ](), 1368 width: viewport[ (viewport[0] === window ? 'w' : 'outerW') + 'idth' ](), 1369 scrollLeft: fixed ? 0 : viewport.scrollLeft(), 1370 scrollTop: fixed ? 0 : viewport.scrollTop(), 1371 offset: viewport.offset() || { left: 0, top: 0 } 1372 }; 1373 1374 // Adjust position based onviewport and adjustment options 1375 position.adjusted = { 1376 left: readjust.horizontal !== 'none' ? readjust.left(position.left) : 0, 1377 top: readjust.vertical !== 'none' ? readjust.top(position.top) : 0 1378 }; 1379 } 1380 1381 //Viewport adjustment is disabled, set values to zero 1382 else { position.adjusted = { left: 0, top: 0 }; } 1383 1384 // Set tooltip position class 1385 tooltip.attr('class', function(i, val) { 1386 return $.attr(this, 'class').replace(/ui-tooltip-pos-\w+/i, ''); 1387 }) 1388 .addClass(uitooltip + '-pos-' + my.abbreviation()); 1389 1390 // Call API method 1391 callback.originalEvent = $.extend({}, event); 1392 tooltip.trigger(callback, [self, position, viewport.elem || viewport]); 1393 if(callback.isDefaultPrevented()){ return self; } 1394 delete position.adjusted; 1395 1396 // If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly 1397 if(effect === FALSE || isNaN(position.left) || isNaN(position.top) || target === 'mouse' || !$.isFunction(posOptions.effect)) { 1398 tooltip.css(position); 1399 } 1400 1401 // Use custom function if provided 1402 else if($.isFunction(posOptions.effect)) { 1403 posOptions.effect.call(tooltip, self, $.extend({}, position)); 1404 tooltip.queue(function(next) { 1405 // Reset attributes to avoid cross-browser rendering bugs 1406 $(this).css({ opacity: '', height: '' }); 1407 if($.browser.msie) { this.style.removeAttribute('filter'); } 1408 1409 next(); 1410 }); 1411 } 1412 1413 // Set positioning flag 1414 isPositioning = 0; 1415 1416 return self; 1417 }, 1418 1419 // Max/min width simulator function for all browsers.. yeaaah! 1420 redraw: function() 1421 { 1422 if(self.rendered < 1 || isDrawing) { return self; } 1423 1424 var container = options.position.container, 1425 perc, width, max, min; 1426 1427 // Set drawing flag 1428 isDrawing = 1; 1429 1430 // If tooltip has a set height, just set it... like a boss! 1431 if(options.style.height) { tooltip.css('height', options.style.height); } 1432 1433 // If tooltip has a set width, just set it... like a boss! 1434 if(options.style.width) { tooltip.css('width', options.style.width); } 1435 1436 // Otherwise simualte max/min width... 1437 else { 1438 // Reset width and add fluid class 1439 tooltip.css('width', '').addClass(fluidClass); 1440 1441 // Grab our tooltip width (add 1 so we don't get wrapping problems.. huzzah!) 1442 width = tooltip.width() + 1; 1443 1444 // Grab our max/min properties 1445 max = tooltip.css('max-width') || ''; 1446 min = tooltip.css('min-width') || ''; 1447 1448 // Parse into proper pixel values 1449 perc = (max + min).indexOf('%') > -1 ? container.width() / 100 : 0; 1450 max = ((max.indexOf('%') > -1 ? perc : 1) * parseInt(max, 10)) || width; 1451 min = ((min.indexOf('%') > -1 ? perc : 1) * parseInt(min, 10)) || 0; 1452 1453 // Determine new dimension size based on max/min/current values 1454 width = max + min ? Math.min(Math.max(width, min), max) : width; 1455 1456 // Set the newly calculated width and remvoe fluid class 1457 tooltip.css('width', Math.round(width)).removeClass(fluidClass); 1458 } 1459 1460 // Set drawing flag 1461 isDrawing = 0; 1462 1463 return self; 1464 }, 1465 1466 disable: function(state) 1467 { 1468 if('boolean' !== typeof state) { 1469 state = !(tooltip.hasClass(disabled) || cache.disabled); 1470 } 1471 1472 if(self.rendered) { 1473 tooltip.toggleClass(disabled, state); 1474 $.attr(tooltip[0], 'aria-disabled', state); 1475 } 1476 else { 1477 cache.disabled = !!state; 1478 } 1479 1480 return self; 1481 }, 1482 1483 enable: function() { return self.disable(FALSE); }, 1484 1485 destroy: function() 1486 { 1487 var t = target[0], 1488 title = $.attr(t, oldtitle); 1489 1490 // Destroy tooltip and any associated plugins if rendered 1491 if(self.rendered) { 1492 tooltip.remove(); 1493 1494 $.each(self.plugins, function() { 1495 if(this.destroy) { this.destroy(); } 1496 }); 1497 } 1498 1499 // Clear timers and remove bound events 1500 clearTimeout(self.timers.show); 1501 clearTimeout(self.timers.hide); 1502 unassignEvents(); 1503 1504 // Remove api object 1505 $.removeData(t, 'qtip'); 1506 1507 // Reset old title attribute if removed 1508 if(options.suppress && title) { 1509 $.attr(t, 'title', title); 1510 target.removeAttr(oldtitle); 1511 } 1512 1513 // Remove ARIA attributes and bound qtip events 1514 target.removeAttr('aria-describedby').unbind('.qtip'); 1515 1516 // Remove ID from sued id object 1517 delete usedIDs[self.id]; 1518 1519 return target; 1520 } 1521 }); 1522} 1523 1524// Initialization method 1525function init(id, opts) 1526{ 1527 var obj, posOptions, attr, config, title, 1528 1529 // Setup element references 1530 elem = $(this), 1531 docBody = $(document.body), 1532 1533 // Use document body instead of document element if needed
1534 newTarget = this === document ? docBody : elem, 1535 1536 // Grab metadata from element if plugin is present 1537 metadata = (elem.metadata) ? elem.metadata(opts.metadata) : NULL, 1538 1539 // If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise 1540 metadata5 = opts.metadata.type === 'html5' && metadata ? metadata[opts.metadata.name] : NULL, 1541 1542 // Grab data from metadata.name (or data-qtipopts as fallback) using .data() method, 1543 html5 = elem.data(opts.metadata.name || 'qtipopts'); 1544 1545 // If we don't get an object returned attempt to parse it manualyl without parseJSON 1546 try { html5 = typeof html5 === 'string' ? (new Function("return " + html5))() : html5; } 1547 catch(e) { log('Unable to parse HTML5 attribute data: ' + html5); } 1548 1549 // Merge in and sanitize metadata 1550 config = $.extend(TRUE, {}, QTIP.defaults, opts, 1551 typeof html5 === 'object' ? sanitizeOptions(html5) : NULL, 1552 sanitizeOptions(metadata5 || metadata)); 1553 1554 // Re-grab our positioning options now we've merged our metadata and set id to passed value 1555 posOptions = config.position; 1556 config.id = id; 1557 1558 // Setup missing content if none is detected 1559 if('boolean' === typeof config.content.text) { 1560 attr = elem.attr(config.content.attr); 1561 1562 // Grab from supplied attribute if available 1563 if(config.content.attr !== FALSE && attr) { config.content.text = attr; } 1564 1565 // No valid content was found, abort render 1566 else { 1567 log('Unable to locate content for tooltip! Aborting render of tooltip on element: ', elem); 1568 return FALSE; 1569 } 1570 } 1571 1572 // Setup target options 1573 if(posOptions.container === FALSE) { posOptions.container = docBody; } 1574 if(posOptions.target === FALSE) { posOptions.target = newTarget; } 1575 if(config.show.target === FALSE) { config.show.target = newTarget; } 1576 if(config.show.solo === TRUE) { config.show.solo = docBody; } 1577 if(config.hide.target === FALSE) { config.hide.target = newTarget; } 1578 if(config.position.viewport === TRUE) { config.position.viewport = posOptions.container; } 1579 1580 // Convert position corner values into x and y strings 1581 posOptions.at = new PLUGINS.Corner(posOptions.at); 1582 posOptions.my = new PLUGINS.Corner(posOptions.my); 1583 1584 // Destroy previous tooltip if overwrite is enabled, or skip element if not 1585 if($.data(this, 'qtip')) { 1586 if(config.overwrite) { 1587 elem.qtip('destroy'); 1588 } 1589 else if(config.overwrite === FALSE) { 1590 return FALSE; 1591 } 1592 } 1593 1594 // Remove title attribute and store it if present 1595 if(config.suppress && (title = $.attr(this, 'title'))) { 1596 $(this).removeAttr('title').attr(oldtitle, title); 1597 } 1598 1599 // Initialize the tooltip and add API reference 1600 obj = new QTip(elem, config, id, !!attr); 1601 $.data(this, 'qtip', obj); 1602 1603 // Catch remove events on target element to destroy redundant tooltip 1604 elem.bind('remove.qtip', function(){ obj.destroy(); }); 1605 1606 return obj; 1607} 1608 1609// jQuery $.fn extension method 1610QTIP = $.fn.qtip = function(options, notation, newValue) 1611{ 1612 var command = ('' + options).toLowerCase(), // Parse command 1613 returned = NULL, 1614 args = command === 'disable' ? [TRUE] : $.makeArray(arguments).slice(1), 1615 event = args[args.length - 1], 1616 opts = this[0] ? $.data(this[0], 'qtip') : NULL; 1617 1618 // Check for API request 1619 if((!arguments.length && opts) || command === 'api') { 1620 return opts; 1621 } 1622 1623 // Execute API command if present 1624 else if('string' === typeof options) 1625 { 1626 this.each(function() 1627 { 1628 var api = $.data(this, 'qtip'); 1629 if(!api) { return TRUE; } 1630 1631 // Cache the event if possible 1632 if(event && event.timeStamp) { api.cache.event = event; } 1633 1634 // Check for specific API commands 1635 if((command === 'option' || command === 'options') && notation) { 1636 if($.isPlainObject(notation) || newValue !== undefined) { 1637 api.set(notation, newValue); 1638 } 1639 else { 1640 returned = api.get(notation); 1641 return FALSE; 1642 } 1643 } 1644 1645 // Execute API command 1646 else if(api[command]) { 1647 api[command].apply(api[command], args); 1648 } 1649 }); 1650 1651 return returned !== NULL ? returned : this; 1652 } 1653 1654 // No API commands. validate provided options and setup qTips 1655 else if('object' === typeof options || !arguments.length) 1656 { 1657 opts = sanitizeOptions($.extend(TRUE, {}, options)); 1658 1659 // Bind the qTips 1660 return QTIP.bind.call(this, opts, event); 1661 } 1662}; 1663 1664// $.fn.qtip Bind method 1665QTIP.bind = function(opts, event) 1666{ 1667 return this.each(function(i) { 1668 var options, targets, events, namespace, api, id; 1669 1670 // Find next available ID, or use custom ID if provided 1671 id = $.isArray(opts.id) ? opts.id[i] : opts.id; 1672 id = !id || id === FALSE || id.length < 1 || usedIDs[id] ? QTIP.nextid++ : (usedIDs[id] = id); 1673 1674 // Setup events namespace 1675 namespace = '.qtip-'+id+'-create'; 1676 1677 // Initialize the qTip and re-grab newly sanitized options 1678 api = init.call(this, id, opts); 1679 if(api === FALSE) { return TRUE; } 1680 options = api.options; 1681 1682 // Initialize plugins 1683 $.each(PLUGINS, function() { 1684 if(this.initialize === 'initialize') { this(api); } 1685 }); 1686 1687 // Determine hide and show targets 1688 targets = { show: options.show.target, hide: options.hide.target }; 1689 events = { 1690 show: $.trim('' + options.show.event).replace(/ /g, namespace+' ') + namespace, 1691 hide: $.trim('' + options.hide.event).replace(/ /g, namespace+' ') + namespace 1692 }; 1693 1694 /* 1695 * Make sure hoverIntent functions properly by using mouseleave as a hide event if 1696 * mouseenter/mouseout is used for show.event, even if it isn't in the users options. 1697 */ 1698 if(/mouse(over|enter)/i.test(events.show) && !/mouse(out|leave)/i.test(events.hide)) { 1699 events.hide += ' mouseleave' + namespace; 1700 } 1701 1702 /*
1703 * Also make sure initial mouse targetting works correctly by caching mousemove coords 1704 * on show targets before the tooltip has rendered. 1705 */ 1706 targets.show.bind('mousemove'+namespace, function(event) { 1707 MOUSE = { pageX: event.pageX, pageY: event.pageY, type: 'mousemove' }; 1708 }); 1709 1710 // Define hoverIntent function 1711 function hoverIntent(event) { 1712 function render() { 1713 // Cache mouse coords,render and render the tooltip 1714 api.render(typeof event === 'object' || options.show.ready); 1715 1716 // Unbind show and hide events 1717 targets.show.add(targets.hide).unbind(namespace); 1718 } 1719 1720 // Only continue if tooltip isn't disabled 1721 if(api.cache.disabled) { return FALSE; } 1722 1723 // Cache the event data 1724 api.cache.event = $.extend({}, event); 1725 api.cache.target = event ? $(event.target) : [undefined]; 1726 1727 // Start the event sequence 1728 if(options.show.delay > 0) { 1729 clearTimeout(api.timers.show); 1730 api.timers.show = setTimeout(render, options.show.delay); 1731 if(events.show !== events.hide) { 1732 targets.hide.bind(events.hide, function() { clearTimeout(api.timers.show); }); 1733 } 1734 } 1735 else { render(); } 1736 } 1737 1738 // Bind show events to target 1739 targets.show.bind(events.show, hoverIntent); 1740 1741 // Prerendering is enabled, create tooltip now 1742 if(options.show.ready || options.prerender) { hoverIntent(event); } 1743 }); 1744}; 1745 1746// Setup base plugins 1747PLUGINS = QTIP.plugins = { 1748 // Corner object parser 1749 Corner: function(corner) { 1750 corner = ('' + corner).replace(/([A-Z])/, ' $1').replace(/middle/gi, 'center').toLowerCase(); 1751 this.x = (corner.match(/left|right/i) || corner.match(/center/) || ['inherit'])[0].toLowerCase(); 1752 this.y = (corner.match(/top|bottom|center/i) || ['inherit'])[0].toLowerCase(); 1753 1754 this.precedance = (corner.charAt(0).search(/^(t|b)/) > -1) ? 'y' : 'x'; 1755 this.string = function() { return this.precedance === 'y' ? this.y+this.x : this.x+this.y; }; 1756 this.abbreviation = function() { 1757 var x = this.x.substr(0,1), y = this.y.substr(0,1); 1758 return x === y ? x : (x === 'c' || (x !== 'c' && y !== 'c')) ? y + x : x + y; 1759 }; 1760 }, 1761 1762 // Custom (more correct for qTip!) offset calculator 1763 offset: function(elem, container, fixed) { 1764 var pos = elem.offset(), 1765 parent = container, 1766 deep = 0, 1767 docBody = document.body, 1768 coffset; 1769 1770 function scroll(e, i) { 1771 pos.left += i * e.scrollLeft(); 1772 pos.top += i * e.scrollTop(); 1773 } 1774 1775 if(parent) { 1776 // Compensate for non-static containers offset 1777 do { 1778 if(parent.css('position') !== 'static') { 1779 coffset = parent[0] === docBody ? 1780 { left: parseInt(parent.css('left'), 10) || 0, top: parseInt(parent.css('top'), 10) || 0 } : 1781 parent.position(); 1782 1783 pos.left -= coffset.left + (parseInt(parent.css('borderLeftWidth'), 10) || 0) + (parseInt(parent.css('marginLeft'), 10) || 0); 1784 pos.top -= coffset.top + (parseInt(parent.css('borderTopWidth'), 10) || 0); 1785 1786 deep++; 1787 } 1788 if(parent[0] === docBody) { break; } 1789 } 1790 while(parent = parent.offsetParent()); 1791 1792 // Compensate for containers scroll if it also has an offsetParent 1793 if(container[0] !== docBody && deep > 1) { scroll( container, 1 ); } 1794 1795 // Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2 - v4.0) 1796 if((PLUGINS.iOS < 4.1 && PLUGINS.iOS > 3.1) || (!PLUGINS.iOS && fixed)) { scroll( $(window), -1 ); } 1797 } 1798 1799 return pos; 1800 }, 1801 1802 /* 1803 * iOS 3.2 - 4.0 scroll fix detection used in offset() function. 1804 */ 1805 iOS: parseFloat( 1806 ('' + (/CPU.*OS ([0-9_]{1,3})|(CPU like).*AppleWebKit.*Mobile/i.exec(navigator.userAgent) || [0,''])[1]) 1807 .replace('undefined', '3_2').replace('_','.') 1808 ) || FALSE, 1809 1810 /* 1811 * jQuery-specific $.fn overrides 1812 */ 1813 fn: { 1814 /* Allow other plugins to successfully retrieve the title of an element with a qTip applied */ 1815 attr: function(attr, val) { 1816 if(this.length) { 1817 var self = this[0], 1818 title = 'title', 1819 api = $.data(self, 'qtip'); 1820 1821 if(attr === title && 'object' === typeof api && api.options.suppress) { 1822 if(arguments.length < 2) { 1823 return $.attr(self, oldtitle); 1824 } 1825 else { 1826 // If qTip is rendered and title was originally used as content, update it 1827 if(api && api.options.content.attr === title && api.cache.attr) { 1828 api.set('content.text', val); 1829 } 1830 1831 // Use the regular attr method to set, then cache the result 1832 return this.attr(oldtitle, val); 1833 } 1834 } 1835 } 1836 1837 return $.fn['attr'+replaceSuffix].apply(this, arguments); 1838 }, 1839 1840 /* Allow clone to correctly retrieve cached title attributes */ 1841 clone: function(keepData) { 1842 var titles = $([]), title = 'title', 1843 1844 // Clone our element using the real clone method 1845 elems = $.fn['clone'+replaceSuffix].apply(this, arguments); 1846 1847 // Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false 1848 if(!keepData) { 1849 elems.filter('['+oldtitle+']').attr('title', function() { 1850 return $.attr(this, oldtitle); 1851 }) 1852 .removeAttr(oldtitle); 1853 } 1854 1855 return elems; 1856 }, 1857 1858 /* 1859 * Taken directly from jQuery 1.8.2 widget source code 1860 * Trigger 'remove' event on all elements on removal if jQuery UI isn't present 1861 */ 1862 remove: $.ui ? NULL : function( selector, keepData ) { 1863 $(this).each(function() { 1864 if (!keepData) { 1865 if (!selector || $.filter( selector, [ this ] ).length) { 1866 $('*', this).add(this).each(function() { 1867 $(this).triggerHandler('remove'); 1868 }); 1869 } 1870 } 1871 }); 1872 } 1873 } 1874}; 1875 1876// Apply the fn overrides above 1877$.each(PLUGINS.fn, function(name, func) { 1878 if(!func) { return TRUE; } 1879 1880 var old = $.fn[name+replaceSuffix] = $.fn[name]; 1881 $.fn[name] = function() { 1882 return func.apply(this, arguments) || old.apply(this, arguments); 1883 }; 1884}); 1885 1886// Set global qTip properties 1887QTIP.version = '2.0.0pre'; 1888QTIP.nextid = 0; 1889QTIP.inactiveEvents = 'click dblclick mousedown mouseup mousemove mouseleave mouseenter'.split(' '); 1890QTIP.zindex = 15000; 1891 1892// Define configuration defaults 1893QTIP.defaults = { 1894 prerender: FALSE, 1895 id: FALSE, 1896 overwrite: TRUE, 1897 suppress: TRUE, 1898 content: { 1899 text: TRUE, 1900 attr: 'title', 1901 title: { 1902 text: FALSE, 1903 button: FALSE 1904 } 1905 }, 1906 position: {
1907 my: 'top left', 1908 at: 'bottom right', 1909 target: FALSE, 1910 container: FALSE, 1911 viewport: FALSE, 1912 adjust: { 1913 x: 0, y: 0, 1914 mouse: TRUE, 1915 resize: TRUE, 1916 method: 'flip flip' 1917 }, 1918 effect: function(api, pos, viewport) { 1919 $(this).animate(pos, { 1920 duration: 200, 1921 queue: FALSE 1922 }); 1923 } 1924 }, 1925 show: { 1926 target: FALSE, 1927 event: 'mouseenter', 1928 effect: TRUE, 1929 delay: 90, 1930 solo: FALSE, 1931 ready: FALSE, 1932 autofocus: FALSE 1933 }, 1934 hide: { 1935 target: FALSE, 1936 event: 'mouseleave', 1937 effect: TRUE, 1938 delay: 0, 1939 fixed: FALSE, 1940 inactive: FALSE, 1941 leave: 'window', 1942 distance: FALSE 1943 }, 1944 style: { 1945 classes: '', 1946 widget: FALSE, 1947 width: FALSE, 1948 height: FALSE 1949 }, 1950 events: { 1951 render: NULL, 1952 move: NULL, 1953 show: NULL, 1954 hide: NULL, 1955 toggle: NULL, 1956 visible: NULL, 1957 focus: NULL, 1958 blur: NULL 1959 } 1960}; 1961 1962function Ajax(api) 1963{ 1964 var self = this, 1965 tooltip = api.elements.tooltip, 1966 opts = api.options.content.ajax, 1967 namespace = '.qtip-ajax', 1968 rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, 1969 first = TRUE; 1970 1971 api.checks.ajax = { 1972 '^content.ajax': function(obj, name, v) { 1973 // If content.ajax object was reset, set our local var 1974 if(name === 'ajax') { opts = v; } 1975 1976 if(name === 'once') { 1977 self.init(); 1978 } 1979 else if(opts && opts.url) { 1980 self.load(); 1981 } 1982 else { 1983 tooltip.unbind(namespace); 1984 } 1985 } 1986 }; 1987 1988 $.extend(self, { 1989 init: function() 1990 { 1991 // Make sure ajax options are enabled and bind event 1992 if(opts && opts.url) { 1993 tooltip.unbind(namespace)[ opts.once ? 'one' : 'bind' ]('tooltipshow'+namespace, self.load); 1994 } 1995 1996 return self; 1997 }, 1998 1999 load: function(event, first) 2000 { 2001 // Make sure default event hasn't been prevented 2002 if(event && event.isDefaultPrevented()) { return self; } 2003 2004 var hasSelector = opts.url.indexOf(' '), 2005 url = opts.url, 2006 selector, 2007 hideFirst = opts.once && !opts.loading && first; 2008 2009 // If loading option is disabled, hide the tooltip until content is retrieved (first time only) 2010 if(hideFirst) { tooltip.css('visibility', 'hidden'); } 2011 2012 // Check if user delcared a content selector like in .load() 2013 if(hasSelector > -1) { 2014 selector = url.substr(hasSelector); 2015 url = url.substr(0, hasSelector); 2016 } 2017 2018 // Define common after callback for both success/error handlers 2019 function after() { 2020 // Re-display tip if loading and first time, and reset first flag 2021 if(hideFirst) { tooltip.css('visibility', ''); first = FALSE; } 2022 2023 // Call users complete if it was defined 2024 if($.isFunction(opts.complete)) { opts.complete.apply(this, arguments); } 2025 } 2026 2027 // Define success handler 2028 function successHandler(content) { 2029 if(selector) { 2030 // Create a dummy div to hold the results and grab the selector element 2031 content = $('<div/>') 2032 // inject the contents of the document in, removing the scripts 2033 // to avoid any 'Permission Denied' errors in IE 2034 .append(content.replace(rscript, "")) 2035 2036 // Locate the specified elements 2037 .find(selector); 2038 } 2039 2040 // Set the content 2041 api.set('content.text', content); 2042 } 2043 2044 // Error handler 2045 function errorHandler(xh, status, error){ api.set('content.text', status + ': ' + error); } 2046 2047 // Setup $.ajax option object and process the request 2048 $.ajax( $.extend({ success: successHandler, error: errorHandler, context: api }, opts, { url: url, complete: after }) ); 2049 2050 return self; 2051 } 2052 }); 2053 2054 self.init(); 2055} 2056 2057 2058PLUGINS.ajax = function(api) 2059{ 2060 var self = api.plugins.ajax; 2061 2062 return 'object' === typeof self ? self : (api.plugins.ajax = new Ajax(api)); 2063}; 2064 2065PLUGINS.ajax.initialize = 'render'; 2066 2067// Setup plugin sanitization 2068PLUGINS.ajax.sanitize = function(options) 2069{ 2070 var content = options.content, opts; 2071 if(content && 'ajax' in content) { 2072 opts = content.ajax; 2073 if(typeof opts !== 'object') { opts = options.content.ajax = { url: opts }; } 2074 if('boolean' !== typeof opts.once && opts.once) { opts.once = !!opts.once; } 2075 } 2076}; 2077 2078// Extend original api defaults 2079$.extend(TRUE, QTIP.defaults, { 2080 content: { 2081 ajax: { 2082 loading: TRUE, 2083 once: TRUE 2084 } 2085 } 2086}); 2087 2088/* 2089 * BGIFrame adaption (http://plugins.jquery.com/project/bgiframe) 2090 * Special thanks to Brandon Aaron 2091 */ 2092function BGIFrame(api) 2093{ 2094 var self = this, 2095 elems = api.elements, 2096 tooltip = elems.tooltip, 2097 namespace = '.bgiframe-' + api.id; 2098 2099 $.extend(self, { 2100 init: function() 2101 { 2102 // Create the BGIFrame element 2103 elems.bgiframe = $('<iframe class="ui-tooltip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" ' + 2104 ' style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); ' + 2105 '-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>'); 2106 2107 // Append the new element to the tooltip 2108 elems.bgiframe.appendTo(tooltip); 2109 2110 // Update BGIFrame on tooltip move 2111 tooltip.bind('tooltipmove'+namespace, self.adjust); 2112 }, 2113 2114 adjust: function() 2115 { 2116 var dimensions = api.get('dimensions'), // Determine current tooltip dimensions 2117 plugin = api.plugins.tip, 2118 tip = elems.tip, 2119 tipAdjust, offset; 2120 2121 // Adjust border offset 2122 offset = parseInt(tooltip.css('border-left-width'), 10) || 0; 2123 offset = { left: -offset, top: -offset }; 2124 2125 // Adjust for tips plugin 2126 if(plugin && tip) { 2127 tipAdjust = (plugin.corner.precedance === 'x') ? ['width', 'left'] : ['height', 'top']; 2128 offset[ tipAdjust[1] ] -= tip[ tipAdjust[0] ](); 2129 } 2130 2131 // Update bgiframe 2132 elems.bgiframe.css(offset).css(dimensions); 2133 }, 2134 2135 destroy: function() 2136 { 2137 // Remove iframe 2138 elems.bgiframe.remove(); 2139 2140 // Remove bound events 2141 tooltip.unbind(namespace); 2142 } 2143 }); 2144 2145 self.init(); 2146} 2147 2148PLUGINS.bgiframe = function(api) 2149{ 2150 var browser = $.browser, 2151 self = api.plugins.bgiframe; 2152 2153 // Proceed only if the browser is IE6 and offending elements are present 2154 if($('select, object').length < 1 || !(browser.msie && browser.version.charAt(0) === '6')) { 2155 return FALSE; 2156 } 2157 2158 return 'object' === typeof self ? self : (api.plugins.bgiframe = new BGIFrame(api)); 2159}; 2160 2161// Plugin needs to be initialized on render 2162PLUGINS.bgiframe.initialize = 'render'; 2163 2164PLUGINS.imagemap = function(area, corner) 2165{ 2166 if(!area.jquery) { area = $(area); } 2167 2168 var shape = area.attr('shape').toLowerCase(), 2169 baseCoords = area.attr('coords').split(','), 2170 coords = [], 2171 image = $('img[usemap="#'+area.parent('map').attr('name')+'"]'), 2172 imageOffset = image.offset(), 2173 result = { 2174 width: 0, height: 0, 2175 offset: { top: 1e10, right: 0, bottom: 0, left: 1e10 } 2176 }, 2177 i = 0, next = 0; 2178 2179 // POLY area coordinate calculator 2180 // Special thanks to Ed Cradock for helping out with this. 2181 // Uses a binary search algorithm to find suitable coordinates. 2182 function polyCoordinates(result, coords) 2183 { 2184 var i = 0, 2185 compareX = 1, compareY = 1, 2186 realX = 0, realY = 0, 2187 newWidth = result.width, 2188 newHeight = result.height; 2189 2190 // Use a binary search algorithm to locate most suitable coordinate (hopefully) 2191 while(newWidth > 0 && newHeight > 0 && compareX > 0 && compareY > 0) 2192 { 2193 newWidth = Math.floor(newWidth / 2); 2194 newHeight = Math.floor(newHeight / 2); 2195 2196 if(corner.x === 'left'){ compareX = newWidth; } 2197 else if(corner.x === 'right'){ compareX = result.width - newWidth; } 2198 else{ compareX += Math.floor(newWidth / 2); } 2199 2200 if(corner.y === 'top'){ compareY = newHeight; } 2201 else if(corner.y === 'bottom'){ compareY = result.height - newHeight; } 2202 else{ compareY += Math.floor(newHeight / 2); } 2203
2204 i = coords.length; while(i--) 2205 { 2206 if(coords.length < 2){ break; } 2207 2208 realX = coords[i][0] - result.offset.left; 2209 realY = coords[i][1] - result.offset.top; 2210 2211 if((corner.x === 'left' && realX >= compareX) || 2212 (corner.x === 'right' && realX <= compareX) || 2213 (corner.x === 'center' && (realX < compareX || realX > (result.width - compareX))) || 2214 (corner.y === 'top' && realY >= compareY) || 2215 (corner.y === 'bottom' && realY <= compareY) || 2216 (corner.y === 'center' && (realY < compareY || realY > (result.height - compareY)))) { 2217 coords.splice(i, 1); 2218 } 2219 } 2220 } 2221 2222 return { left: coords[0][0], top: coords[0][1] }; 2223 } 2224 2225 // Make sure we account for padding and borders on the image 2226 imageOffset.left += Math.ceil((image.outerWidth() - image.width()) / 2); 2227 imageOffset.top += Math.ceil((image.outerHeight() - image.height()) / 2); 2228 2229 // Parse coordinates into proper array 2230 if(shape === 'poly') { 2231 i = baseCoords.length; while(i--) 2232 { 2233 next = [ parseInt(baseCoords[--i], 10), parseInt(baseCoords[i+1], 10) ]; 2234 2235 if(next[0] > result.offset.right){ result.offset.right = next[0]; } 2236 if(next[0] < result.offset.left){ result.offset.left = next[0]; } 2237 if(next[1] > result.offset.bottom){ result.offset.bottom = next[1]; } 2238 if(next[1] < result.offset.top){ result.offset.top = next[1]; } 2239 2240 coords.push(next); 2241 } 2242 } 2243 else { 2244 coords = $.map(baseCoords, function(coord){ return parseInt(coord, 10); }); 2245 } 2246 2247 // Calculate details 2248 switch(shape) 2249 { 2250 case 'rect': 2251 result = { 2252 width: Math.abs(coords[2] - coords[0]), 2253 height: Math.abs(coords[3] - coords[1]), 2254 offset: { left: coords[0], top: coords[1] } 2255 }; 2256 break; 2257 2258 case 'circle': 2259 result = { 2260 width: coords[2] + 2, 2261 height: coords[2] + 2, 2262 offset: { left: coords[0], top: coords[1] } 2263 }; 2264 break; 2265 2266 case 'poly': 2267 $.extend(result, { 2268 width: Math.abs(result.offset.right - result.offset.left), 2269 height: Math.abs(result.offset.bottom - result.offset.top) 2270 }); 2271 2272 if(corner.string() === 'centercenter') { 2273 result.offset = { 2274 left: result.offset.left + (result.width / 2), 2275 top: result.offset.top + (result.height / 2) 2276 }; 2277 } 2278 else { 2279 result.offset = polyCoordinates(result, coords.slice()); 2280 } 2281 2282 result.width = result.height = 0; 2283 break; 2284 } 2285 2286 // Add image position to offset coordinates 2287 result.offset.left += imageOffset.left; 2288 result.offset.top += imageOffset.top; 2289 2290 return result; 2291}; 2292 2293function Modal(api) 2294{ 2295 var self = this, 2296 options = api.options.show.modal, 2297 elems = api.elements, 2298 tooltip = elems.tooltip, 2299 overlaySelector = '#qtip-overlay', 2300 globalNamespace = '.qtipmodal', 2301 namespace = globalNamespace + api.id, 2302 attr = 'is-modal-qtip', 2303 docBody = $(document.body), 2304 overlay; 2305 2306 // Setup option set checks 2307 api.checks.modal = { 2308 '^show.modal.(on|blur)$': function() { 2309 // Initialise 2310 self.init(); 2311 2312 // Show the modal if not visible already and tooltip is visible 2313 elems.overlay.toggle( tooltip.is(':visible') ); 2314 } 2315 }; 2316 2317 $.extend(self, { 2318 init: function() 2319 { 2320 // If modal is disabled... return 2321 if(!options.on) { return self; } 2322 2323 // Create the overlay if needed 2324 overlay = self.create(); 2325 2326 // Add unique attribute so we can grab modal tooltips easily via a selector 2327 tooltip.attr(attr, TRUE) 2328 2329 .css('z-index', PLUGINS.modal.zindex + $(selector+'['+attr+']').length) 2330 2331 // Remove previous bound events in globalNamespace 2332 .unbind(globalNamespace).unbind(namespace) 2333 2334 // Apply our show/hide/focus modal events 2335 .bind('tooltipshow'+globalNamespace+' tooltiphide'+globalNamespace, function(event, api, duration) { 2336 var oEvent = event.originalEvent; 2337 2338 // Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop 2339 if(oEvent && event.type === 'tooltiphide' && /mouse(leave|enter)/.test(oEvent.type) && $(oEvent.relatedTarget).closest(overlay[0]).length) { 2340 event.preventDefault(); 2341 } 2342 else { 2343 self[ event.type.replace('tooltip', '') ](event, duration); 2344 } 2345 }) 2346 2347 // Adjust modal z-index on tooltip focus 2348 .bind('tooltipfocus'+globalNamespace, function(event) { 2349 // If focus was cancelled before it reearch us, don't do anything 2350 if(event.isDefaultPrevented()) { return; } 2351 2352 var qtips = $(selector).filter('['+attr+']'), 2353
2354 // Keep the modal's lower than other, regular qtips 2355 newIndex = PLUGINS.modal.zindex + qtips.length, 2356 curIndex = parseInt(tooltip[0].style.zIndex, 10); 2357 2358 // Set overlay z-index 2359 overlay[0].style.zIndex = newIndex; 2360 2361 // Reduce modal z-index's and keep them properly ordered 2362 qtips.each(function() { 2363 if(this.style.zIndex > curIndex) { 2364 this.style.zIndex -= 1; 2365 } 2366 }); 2367 2368 // Fire blur event for focused tooltip 2369 qtips.end().filter('.' + focusClass).qtip('blur', event.originalEvent); 2370 2371 // Set the new z-index 2372 tooltip.addClass(focusClass)[0].style.zIndex = newIndex; 2373 2374 // Prevent default handling 2375 event.preventDefault(); 2376 }) 2377 2378 // Focus any other visible modals when this one hides 2379 .bind('tooltiphide'+globalNamespace, function(event) { 2380 $('[' + attr + ']').filter(':visible').not(tooltip).last().qtip('focus', event); 2381 }); 2382 2383 // Apply keyboard "Escape key" close handler 2384 if(options.escape) { 2385 $(window).unbind(namespace).bind('keydown'+namespace, function(event) { 2386 if(event.keyCode === 27 && tooltip.hasClass(focusClass)) { 2387 api.hide(event); 2388 } 2389 }); 2390 } 2391 2392 // Apply click handler for blur option 2393 if(options.blur) { 2394 elems.overlay.unbind(namespace).bind('click'+namespace, function(event) { 2395 if(tooltip.hasClass(focusClass)) { api.hide(event); } 2396 }); 2397 } 2398 2399 return self; 2400 }, 2401 2402 create: function() 2403 { 2404 var elem = $(overlaySelector); 2405 2406 // Return if overlay is already rendered 2407 if(elem.length) { elems.overlay = elem; return elem; } 2408 2409 // Create document overlay 2410 overlay = elems.overlay = $('<div />', { 2411 id: overlaySelector.substr(1), 2412 html: '<div></div>', 2413 mousedown: function() { return FALSE; } 2414 }) 2415 .insertBefore( $(selector).first() ); 2416 2417 // Update position on window resize or scroll 2418 $(window).unbind(globalNamespace).bind('resize'+globalNamespace, function() { 2419 overlay.css({ 2420 height: $(window).height(), 2421 width: $(window).width() 2422 }); 2423 }) 2424 .triggerHandler('resize'); 2425 2426 return overlay; 2427 }, 2428 2429 toggle: function(event, state, duration) 2430 { 2431 // Make sure default event hasn't been prevented 2432 if(event && event.isDefaultPrevented()) { return self; } 2433 2434 var effect = options.effect, 2435 type = state ? 'show': 'hide', 2436 visible = overlay.is(':visible'), 2437 modals = $('[' + attr + ']').filter(':visible').not(tooltip), 2438 zindex; 2439 2440 // Create our overlay if it isn't present already 2441 if(!overlay) { overlay = self.create(); } 2442 2443 // Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible 2444 if((overlay.is(':animated') && visible === state) || (!state && modals.length)) { return self; } 2445 2446 // State specific... 2447 if(state) { 2448 // Set position 2449 overlay.css({ left: 0, top: 0 }); 2450 2451 // Toggle backdrop cursor style on show 2452 overlay.toggleClass('blurs', options.blur); 2453 2454 // Make sure we can't focus anything outside the tooltip 2455 docBody.delegate('*', 'focusin'+namespace, function(event) { 2456 if($(event.target).closest(selector)[0] !== tooltip[0]) { 2457 $('a, :input, img', tooltip).add(tooltip).focus(); 2458 } 2459 }); 2460 } 2461 else { 2462 // Undelegate focus handler 2463 docBody.undelegate('*', 'focusin'+namespace); 2464 } 2465 2466 // Stop all animations 2467 overlay.stop(TRUE, FALSE); 2468 2469 // Use custom function if provided 2470 if($.isFunction(effect)) { 2471 effect.call(overlay, state); 2472 } 2473 2474 // If no effect type is supplied, use a simple toggle 2475 else if(effect === FALSE) { 2476 overlay[ type ](); 2477 } 2478 2479 // Use basic fade function 2480 else { 2481 overlay.fadeTo( parseInt(duration, 10) || 90, state ? 1 : 0, function() { 2482 if(!state) { $(this).hide(); } 2483 }); 2484 } 2485 2486 // Reset position on hide 2487 if(!state) { 2488 overlay.queue(function(next) { 2489 overlay.css({ left: '', top: '' }); 2490 next(); 2491 }); 2492 } 2493 2494 return self; 2495 }, 2496 2497 show: function(event, duration) { return self.toggle(event, TRUE, duration); }, 2498 hide: function(event, duration) { return self.toggle(event, FALSE, duration); }, 2499 2500 destroy: function() 2501 { 2502 var delBlanket = overlay; 2503 2504 if(delBlanket) { 2505 // Check if any other modal tooltips are present 2506 delBlanket = $('[' + attr + ']').not(tooltip).length < 1; 2507 2508 // Remove overlay if needed 2509 if(delBlanket) { 2510 elems.overlay.remove(); 2511 $(window).unbind(globalNamespace); 2512 } 2513 else { 2514 elems.overlay.unbind(globalNamespace+api.id); 2515 } 2516 2517 // Undelegate focus handler 2518 docBody.undelegate('*', 'focusin'+namespace); 2519 } 2520 2521 // Remove bound events 2522 return tooltip.removeAttr(attr).unbind(globalNamespace); 2523 } 2524 }); 2525 2526 self.init(); 2527} 2528 2529PLUGINS.modal = function(api) { 2530 var self = api.plugins.modal; 2531 2532 return 'object' === typeof self ? self : (api.plugins.modal = new Modal(api)); 2533}; 2534 2535// Plugin needs to be initialized on render 2536PLUGINS.modal.initialize = 'render'; 2537 2538// Setup sanitiztion rules 2539PLUGINS.modal.sanitize = function(opts) { 2540 if(opts.show) { 2541 if(typeof opts.show.modal !== 'object') { opts.show.modal = { on: !!opts.show.modal }; } 2542 else if(typeof opts.show.modal.on === 'undefined') { opts.show.modal.on = TRUE; } 2543 } 2544}; 2545 2546// Base z-index for all modal tooltips (use qTip core z-index as a base) 2547PLUGINS.modal.zindex = QTIP.zindex -= 200; 2548 2549// Extend original api defaults 2550$.extend(TRUE, QTIP.defaults, { 2551 show: { 2552 modal: { 2553 on: FALSE, 2554 effect: TRUE, 2555 blur: TRUE, 2556 escape: TRUE 2557 } 2558 } 2559}); 2560 2561PLUGINS.svg = function(svg, corner) 2562{ 2563 var doc = $(document), 2564 elem = svg[0], 2565 result = { 2566 width: 0, height: 0, 2567 offset: { top: 1e10, left: 1e10 } 2568 }, 2569 box, mtx, root, point, tPoint; 2570 2571 if (elem.getBBox && elem.parentNode) { 2572 box = elem.getBBox(); 2573 mtx = elem.getScreenCTM(); 2574 root = elem.farthestViewportElement || elem; 2575 2576 // Return if no method is found 2577 if(!root.createSVGPoint) { return result; } 2578 2579 // Create our point var 2580 point = root.createSVGPoint(); 2581 2582 // Adjust top and left 2583 point.x = box.x; 2584 point.y = box.y; 2585 tPoint = point.matrixTransform(mtx); 2586 result.offset.left = tPoint.x; 2587 result.offset.top = tPoint.y; 2588 2589 // Adjust width and height 2590 point.x += box.width; 2591 point.y += box.height; 2592 tPoint = point.matrixTransform(mtx); 2593 result.width = tPoint.x - result.offset.left; 2594 result.height = tPoint.y - result.offset.top; 2595 2596 // Adjust by scroll offset 2597 result.offset.left += doc.scrollLeft(); 2598 result.offset.top += doc.scrollTop(); 2599 } 2600 2601 return result; 2602}; 2603 2604// Tip coordinates calculator 2605function calculateTip(corner, width, height) 2606{
2607 var width2 = Math.ceil(width / 2), height2 = Math.ceil(height / 2), 2608 2609 // Define tip coordinates in terms of height and width values 2610 tips = { 2611 bottomright: [[0,0], [width,height], [width,0]], 2612 bottomleft: [[0,0], [width,0], [0,height]], 2613 topright: [[0,height], [width,0], [width,height]], 2614 topleft: [[0,0], [0,height], [width,height]], 2615 topcenter: [[0,height], [width2,0], [width,height]], 2616 bottomcenter: [[0,0], [width,0], [width2,height]], 2617 rightcenter: [[0,0], [width,height2], [0,height]], 2618 leftcenter: [[width,0], [width,height], [0,height2]] 2619 }; 2620 2621 // Set common side shapes 2622 tips.lefttop = tips.bottomright; tips.righttop = tips.bottomleft; 2623 tips.leftbottom = tips.topright; tips.rightbottom = tips.topleft; 2624 2625 return tips[ corner.string() ]; 2626} 2627 2628 2629function Tip(qTip, command) 2630{ 2631 var self = this, 2632 opts = qTip.options.style.tip, 2633 elems = qTip.elements, 2634 tooltip = elems.tooltip, 2635 cache = { 2636 top: 0, 2637 left: 0, 2638 corner: '' 2639 }, 2640 size = { 2641 width: opts.width, 2642 height: opts.height 2643 }, 2644 color = { }, 2645 border = opts.border || 0, 2646 namespace = '.qtip-tip', 2647 hasCanvas = !!($('<canvas />')[0] || {}).getContext; 2648 2649 self.corner = NULL; 2650 self.mimic = NULL; 2651 self.border = border; 2652 self.offset = opts.offset; 2653 self.size = size; 2654 2655 // Add new option checks for the plugin 2656 qTip.checks.tip = { 2657 '^position.my|style.tip.(corner|mimic|border)$': function() { 2658 // Make sure a tip can be drawn 2659 if(!self.init()) { 2660 self.destroy(); 2661 } 2662 2663 // Reposition the tooltip 2664 qTip.reposition(); 2665 }, 2666 '^style.tip.(height|width)$': function() { 2667 // Re-set dimensions and redraw the tip 2668 size = { 2669 width: opts.width, 2670 height: opts.height 2671 }; 2672 self.create(); 2673 self.update(); 2674 2675 // Reposition the tooltip 2676 qTip.reposition(); 2677 }, 2678 '^content.title.text|style.(classes|widget)$': function() { 2679 if(elems.tip) { 2680 self.update(); 2681 } 2682 } 2683 }; 2684 2685 function reposition(event, api, pos, viewport) { 2686 if(!elems.tip) { return; } 2687 2688 var newCorner = $.extend({}, self.corner), 2689 adjust = pos.adjusted, 2690 method = qTip.options.position.adjust.method.split(' '), 2691 horizontal = method[0], 2692 vertical = method[1] || method[0], 2693 shift = { left: FALSE, top: FALSE, x: 0, y: 0 }, 2694 offset, css = {}, props; 2695 2696 // Make sure our tip position isn't fixed e.g. doesn't adjust with viewport 2697 if(self.corner.fixed !== TRUE) { 2698 // Horizontal - Shift or flip method 2699 if(horizontal === 'shift' && newCorner.precedance === 'x' && adjust.left && newCorner.y !== 'center') { 2700 newCorner.precedance = newCorner.precedance === 'x' ? 'y' : 'x'; 2701 } 2702 else if(horizontal === 'flip' && adjust.left){ 2703 newCorner.x = newCorner.x === 'center' ? (adjust.left > 0 ? 'left' : 'right') : (newCorner.x === 'left' ? 'right' : 'left'); 2704 } 2705 2706 // Vertical - Shift or flip method 2707 if(vertical === 'shift' && newCorner.precedance === 'y' && adjust.top && newCorner.x !== 'center') { 2708 newCorner.precedance = newCorner.precedance === 'y' ? 'x' : 'y'; 2709 } 2710 else if(vertical === 'flip' && adjust.top) { 2711 newCorner.y = newCorner.y === 'center' ? (adjust.top > 0 ? 'top' : 'bottom') : (newCorner.y === 'top' ? 'bottom' : 'top'); 2712 } 2713 2714 // Update and redraw the tip if needed (check cached details of last drawn tip) 2715 if(newCorner.string() !== cache.corner && (cache.top !== adjust.top || cache.left !== adjust.left)) { 2716 self.update(newCorner, FALSE); 2717 } 2718 } 2719 2720 // Setup tip offset properties 2721 offset = self.position(newCorner, adjust); 2722 if(offset.right !== undefined) { offset.left = -offset.right; } 2723 if(offset.bottom !== undefined) { offset.top = -offset.bottom; } 2724 offset.user = Math.max(0, opts.offset); 2725 2726 // Viewport "shift" specific adjustments 2727 if(shift.left = (horizontal === 'shift' && !!adjust.left)) { 2728 if(newCorner.x === 'center') { 2729 css['margin-left'] = shift.x = offset['margin-left'] - adjust.left; 2730 } 2731 else { 2732 props = offset.right !== undefined ? 2733 [ adjust.left, -offset.left ] : [ -adjust.left, offset.left ]; 2734 2735 if( (shift.x = Math.max(props[0], props[1])) > props[0] ) { 2736 pos.left -= adjust.left; 2737 shift.left = FALSE; 2738 } 2739 2740 css[ offset.right !== undefined ? 'right' : 'left' ] = shift.x; 2741 } 2742 } 2743 if(shift.top = (vertical === 'shift' && !!adjust.top)) { 2744 if(newCorner.y === 'center') { 2745 css['margin-top'] = shift.y = offset['margin-top'] - adjust.top; 2746 } 2747 else { 2748 props = offset.bottom !== undefined ? 2749 [ adjust.top, -offset.top ] : [ -adjust.top, offset.top ]; 2750 2751 if( (shift.y = Math.max(props[0], props[1])) > props[0] ) { 2752 pos.top -= adjust.top; 2753 shift.top = FALSE; 2754 } 2755 2756 css[ offset.bottom !== undefined ? 'bottom' : 'top' ] = shift.y; 2757 } 2758 } 2759 2760 /*
2761 * If the tip is adjusted in both dimensions, or in a 2762 * direction that would cause it to be anywhere but the 2763 * outer border, hide it! 2764 */ 2765 elems.tip.css(css).toggle( 2766 !((shift.x && shift.y) || (newCorner.x === 'center' && shift.y) || (newCorner.y === 'center' && shift.x)) 2767 ); 2768 2769 // Adjust position to accomodate tip dimensions 2770 pos.left -= offset.left.charAt ? offset.user : horizontal !== 'shift' || shift.top || !shift.left && !shift.top ? offset.left : 0; 2771 pos.top -= offset.top.charAt ? offset.user : vertical !== 'shift' || shift.left || !shift.left && !shift.top ? offset.top : 0; 2772 2773 // Cache details 2774 cache.left = adjust.left; cache.top = adjust.top; 2775 cache.corner = newCorner.string(); 2776 } 2777 2778 /* border width calculator */ 2779 function borderWidth(corner, side, backup) { 2780 side = !side ? corner[corner.precedance] : side; 2781 2782 var isFluid = tooltip.hasClass(fluidClass), 2783 isTitleTop = elems.titlebar && corner.y === 'top', 2784 elem = isTitleTop ? elems.titlebar : elems.content, 2785 css = 'border-' + side + '-width', 2786 val; 2787 2788 // Grab the border-width value (add fluid class if needed) 2789 tooltip.addClass(fluidClass); 2790 val = parseInt(elem.css(css), 10); 2791 val = (backup ? val || parseInt(tooltip.css(css), 10) : val) || 0; 2792 tooltip.toggleClass(fluidClass, isFluid); 2793 2794 return val; 2795 } 2796 2797 function borderRadius(corner) { 2798 var isTitleTop = elems.titlebar && corner.y === 'top', 2799 elem = isTitleTop ? elems.titlebar : elems.content, 2800 moz = $.browser.mozilla, 2801 prefix = moz ? '-moz-' : $.browser.webkit ? '-webkit-' : '', 2802 side = corner.y + (moz ? '' : '-') + corner.x, 2803 css = prefix + (moz ? 'border-radius-' + side : 'border-' + side + '-radius'); 2804 2805 return parseInt(elem.css(css), 10) || parseInt(tooltip.css(css), 10) || 0; 2806 } 2807 2808 function calculateSize(corner) { 2809 var y = corner.precedance === 'y', 2810 width = size [ y ? 'width' : 'height' ], 2811 height = size [ y ? 'height' : 'width' ], 2812 isCenter = corner.string().indexOf('center') > -1, 2813 base = width * (isCenter ? 0.5 : 1), 2814 pow = Math.pow, 2815 round = Math.round, 2816 bigHyp, ratio, result, 2817 2818 smallHyp = Math.sqrt( pow(base, 2) + pow(height, 2) ), 2819 2820 hyp = [ 2821 (border / base) * smallHyp, (border / height) * smallHyp 2822 ]; 2823 hyp[2] = Math.sqrt( pow(hyp[0], 2) - pow(border, 2) ); 2824 hyp[3] = Math.sqrt( pow(hyp[1], 2) - pow(border, 2) ); 2825 2826 bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]); 2827 ratio = bigHyp / smallHyp; 2828 2829 result = [ round(ratio * height), round(ratio * width) ]; 2830 return { height: result[ y ? 0 : 1 ], width: result[ y ? 1 : 0 ] }; 2831 } 2832 2833 $.extend(self, { 2834 init: function() 2835 { 2836 var enabled = self.detectCorner() && (hasCanvas || $.browser.msie); 2837 2838 // Determine tip corner and type 2839 if(enabled) { 2840 // Create a new tip and draw it 2841 self.create(); 2842 self.update(); 2843 2844 // Bind update events 2845 tooltip.unbind(namespace).bind('tooltipmove'+namespace, reposition); 2846 } 2847 2848 return enabled; 2849 }, 2850 2851 detectCorner: function() 2852 { 2853 var corner = opts.corner, 2854 posOptions = qTip.options.position, 2855 at = posOptions.at, 2856 my = posOptions.my.string ? posOptions.my.string() : posOptions.my; 2857 2858 // Detect corner and mimic properties 2859 if(corner === FALSE || (my === FALSE && at === FALSE)) { 2860 return FALSE; 2861 } 2862 else { 2863 if(corner === TRUE) { 2864 self.corner = new PLUGINS.Corner(my); 2865 } 2866 else if(!corner.string) { 2867 self.corner = new PLUGINS.Corner(corner); 2868 self.corner.fixed = TRUE; 2869 } 2870 } 2871 2872 return self.corner.string() !== 'centercenter'; 2873 }, 2874 2875 detectColours: function() { 2876 var i, fill, border, 2877 tip = elems.tip.css({ backgroundColor: '', border: '' }), 2878 corner = self.corner, 2879 precedance = corner[ corner.precedance ], 2880 2881 borderSide = 'border-' + precedance + '-color', 2882 borderSideCamel = 'border' + precedance.charAt(0) + precedance.substr(1) + 'Color', 2883 2884 invalid = /rgba?\(0, 0, 0(, 0)?\)|transparent/i, 2885 backgroundColor = 'background-color', 2886 transparent = 'transparent', 2887 2888 bodyBorder = $(document.body).css('color'), 2889 contentColour = qTip.elements.content.css('color'), 2890 2891 useTitle = elems.titlebar && (corner.y === 'top' || (corner.y === 'center' && tip.position().top + (size.height / 2) + opts.offset < elems.titlebar.outerHeight(1))), 2892 colorElem = useTitle ? elems.titlebar : elems.content; 2893 2894 // Apply the fluid class so we can see our CSS values properly 2895 tooltip.addClass(fluidClass); 2896 2897 // Detect tip colours from CSS styles 2898 color.fill = fill = tip.css(backgroundColor); 2899 color.border = border = tip[0].style[ borderSideCamel ] || tip.css(borderSide) || tooltip.css(borderSide); 2900 2901 // Make sure colours are valid 2902 if(!fill || invalid.test(fill)) { 2903 color.fill = colorElem.css(backgroundColor) || transparent; 2904 if(invalid.test(color.fill)) { 2905 color.fill = tooltip.css(backgroundColor) || fill; 2906 } 2907 } 2908 if(!border || invalid.test(border) || border === bodyBorder) { 2909 color.border = colorElem.css(borderSide) || transparent; 2910 if(invalid.test(color.border) || color.border === contentColour) { 2911 color.border = border; 2912 } 2913 } 2914 2915 // Reset background and border colours 2916 $('*', tip).add(tip).css(backgroundColor, transparent).css('border', ''); 2917 2918 // Remove fluid class 2919 tooltip.removeClass(fluidClass); 2920 }, 2921 2922 create: function() 2923 { 2924 var width = size.width, 2925 height = size.height, 2926 vml; 2927 2928 // Remove previous tip element if present 2929 if(elems.tip) { elems.tip.remove(); } 2930 2931 // Create tip element and prepend to the tooltip 2932 elems.tip = $('<div />', { 'class': 'ui-tooltip-tip' }).css({ width: width, height: height }).prependTo(tooltip); 2933
2934 // Create tip drawing element(s) 2935 if(hasCanvas) { 2936 // save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()! 2937 $('<canvas />').appendTo(elems.tip)[0].getContext('2d').save(); 2938 } 2939 else { 2940 vml = '<vml:shape coordorigin="0,0" style="display:inline-block; position:absolute; behavior:url(#default#VML);"></vml:shape>'; 2941 elems.tip.html(vml + vml); 2942 } 2943 }, 2944 2945 update: function(corner, position) 2946 { 2947 var tip = elems.tip, 2948 inner = tip.children(), 2949 width = size.width, 2950 height = size.height, 2951 regular = 'px solid ', 2952 transparent = 'px dashed transparent', // Dashed IE6 border-transparency hack. Awesome! 2953 mimic = opts.mimic, 2954 round = Math.round, 2955 precedance, context, coords, translate, newSize; 2956 2957 // Re-determine tip if not already set 2958 if(!corner) { corner = self.corner; } 2959 2960 // Use corner property if we detect an invalid mimic value 2961 if(mimic === FALSE) { mimic = corner; } 2962 2963 // Otherwise inherit mimic properties from the corner object as necessary 2964 else { 2965 mimic = new PLUGINS.Corner(mimic); 2966 mimic.precedance = corner.precedance; 2967 2968 if(mimic.x === 'inherit') { mimic.x = corner.x; } 2969 else if(mimic.y === 'inherit') { mimic.y = corner.y; } 2970 else if(mimic.x === mimic.y) { 2971 mimic[ corner.precedance ] = corner[ corner.precedance ]; 2972 } 2973 } 2974 precedance = mimic.precedance; 2975 2976 // Update our colours 2977 self.detectColours(); 2978 2979 // Detect border width, taking into account colours 2980 if(color.border !== 'transparent' && color.border !== '#123456') { 2981 // Grab border width 2982 border = borderWidth(corner, NULL, TRUE); 2983 2984 // If border width isn't zero, use border color as fill (1.0 style tips) 2985 if(opts.border === 0 && border > 0) { color.fill = color.border; } 2986 2987 // Set border width (use detected border width if opts.border is true) 2988 self.border = border = opts.border !== TRUE ? opts.border : border; 2989 } 2990 2991 // Border colour was invalid, set border to zero 2992 else { self.border = border = 0; } 2993 2994 // Calculate coordinates 2995 coords = calculateTip(mimic, width , height); 2996 2997 // Determine tip size 2998 self.size = newSize = calculateSize(corner); 2999 tip.css(newSize); 3000 3001 // Calculate tip translation 3002 if(corner.precedance === 'y') { 3003 translate = [ 3004 round(mimic.x === 'left' ? border : mimic.x === 'right' ? newSize.width - width - border : (newSize.width - width) / 2), 3005 round(mimic.y === 'top' ? newSize.height - height : 0) 3006 ]; 3007 } 3008 else { 3009 translate = [ 3010 round(mimic.x === 'left' ? newSize.width - width : 0), 3011 round(mimic.y === 'top' ? border : mimic.y === 'bottom' ? newSize.height - height - border : (newSize.height - height) / 2) 3012 ]; 3013 } 3014 3015 // Canvas drawing implementation 3016 if(hasCanvas) { 3017 // Set the canvas size using calculated size 3018 inner.attr(newSize); 3019 3020 // Grab canvas context and clear/save it 3021 context = inner[0].getContext('2d'); 3022 context.restore(); context.save(); 3023 context.clearRect(0,0,3000,3000); 3024 3025 // Translate origin 3026 context.translate(translate[0], translate[1]); 3027 3028 // Draw the tip 3029 context.beginPath(); 3030 context.moveTo(coords[0][0], coords[0][1]); 3031 context.lineTo(coords[1][0], coords[1][1]); 3032 context.lineTo(coords[2][0], coords[2][1]); 3033 context.closePath(); 3034 context.fillStyle = color.fill; 3035 context.strokeStyle = color.border; 3036 context.lineWidth = border * 2; 3037 context.lineJoin = 'miter'; 3038 context.miterLimit = 100; 3039 if(border) { context.stroke(); } 3040 context.fill(); 3041 } 3042 3043 // VML (IE Proprietary implementation) 3044 else { 3045 // Setup coordinates string 3046 coords = 'm' + coords[0][0] + ',' + coords[0][1] + ' l' + coords[1][0] + 3047 ',' + coords[1][1] + ' ' + coords[2][0] + ',' + coords[2][1] + ' xe'; 3048 3049 // Setup VML-specific offset for pixel-perfection 3050 translate[2] = border && /^(r|b)/i.test(corner.string()) ? 3051 parseFloat($.browser.version, 10) === 8 ? 2 : 1 : 0; 3052 3053 // Set initial CSS 3054 inner.css({ 3055 antialias: ''+(mimic.string().indexOf('center') > -1), 3056 left: translate[0] - (translate[2] * Number(precedance === 'x')), 3057 top: translate[1] - (translate[2] * Number(precedance === 'y')), 3058 width: width + border, 3059 height: height + border 3060 }) 3061 .each(function(i) { 3062 var $this = $(this); 3063 3064 // Set shape specific attributes 3065 $this[ $this.prop ? 'prop' : 'attr' ]({ 3066 coordsize: (width+border) + ' ' + (height+border), 3067 path: coords, 3068 fillcolor: color.fill, 3069 filled: !!i, 3070 stroked: !!!i 3071 }) 3072 .css({ display: border || i ? 'block' : 'none' }); 3073 3074 // Check if border is enabled and add stroke element 3075 if(!i && $this.html() === '') { 3076 $this.html( 3077 '<vml:stroke weight="'+(border*2)+'px" color="'+color.border+'" miterlimit="1000" joinstyle="miter" ' + 3078 ' style="behavior:url(#default#VML); display:inline-block;" />' 3079 ); 3080 } 3081 }); 3082 } 3083 3084 // Position if needed 3085 if(position !== FALSE) { self.position(corner); } 3086 }, 3087 3088 // Tip positioning method 3089 position: function(corner) 3090 { 3091 var tip = elems.tip, 3092 position = {}, 3093 userOffset = Math.max(0, opts.offset), 3094 precedance, dimensions, corners; 3095 3096 // Return if tips are disabled or tip is not yet rendered 3097 if(opts.corner === FALSE || !tip) { return FALSE; } 3098 3099 // Inherit corner if not provided 3100 corner = corner || self.corner; 3101 precedance = corner.precedance; 3102
3103 // Determine which tip dimension to use for adjustment 3104 dimensions = calculateSize(corner); 3105 3106 // Setup corners and offset array 3107 corners = [ corner.x, corner.y ]; 3108 if(precedance === 'x') { corners.reverse(); } 3109 3110 // Calculate tip position 3111 $.each(corners, function(i, side) { 3112 var b, br; 3113 3114 if(side === 'center') { 3115 b = precedance === 'y' ? 'left' : 'top'; 3116 position[ b ] = '50%'; 3117 position['margin-' + b] = -Math.round(dimensions[ precedance === 'y' ? 'width' : 'height' ] / 2) + userOffset; 3118 } 3119 else { 3120 b = borderWidth(corner, side, TRUE); 3121 br = borderRadius(corner); 3122 3123 position[ side ] = i ? 3124 border ? borderWidth(corner, side) : 0 : 3125 userOffset + (br > b ? br : 0); 3126 } 3127 }); 3128 3129 // Adjust for tip dimensions 3130 position[ corner[precedance] ] -= dimensions[ precedance === 'x' ? 'width' : 'height' ]; 3131 3132 // Set and return new position 3133 tip.css({ top: '', bottom: '', left: '', right: '', margin: '' }).css(position); 3134 return position; 3135 }, 3136 3137 destroy: function() 3138 { 3139 // Remov tip and bound events 3140 if(elems.tip) { elems.tip.remove(); } 3141 tooltip.unbind(namespace); 3142 } 3143 }); 3144 3145 self.init(); 3146} 3147 3148PLUGINS.tip = function(api) 3149{ 3150 var self = api.plugins.tip; 3151 3152 return 'object' === typeof self ? self : (api.plugins.tip = new Tip(api)); 3153}; 3154 3155// Initialize tip on render 3156PLUGINS.tip.initialize = 'render'; 3157 3158// Setup plugin sanitization options 3159PLUGINS.tip.sanitize = function(options) 3160{ 3161 var style = options.style, opts; 3162 if(style && 'tip' in style) { 3163 opts = options.style.tip; 3164 if(typeof opts !== 'object'){ options.style.tip = { corner: opts }; } 3165 if(!(/string|boolean/i).test(typeof opts.corner)) { opts.corner = TRUE; } 3166 if(typeof opts.width !== 'number'){ delete opts.width; } 3167 if(typeof opts.height !== 'number'){ delete opts.height; } 3168 if(typeof opts.border !== 'number' && opts.border !== TRUE){ delete opts.border; } 3169 if(typeof opts.offset !== 'number'){ delete opts.offset; } 3170 } 3171}; 3172 3173// Extend original qTip defaults 3174$.extend(TRUE, QTIP.defaults, { 3175 style: { 3176 tip: { 3177 corner: TRUE, 3178 mimic: FALSE, 3179 width: 6, 3180 height: 6, 3181 border: TRUE, 3182 offset: 0 3183 } 3184 } 3185}); 3186 3187 3188}(jQuery, window));; 3189(function ($) { 3190Drupal.behaviors.qtip = { 3191 attach: function(context) { 3192 3193 // Set delay on click to immediate 3194 if (Drupal.settings.qtip.show_event_type == 'click') { 3195 show_delay = 1; 3196 } 3197 else { 3198 show_delay = 140; // This is the default qTip value, set for hover links 3199 } 3200 3201 // Determine how to position the tooltip 3202 if (Drupal.settings.qtip.show_speech_bubble_tip) { 3203 // Set the proper qtip speech-bubble tip position 3204 if (Drupal.settings.qtip.show_speech_bubble_tip_side) { 3205 switch_position = Drupal.settings.qtip.tooltip_position.split('_'); 3206 // We do not want to put tooltip on it's side if it is being displayed 3207 // in the center or side of an element 3208 if (switch_position[1] == 'center') { 3209 tip_position = Drupal.settings.qtip.tooltip_position; 3210 } 3211 else { 3212 tip_position = switch_position[1] + ' ' + switch_position[0]; 3213 } 3214 } 3215 // Otherwise just set it to the same value as the tooltip 3216 else { 3217 tip_position = Drupal.settings.qtip.tooltip_position; 3218 } 3219 } 3220 else { 3221 tip_position = false; 3222 } 3223 3224 // Is a custom style declared by the admin? 3225 if(Drupal.settings.qtip.color == 'custom-color') { 3226 Drupal.settings.qtip.color = Drupal.settings.qtip.custom_color; 3227 } 3228 3229 // Do we want a shadow to show under the tooltip? 3230 if(Drupal.settings.qtip.show_shadow) { 3231 style_classes = 'ui-tooltip-shadow ' + Drupal.settings.qtip.color; 3232 } 3233 else { 3234 style_classes = Drupal.settings.qtip.color; 3235 } 3236 3237 // Do we want to show the tooltips with rounded corners? 3238 if(Drupal.settings.qtip.rounded_corners) { 3239 style_classes += ' ui-tooltip-rounded'; 3240 } 3241 3242 //Do we want a solid tip to display 3243 if (Drupal.settings.qtip.show_speech_bubble_tip_solid) { 3244 solid_tip = false; 3245 } 3246 else { 3247 solid_tip = 5; 3248 } 3249 3250/*** Standard Settings ***/ 3251 $('.qtip-link').each(function() { 3252 if(Drupal.settings.qtip.show_event_type == 'click') { 3253 $(this).addClass('ui-tooltip-click'); 3254 } 3255 3256 // if there is a title associated with this qtip... 3257 if ($(this).children('.qtip-header').length) { 3258 tooltip_title = $(this).children('.qtip-header').html(); 3259 } 3260 // if there isn't we don't want a blank title area to show on the qtip... 3261 else { 3262 tooltip_title = false;
3263 } 3264 3265 build_qtip(this, 'node'); 3266 }); 3267 3268/*** Additional Elements ***/ 3269 if(Drupal.settings.qtip.additional_elements) { 3270 $(Drupal.settings.qtip.additional_elements).each(function() { 3271 $(this).addClass('qtip-additional-element'); 3272 if(Drupal.settings.qtip.show_event_type == 'click') { 3273 $(this).addClass('ui-tooltip-click'); 3274 } 3275 3276 // if there is a title associated with this qtip... 3277 if ($(this).children('.qtip-header').length) { 3278 tooltip_title = $(this).children('.qtip-header').html(); 3279 } 3280 // if there isn't we don't want a blank title area to show on the qtip... 3281 else { 3282 tooltip_title = false; 3283 } 3284 3285 build_qtip(this, 'node'); 3286 }); 3287 } 3288 3289/*** Webform Settings ***/ 3290 // Does the admin even want the descriptions to show up in a tooltip? 3291 if (Drupal.settings.qtip.show_webform_descriptions) { 3292 /* 3293 form-text - works well 3294 form-textarea - works, but width can throw off the tooltip 3295 form-radios - works, but the radios element wrapper is 100% width, so throws off tooltip like form-textarea 3296 form-select - works well - should maybe have something like mouseenter or something for usability 3297 */ 3298 // Elements where description is a sibling to the selected element 3299 $('form.webform-client-form .form-text,' + 3300 'form.webform-client-form .form-select').each(function() { 3301 description = $(this).siblings('.description'); 3302 description.css('display', 'none'); 3303 tooltip_title = false; 3304 show_delay = 1; 3305 build_qtip(this, 'form', description.html()); 3306 }); 3307 3308 // Elements where description is a sibling to the PARENT of the selected element 3309 $('form.webform-client-form .form-radios,' + 3310 'form.webform-client-form .form-textarea').each(function() { 3311 description = $(this).parent().siblings('.description'); 3312 description.css('display', 'none'); 3313 tooltip_title = false; 3314 show_delay = 1; 3315 3316 build_qtip(this, 'form', description.html()); 3317 }); 3318 } 3319 3320 3321 3322 function build_qtip(el, type, desc) { 3323 if (type == 'form') { 3324 show_text = desc ? desc : ''; 3325 // By default, the description text is wrapped inside <p> 3326 // Since we are putting this in a tooltip we want to remove this 3327 // and any other tags that might possibly get set 3328 show_text = show_text.replace(/(<.*?>)/ig,""); 3329 set_my = 'left_center'; 3330 tip_position = 'left_center'; 3331 set_at = 'right_center'; 3332 show_event = 'focus'; 3333 hide_event = 'blur'; 3334 } 3335 else { // "Normal" tooltip 3336 show_text = $(el).children('.qtip-tooltip'); 3337 set_my = Drupal.settings.qtip.tooltip_position; 3338 set_at = Drupal.settings.qtip.target_position; 3339 show_event = Drupal.settings.qtip.show_event_type; 3340 hide_event = Drupal.settings.qtip.hide_event_type; 3341 } 3342 $(el).qtip({ 3343 content: { 3344 text: show_text, 3345 title: { 3346 text: tooltip_title 3347 } 3348 }, 3349 position: { 3350 my: set_my, // my = speech bubble position on tooltip 3351 at: set_at, // at = where on link text tooltip will appear 3352 adjust: { 3353 screen: true // Keeps tooltip within visible window 3354 } 3355 }, 3356 style: { 3357 classes: style_classes, 3358 tip: { 3359 corner: tip_position, // Position of speech bubble tip...false will not display tip 3360 border: solid_tip, // parseInt(Drupal.settings.qtip.border_width) 3361 width: parseInt(Drupal.settings.qtip.speech_bubble_size), 3362 height: parseInt(Drupal.settings.qtip.speech_bubble_size) 3363 } 3364 }, 3365 show: { 3366 event: show_event, 3367 solo: true, // Determines whether or not the tooltip will hide all others when the show.event is triggered on the show.target. 3368 delay: show_delay 3369 }, 3370 hide: { 3371 event: hide_event, 3372 fixed: true //When set to true, the tooltip will not hide if moused over, allowing the contents to be clicked and interacted with. 3373 } 3374 }); 3375 } 3376 } 3377}; 3378})(jQuery); 3379; 3380/*! jquery.cookie v1.4.1 | MIT */ 3381!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?a(require("jquery")):a(jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0===a.cookie(b)?!1:(a.cookie(b,"",a.extend({},c,{expires:-1})),!a.cookie(b))}});; 3382 3383/*! 3384 * jQuery Form Plugin 3385 * version: 2.52 (07-DEC-2010) 3386 * @requires jQuery v1.3.2 or later 3387 * 3388 * Examples and documentation at: http://malsup.com/jquery/form/ 3389 * Dual licensed under the MIT and GPL licenses: 3390 * http://www.opensource.org/licenses/mit-license.php 3391 * http://www.gnu.org/licenses/gpl.html 3392 */ 3393;(function(b){function q(){if(b.fn.ajaxSubmit.debug){var a="[jquery.form] "+Array.prototype.join.call(arguments,"");if(window.console&&window.console.log)window.console.log(a);else window.opera&&window.opera.postError&&window.opera.postError(a)}}b.fn.ajaxSubmit=function(a){function f(){function t(){var o=i.attr("target"),m=i.attr("action");l.setAttribute("target",u);l.getAttribute("method")!="POST"&&l.setAttribute("method","POST");l.getAttribute("action")!=e.url&&l.setAttribute("action",e.url);e.skipEncodingOverride|| i.attr({encoding:"multipart/form-data",enctype:"multipart/form-data"});e.timeout&&setTimeout(function(){F=true;s()},e.timeout);var v=[];
3393try{if(e.extraData)for(var w in e.extraData)v.push(b('<input type="hidden" name="'+w+'" value="'+e.extraData[w]+'" />').appendTo(l)[0]);r.appendTo("body");r.data("form-plugin-onload",s);l.submit()}finally{l.setAttribute("action",m);o?l.setAttribute("target",o):i.removeAttr("target");b(v).remove()}}function s(){if(!G){r.removeData("form-plugin-onload");var o=true; try{if(F)throw"timeout";p=x.contentWindow?x.contentWindow.document:x.contentDocument?x.contentDocument:x.document;var m=e.dataType=="xml"||p.XMLDocument||b.isXMLDoc(p);q("isXml="+m);if(!m&&window.opera&&(p.body==null||p.body.innerHTML==""))if(--K){q("requeing onLoad callback, DOM not available");setTimeout(s,250);return}G=true;j.responseText=p.documentElement?p.documentElement.innerHTML:null;j.responseXML=p.XMLDocument?p.XMLDocument:p;j.getResponseHeader=function(L){return{"content-type":e.dataType}[L]}; var v=/(json|script)/.test(e.dataType);if(v||e.textarea){var w=p.getElementsByTagName("textarea")[0];if(w)j.responseText=w.value;else if(v){var H=p.getElementsByTagName("pre")[0],I=p.getElementsByTagName("body")[0];if(H)j.responseText=H.textContent;else if(I)j.responseText=I.innerHTML}}else if(e.dataType=="xml"&&!j.responseXML&&j.responseText!=null)j.responseXML=C(j.responseText);J=b.httpData(j,e.dataType)}catch(D){q("error caught:",D);o=false;j.error=D;b.handleError(e,j,"error",D)}if(j.aborted){q("upload aborted"); o=false}if(o){e.success.call(e.context,J,"success",j);y&&b.event.trigger("ajaxSuccess",[j,e])}y&&b.event.trigger("ajaxComplete",[j,e]);y&&!--b.active&&b.event.trigger("ajaxStop");if(e.complete)e.complete.call(e.context,j,o?"success":"error");setTimeout(function(){r.removeData("form-plugin-onload");r.remove();j.responseXML=null},100)}}function C(o,m){if(window.ActiveXObject){m=new ActiveXObject("Microsoft.XMLDOM");m.async="false";m.loadXML(o)}else m=(new DOMParser).parseFromString(o,"text/xml");return m&& m.documentElement&&m.documentElement.tagName!="parsererror"?m:null}var l=i[0];if(b(":input[name=submit],:input[id=submit]",l).length)alert('Error: Form elements must not have name or id of "submit".');else{var e=b.extend(true,{},b.ajaxSettings,a);e.context=e.context||e;var u="jqFormIO"+(new Date).getTime(),E="_"+u;window[E]=function(){var o=r.data("form-plugin-onload");if(o){o();window[E]=undefined;try{delete window[E]}catch(m){}}};var r=b('<iframe id="'+u+'" name="'+u+'" src="'+e.iframeSrc+'" onload="window[\'_\'+this.id]()" />'), x=r[0];r.css({position:"absolute",top:"-1000px",left:"-1000px"});var j={aborted:0,responseText:null,responseXML:null,status:0,statusText:"n/a",getAllResponseHeaders:function(){},getResponseHeader:function(){},setRequestHeader:function(){},abort:function(){this.aborted=1;r.attr("src",e.iframeSrc)}},y=e.global;y&&!b.active++&&b.event.trigger("ajaxStart");y&&b.event.trigger("ajaxSend",[j,e]);if(e.beforeSend&&e.beforeSend.call(e.context,j,e)===false)e.global&&b.active--;else if(!j.aborted){var G=false, F=0,z=l.clk;if(z){var A=z.name;if(A&&!z.disabled){e.extraData=e.extraData||{};e.extraData[A]=z.value;if(z.type=="image"){e.extraData[A+".x"]=l.clk_x;e.extraData[A+".y"]=l.clk_y}}}e.forceSync?t():setTimeout(t,10);var J,p,K=50}}}if(!this.length){q("ajaxSubmit: skipping submit process - no element selected");return this}if(typeof a=="function")a={success:a};var d=this.attr("action");if(d=typeof d==="string"?b.trim(d):"")d=(d.match(/^([^#]+)/)||[])[1];d=d||window.location.href||"";a=b.extend(true,{url:d, type:this.attr("method")||"GET",iframeSrc:/^https/i.test(window.location.href||"")?"javascript:false":"about:blank"},a);d={};this.trigger("form-pre-serialize",[this,a,d]);if(d.veto){q("ajaxSubmit: submit vetoed via form-pre-serialize trigger");return this}if(a.beforeSerialize&&a.beforeSerialize(this,a)===false){q("ajaxSubmit: submit aborted via beforeSerialize callback");return this}var c,h,g=this.formToArray(a.semantic);if(a.data){a.extraData=a.data;for(c in a.data)if(a.data[c]instanceof Array)for(var k in a.data[c])g.push({name:c, value:a.data[c][k]});else{h=a.data[c];h=b.isFunction(h)?h():h;g.push({name:c,value:h})}}if(a.beforeSubmit&&a.beforeSubmit(g,this,a)===false){q("ajaxSubmit: submit aborted via beforeSubmit callback");return this}this.trigger("form-submit-validate",[g,this,a,d]);
3393if(d.veto){q("ajaxSubmit: submit vetoed via form-submit-validate trigger");return this}c=b.param(g);if(a.type.toUpperCase()=="GET"){a.url+=(a.url.indexOf("?")>=0?"&":"?")+c;a.data=null}else a.data=c;var i=this,n=[];a.resetForm&&n.push(function(){i.resetForm()}); a.clearForm&&n.push(function(){i.clearForm()});if(!a.dataType&&a.target){var B=a.success||function(){};n.push(function(t){var s=a.replaceTarget?"replaceWith":"html";b(a.target)[s](t).each(B,arguments)})}else a.success&&n.push(a.success);a.success=function(t,s,C){for(var l=a.context||a,e=0,u=n.length;e<u;e++)n[e].apply(l,[t,s,C||i,i])};c=b("input:file",this).length>0;k=i.attr("enctype")=="multipart/form-data"||i.attr("encoding")=="multipart/form-data";if(a.iframe!==false&&(c||a.iframe||k))a.closeKeepAlive? b.get(a.closeKeepAlive,f):f();else b.ajax(a);this.trigger("form-submit-notify",[this,a]);return this};b.fn.ajaxForm=function(a){if(this.length===0){var f={s:this.selector,c:this.context};if(!b.isReady&&f.s){q("DOM not ready, queuing ajaxForm");b(function(){b(f.s,f.c).ajaxForm(a)});return this}q("terminating; zero elements found by selector"+(b.isReady?"":" (DOM not ready)"));return this}return this.ajaxFormUnbind().bind("submit.form-plugin",function(d){if(!d.isDefaultPrevented()){d.preventDefault(); b(this).ajaxSubmit(a)}}).bind("click.form-plugin",function(d){var c=d.target,h=b(c);if(!h.is(":submit,input:image")){c=h.closest(":submit");if(c.length==0)return;c=c[0]}var g=this;g.clk=c;if(c.type=="image")if(d.offsetX!=undefined){g.clk_x=d.offsetX;g.clk_y=d.offsetY}else if(typeof b.fn.offset=="function"){h=h.offset();g.clk_x=d.pageX-h.left;g.clk_y=d.pageY-h.top}else{g.clk_x=d.pageX-c.offsetLeft;g.clk_y=d.pageY-c.offsetTop}setTimeout(function(){g.clk=g.clk_x=g.clk_y=null},100)})};b.fn.ajaxFormUnbind= function(){return this.unbind("submit.form-plugin click.form-plugin")};b.fn.formToArray=function(a){var f=[];if(this.length===0)return f;var d=this[0],c=a?d.getElementsByTagName("*"):d.elements;if(!c)return f;var h,g,k,i,n,B;h=0;for(n=c.length;h<n;h++){g=c[h];if(k=g.name)if(a&&d.clk&&g.type=="image"){if(!g.disabled&&d.clk==g){f.push({name:k,value:b(g).val()});f.push({name:k+".x",value:d.clk_x},{name:k+".y",value:d.clk_y})}}else if((i=b.fieldValue(g,true))&&i.constructor==Array){g=0;for(B=i.length;g< B;g++)f.push({name:k,value:i[g]})}else i!==null&&typeof i!="undefined"&&f.push({name:k,value:i})}if(!a&&d.clk){a=b(d.clk);c=a[0];if((k=c.name)&&!c.disabled&&c.type=="image"){f.push({name:k,value:a.val()});f.push({name:k+".x",value:d.clk_x},{name:k+".y",value:d.clk_y})}}return f};b.fn.formSerialize=function(a){return b.param(this.formToArray(a))};b.fn.fieldSerialize=function(a){var f=[];this.each(function(){var d=this.name;if(d){var c=b.fieldValue(this,a);if(c&&c.constructor==Array)for(var h=0,g=c.length;h< g;h++)f.push({name:d,value:c[h]});else c!==null&&typeof c!="undefined"&&f.push({name:this.name,value:c})}});return b.param(f)};b.fn.fieldValue=function(a){for(var f=[],d=0,c=this.length;d<c;d++){var h=b.fieldValue(this[d],a);h===null||typeof h=="undefined"||h.constructor==Array&&!h.length||(h.constructor==Array?b.merge(f,h):f.push(h))}return f};b.fieldValue=function(a,f){var d=a.name,c=a.type,h=a.tagName.toLowerCase();if(f===undefined)f=true;if(f&&(!d||a.disabled||c=="reset"||c=="button"||(c=="checkbox"|| c=="radio")&&!a.checked||(c=="submit"||c=="image")&&a.form&&a.form.clk!=a||h=="select"&&a.selectedIndex==-1))return null;if(h=="select"){var g=a.selectedIndex;if(g<0)return null;d=[];h=a.options;var k=(c=c=="select-one")?g+1:h.length;for(g=c?g:0;g<k;g++){var i=h[g];if(i.selected){var n=i.value;n||(n=i.attributes&&i.attributes.value&&!i.attributes.value.specified?i.text:i.value);if(c)return n;d.push(n)}}return d}return b(a).val()};b.fn.clearForm=function(){return this.each(function(){b("input,select,textarea", this).clearFields()})};b.fn.clearFields=b.fn.clearInputs=function(){return this.each(function(){var a=this.type,f=this.tagName.toLowerCase();if(a=="text"||a=="password"||f=="textarea")this.value="";else if(a=="checkbox"||a=="radio")this.checked=false;else if(f=="select")this.selectedIndex=-1})};b.fn.resetForm=function(){return this.each(function(){if(typeof this.reset=="function"||typeof this.reset=="object"&&!this.reset.nodeType)this.reset()})};
3393b.fn.enable=function(a){if(a===undefined)a=true;return this.each(function(){this.disabled= !a})};b.fn.selected=function(a){if(a===undefined)a=true;return this.each(function(){var f=this.type;if(f=="checkbox"||f=="radio")this.checked=a;else if(this.tagName.toLowerCase()=="option"){f=b(this).parent("select");a&&f[0]&&f[0].type=="select-one"&&f.find("option").selected(false);this.selected=a}})}})(jQuery);; 3394(function ($) { 3395 3396/** 3397 * Provides Ajax page updating via jQuery $.ajax (Asynchronous JavaScript and XML). 3398 * 3399 * Ajax is a method of making a request via JavaScript while viewing an HTML 3400 * page. The request returns an array of commands encoded in JSON, which is 3401 * then executed to make any changes that are necessary to the page. 3402 * 3403 * Drupal uses this file to enhance form elements with #ajax['path'] and 3404 * #ajax['wrapper'] properties. If set, this file will automatically be included 3405 * to provide Ajax capabilities. 3406 */ 3407 3408Drupal.ajax = Drupal.ajax || {}; 3409 3410Drupal.settings.urlIsAjaxTrusted = Drupal.settings.urlIsAjaxTrusted || {}; 3411 3412/** 3413 * Attaches the Ajax behavior to each Ajax form element. 3414 */ 3415Drupal.behaviors.AJAX = { 3416 attach: function (context, settings) { 3417 // Load all Ajax behaviors specified in the settings. 3418 for (var base in settings.ajax) { 3419 if (!$('#' + base + '.ajax-processed').length) { 3420 var element_settings = settings.ajax[base]; 3421 3422 if (typeof element_settings.selector == 'undefined') { 3423 element_settings.selector = '#' + base; 3424 } 3425 $(element_settings.selector).each(function () { 3426 element_settings.element = this; 3427 Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); 3428 }); 3429 3430 $('#' + base).addClass('ajax-processed'); 3431 } 3432 } 3433 3434 // Bind Ajax behaviors to all items showing the class. 3435 $('.use-ajax:not(.ajax-processed)').addClass('ajax-processed').each(function () { 3436 var element_settings = {}; 3437 // Clicked links look better with the throbber than the progress bar. 3438 element_settings.progress = { 'type': 'throbber' }; 3439 3440 // For anchor tags, these will go to the target of the anchor rather 3441 // than the usual location. 3442 if ($(this).attr('href')) { 3443 element_settings.url = $(this).attr('href'); 3444 element_settings.event = 'click'; 3445 } 3446 var base = $(this).attr('id'); 3447 Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); 3448 }); 3449 3450 // This class means to submit the form to the action using Ajax. 3451 $('.use-ajax-submit:not(.ajax-processed)').addClass('ajax-processed').each(function () { 3452 var element_settings = {}; 3453 3454 // Ajax submits specified in this manner automatically submit to the 3455 // normal form action. 3456 element_settings.url = $(this.form).attr('action'); 3457 // Form submit button clicks need to tell the form what was clicked so 3458 // it gets passed in the POST request. 3459 element_settings.setClick = true; 3460 // Form buttons use the 'click' event rather than mousedown. 3461 element_settings.event = 'click'; 3462 // Clicked form buttons look better with the throbber than the progress bar. 3463 element_settings.progress = { 'type': 'throbber' }; 3464 3465 var base = $(this).attr('id'); 3466 Drupal.ajax[base] = new Drupal.ajax(base, this, element_settings); 3467 }); 3468 } 3469}; 3470 3471/** 3472 * Ajax object. 3473 * 3474 * All Ajax objects on a page are accessible through the global Drupal.ajax 3475 * object and are keyed by the submit button's ID. You can access them from 3476 * your module's JavaScript file to override properties or functions. 3477 * 3478 * For example, if your Ajax enabled button has the ID 'edit-submit', you can 3479 * redefine the function that is called to insert the new content like this 3480 * (inside a Drupal.behaviors attach block): 3481 * @code 3482 * Drupal.behaviors.myCustomAJAXStuff = { 3483 * attach: function (context, settings) { 3484 * Drupal.ajax['edit-submit'].commands.insert = function (ajax, response, status) { 3485 * new_content = $(response.data); 3486 * $('#my-wrapper').append(new_content); 3487 * alert('New content was appended to #my-wrapper'); 3488 * } 3489 * } 3490 * }; 3491 * @endcode 3492 */ 3493Drupal.ajax = function (base, element, element_settings) { 3494 var defaults = { 3495 url: 'system/ajax', 3496 event: 'mousedown', 3497 keypress: true, 3498 selector: '#' + base, 3499 effect: 'none', 3500 speed: 'none', 3501 method: 'replaceWith', 3502 progress: { 3503 type: 'throbber', 3504 message: Drupal.t('Please wait...') 3505 }, 3506 submit: { 3507 'js': true 3508 } 3509 }; 3510 3511 $.extend(this, defaults, element_settings); 3512 3513 this.element = element; 3514 this.element_settings = element_settings; 3515 3516 // Replacing 'nojs' with 'ajax' in the URL allows for an easy method to let 3517 // the server detect when it needs to degrade gracefully. 3518 // There are five scenarios to check for: 3519 // 1. /nojs/ 3520 // 2. /nojs$ - The end of a URL string. 3521 // 3. /nojs? - Followed by a query (with clean URLs enabled). 3522 // E.g.: path/nojs?destination=foobar 3523 // 4. /nojs& - Followed by a query (without clean URLs enabled). 3524 // E.g.: ?q=path/nojs&destination=foobar 3525 // 5. /nojs# - Followed by a fragment. 3526 // E.g.: path/nojs#myfragment 3527 this.url = element_settings.url.replace(/\/nojs(\/|$|\?|&|#)/g, '/ajax$1'); 3528 // If the 'nojs' version of the URL is trusted, also trust the 'ajax' version. 3529 if (Drupal.settings.urlIsAjaxTrusted[element_settings.url]) { 3530 Drupal.settings.urlIsAjaxTrusted[this.url] = true; 3531 } 3532 3533 this.wrapper = '#' + element_settings.wrapper; 3534 3535 // If there isn't a form, jQuery.ajax() will be used instead, allowing us to 3536 // bind Ajax to links as well. 3537 if (this.element.form) { 3538 this.form = $(this.element.form); 3539 } 3540 3541 // Set the options for the ajaxSubmit function. 3542 // The 'this' variable will not persist inside of the options object. 3543 var ajax = this; 3544 ajax.options = { 3545 url: Drupal.sanitizeAjaxUrl(ajax.url), 3546 data: ajax.submit, 3547 beforeSerialize: function (element_settings, options) { 3548 return ajax.beforeSerialize(element_settings, options); 3549 }, 3550 beforeSubmit: function (form_values, element_settings, options) { 3551 ajax.ajaxing = true; 3552 return ajax.beforeSubmit(form_values, element_settings, options); 3553 }, 3554 beforeSend: function (xmlhttprequest, options) { 3555 ajax.ajaxing = true; 3556 return ajax.beforeSend(xmlhttprequest, options); 3557 }, 3558 success: function (response, status, xmlhttprequest) { 3559 // Sanity check for browser support (object expected). 3560 // When using iFrame uploads, responses must be returned as a string. 3561 if (typeof response == 'string') { 3562 response = $.parseJSON(response); 3563 } 3564 3565 // Prior to invoking the response's commands, verify that they can be 3566 // trusted by checking for a response header. See 3567 // ajax_set_verification_header() for details. 3568 // - Empty responses are harmless so can bypass verification. This avoids 3569 // an alert message for server-generated no-op responses that skip Ajax 3570 // rendering. 3571 // - Ajax objects with trusted URLs (e.g., ones defined server-side via 3572 // #ajax) can bypass header verification. This is especially useful for 3573 // Ajax with multipart forms. Because IFRAME transport is used, the 3574 // response headers cannot be accessed for verification. 3575 if (response !== null && !Drupal.settings.urlIsAjaxTrusted[ajax.url]) { 3576 if (xmlhttprequest.getResponseHeader('X-Drupal-Ajax-Token') !== '1') { 3577 var customMessage = Drupal.t("The response failed verification so will not be processed."); 3578 return ajax.error(xmlhttprequest, ajax.url, customMessage); 3579 } 3580 } 3581 3582 return ajax.success(response, status); 3583 }, 3584 complete: function (xmlhttprequest, status) { 3585 ajax.ajaxing = false;
3586 if (status == 'error' || status == 'parsererror') { 3587 return ajax.error(xmlhttprequest, ajax.url); 3588 } 3589 }, 3590 dataType: 'json', 3591 jsonp: false, 3592 type: 'POST' 3593 }; 3594 3595 // For multipart forms (e.g., file uploads), jQuery Form targets the form 3596 // submission to an iframe instead of using an XHR object. The initial "src" 3597 // of the iframe, prior to the form submission, is set to options.iframeSrc. 3598 // "about:blank" is the semantically correct, standards-compliant, way to 3599 // initialize a blank iframe; however, some old IE versions (possibly only 6) 3600 // incorrectly report a mixed content warning when iframes with an 3601 // "about:blank" src are added to a parent document with an https:// origin. 3602 // jQuery Form works around this by defaulting to "javascript:false" instead, 3603 // but that breaks on Chrome 83, so here we force the semantically correct 3604 // behavior for all browsers except old IE. 3605 // @see https://www.drupal.org/project/drupal/issues/3143016 3606 // @see https://github.com/jquery-form/form/blob/df9cb101b9c9c085c8d75ad980c7ff1cf62063a1/jquery.form.js#L68 3607 // @see https://bugs.chromium.org/p/chromium/issues/detail?id=1084874 3608 // @see https://html.spec.whatwg.org/multipage/browsers.html#creating-browsing-contexts 3609 // @see https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy 3610 if (navigator.userAgent.indexOf("MSIE") === -1) { 3611 ajax.options.iframeSrc = 'about:blank'; 3612 } 3613 3614 // Bind the ajaxSubmit function to the element event. 3615 $(ajax.element).bind(element_settings.event, function (event) { 3616 if (!Drupal.settings.urlIsAjaxTrusted[ajax.url] && !Drupal.urlIsLocal(ajax.url)) { 3617 throw new Error(Drupal.t('The callback URL is not local and not trusted: !url', {'!url': ajax.url})); 3618 } 3619 return ajax.eventResponse(this, event); 3620 }); 3621 3622 // If necessary, enable keyboard submission so that Ajax behaviors 3623 // can be triggered through keyboard input as well as e.g. a mousedown 3624 // action. 3625 if (element_settings.keypress) { 3626 $(ajax.element).keypress(function (event) { 3627 return ajax.keypressResponse(this, event); 3628 }); 3629 } 3630 3631 // If necessary, prevent the browser default action of an additional event. 3632 // For example, prevent the browser default action of a click, even if the 3633 // AJAX behavior binds to mousedown. 3634 if (element_settings.prevent) { 3635 $(ajax.element).bind(element_settings.prevent, false); 3636 } 3637}; 3638 3639/** 3640 * Handle a key press. 3641 * 3642 * The Ajax object will, if instructed, bind to a key press response. This 3643 * will test to see if the key press is valid to trigger this event and 3644 * if it is, trigger it for us and prevent other keypresses from triggering. 3645 * In this case we're handling RETURN and SPACEBAR keypresses (event codes 13 3646 * and 32. RETURN is often used to submit a form when in a textfield, and 3647 * SPACE is often used to activate an element without submitting. 3648 */ 3649Drupal.ajax.prototype.keypressResponse = function (element, event) { 3650 // Create a synonym for this to reduce code confusion. 3651 var ajax = this; 3652 3653 // Detect enter key and space bar and allow the standard response for them, 3654 // except for form elements of type 'text' and 'textarea', where the 3655 // spacebar activation causes inappropriate activation if #ajax['keypress'] is 3656 // TRUE. On a text-type widget a space should always be a space. 3657 if (event.which == 13 || (event.which == 32 && element.type != 'text' && element.type != 'textarea')) { 3658 $(ajax.element_settings.element).trigger(ajax.element_settings.event); 3659 return false; 3660 } 3661}; 3662 3663/** 3664 * Handle an event that triggers an Ajax response. 3665 * 3666 * When an event that triggers an Ajax response happens, this method will 3667 * perform the actual Ajax call. It is bound to the event using 3668 * bind() in the constructor, and it uses the options specified on the 3669 * ajax object. 3670 */ 3671Drupal.ajax.prototype.eventResponse = function (element, event) { 3672 // Create a synonym for this to reduce code confusion. 3673 var ajax = this; 3674 3675 // Do not perform another ajax command if one is already in progress. 3676 if (ajax.ajaxing) { 3677 return false; 3678 } 3679 3680 try { 3681 if (ajax.form) { 3682 // If setClick is set, we must set this to ensure that the button's 3683 // value is passed. 3684 if (ajax.setClick) { 3685 // Mark the clicked button. 'form.clk' is a special variable for 3686 // ajaxSubmit that tells the system which element got clicked to 3687 // trigger the submit. Without it there would be no 'op' or
3688 // equivalent. 3689 element.form.clk = element; 3690 } 3691 3692 ajax.form.ajaxSubmit(ajax.options); 3693 } 3694 else { 3695 ajax.beforeSerialize(ajax.element, ajax.options); 3696 $.ajax(ajax.options); 3697 } 3698 } 3699 catch (e) { 3700 // Unset the ajax.ajaxing flag here because it won't be unset during 3701 // the complete response. 3702 ajax.ajaxing = false; 3703 alert("An error occurred while attempting to process " + ajax.options.url + ": " + e.message); 3704 } 3705 3706 // For radio/checkbox, allow the default event. On IE, this means letting 3707 // it actually check the box. 3708 if (typeof element.type != 'undefined' && (element.type == 'checkbox' || element.type == 'radio')) { 3709 return true; 3710 } 3711 else { 3712 return false; 3713 } 3714 3715}; 3716 3717/** 3718 * Handler for the form serialization. 3719 * 3720 * Runs before the beforeSend() handler (see below), and unlike that one, runs 3721 * before field data is collected. 3722 */ 3723Drupal.ajax.prototype.beforeSerialize = function (element, options) { 3724 // Allow detaching behaviors to update field values before collecting them. 3725 // This is only needed when field values are added to the POST data, so only 3726 // when there is a form such that this.form.ajaxSubmit() is used instead of 3727 // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize() 3728 // isn't called, but don't rely on that: explicitly check this.form. 3729 if (this.form) { 3730 var settings = this.settings || Drupal.settings; 3731 Drupal.detachBehaviors(this.form, settings, 'serialize'); 3732 } 3733 3734 // Prevent duplicate HTML ids in the returned markup. 3735 // @see drupal_html_id() 3736 options.data['ajax_html_ids[]'] = []; 3737 $('[id]').each(function () { 3738 options.data['ajax_html_ids[]'].push(this.id); 3739 }); 3740 3741 // Allow Drupal to return new JavaScript and CSS files to load without 3742 // returning the ones already loaded. 3743 // @see ajax_base_page_theme() 3744 // @see drupal_get_css() 3745 // @see drupal_get_js() 3746 options.data['ajax_page_state[theme]'] = Drupal.settings.ajaxPageState.theme; 3747 options.data['ajax_page_state[theme_token]'] = Drupal.settings.ajaxPageState.theme_token; 3748 for (var key in Drupal.settings.ajaxPageState.css) { 3749 options.data['ajax_page_state[css][' + key + ']'] = 1; 3750 } 3751 for (var key in Drupal.settings.ajaxPageState.js) { 3752 options.data['ajax_page_state[js][' + key + ']'] = 1; 3753 } 3754}; 3755 3756/** 3757 * Modify form values prior to form submission. 3758 */ 3759Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) { 3760 // This function is left empty to make it simple to override for modules 3761 // that wish to add functionality here. 3762}; 3763 3764/** 3765 * Prepare the Ajax request before it is sent. 3766 */ 3767Drupal.ajax.prototype.beforeSend = function (xmlhttprequest, options) { 3768 // For forms without file inputs, the jQuery Form plugin serializes the form 3769 // values, and then calls jQuery's $.ajax() function, which invokes this 3770 // handler. In this circumstance, options.extraData is never used. For forms 3771 // with file inputs, the jQuery Form plugin uses the browser's normal form 3772 // submission mechanism, but captures the response in a hidden IFRAME. In this 3773 // circumstance, it calls this handler first, and then appends hidden fields 3774 // to the form to submit the values in options.extraData. There is no simple 3775 // way to know which submission mechanism will be used, so we add to extraData 3776 // regardless, and allow it to be ignored in the former case. 3777 if (this.form) { 3778 options.extraData = options.extraData || {}; 3779 3780 // Let the server know when the IFRAME submission mechanism is used. The 3781 // server can use this information to wrap the JSON response in a TEXTAREA, 3782 // as per http://jquery.malsup.com/form/#file-upload. 3783 options.extraData.ajax_iframe_upload = '1'; 3784 3785 // The triggering element is about to be disabled (see below), but if it 3786 // contains a value (e.g., a checkbox, textfield, select, etc.), ensure that 3787 // value is included in the submission. As per above, submissions that use 3788 // $.ajax() are already serialized prior to the element being disabled, so 3789 // this is only needed for IFRAME submissions. 3790 var v = $.fieldValue(this.element); 3791 if (v !== null) { 3792 options.extraData[this.element.name] = Drupal.checkPlain(v); 3793 } 3794 } 3795 3796 // Disable the element that received the change to prevent user interface 3797 // interaction while the Ajax request is in progress. ajax.ajaxing prevents 3798 // the element from triggering a new request, but does not prevent the user 3799 // from changing its value. 3800 $(this.element).addClass('progress-disabled').attr('disabled', true); 3801 3802 // Insert progressbar or throbber. 3803 if (this.progress.type == 'bar') { 3804 var progressBar = new Drupal.progressBar('ajax-progress-' + this.element.i
3804d, $.noop, this.progress.method, $.noop); 3805 if (this.progress.message) { 3806 progressBar.setProgress(-1, this.progress.message); 3807 } 3808 if (this.progress.url) { 3809 progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500); 3810 } 3811 this.progress.element = $(progressBar.element).addClass('ajax-progress ajax-progress-bar'); 3812 this.progress.object = progressBar; 3813 $(this.element).after(this.progress.element); 3814 } 3815 else if (this.progress.type == 'throbber') { 3816 this.progress.element = $('<div class="ajax-progress ajax-progress-throbber"><div class="throbber"> </div></div>'); 3817 if (this.progress.message) { 3818 $('.throbber', this.progress.element).after('<div class="message">' + this.progress.message + '</div>'); 3819 } 3820 $(this.element).after(this.progress.element); 3821 } 3822}; 3823 3824/** 3825 * Handler for the form redirection completion. 3826 */ 3827Drupal.ajax.prototype.success = function (response, status) { 3828 // Remove the progress element. 3829 if (this.progress.element) { 3830 $(this.progress.element).remove(); 3831 } 3832 if (this.progress.object) { 3833 this.progress.object.stopMonitoring(); 3834 } 3835 $(this.element).removeClass('progress-disabled').removeAttr('disabled'); 3836 3837 Drupal.freezeHeight(); 3838 3839 for (var i in response) { 3840 if (response.hasOwnProperty(i) && response[i]['command'] && this.commands[response[i]['command']]) { 3841 this.commands[response[i]['command']](this, response[i], status); 3842 } 3843 } 3844 3845 // Reattach behaviors, if they were detached in beforeSerialize(). The 3846 // attachBehaviors() called on the new content from processing the response 3847 // commands is not sufficient, because behaviors from the entire form need 3848 // to be reattached. 3849 if (this.form) { 3850 var settings = this.settings || Drupal.settings; 3851 Drupal.attachBehaviors(this.form, settings); 3852 } 3853 3854 Drupal.unfreezeHeight(); 3855 3856 // Remove any response-specific settings so they don't get used on the next 3857 // call by mistake. 3858 this.settings = null; 3859}; 3860 3861/** 3862 * Build an effect object which tells us how to apply the effect when adding new HTML. 3863 */ 3864Drupal.ajax.prototype.getEffect = function (response) { 3865 var type = response.effect || this.effect; 3866 var speed = response.speed || this.speed; 3867 3868 var effect = {}; 3869 if (type == 'none') { 3870 effect.showEffect = 'show'; 3871 effect.hideEffect = 'hide'; 3872 effect.showSpeed = ''; 3873 } 3874 else if (type == 'fade') { 3875 effect.showEffect = 'fadeIn'; 3876 effect.hideEffect = 'fadeOut'; 3877 effect.showSpeed = speed; 3878 } 3879 else { 3880 effect.showEffect = type + 'Toggle'; 3881 effect.hideEffect = type + 'Toggle'; 3882 effect.showSpeed = speed; 3883 } 3884 3885 return effect; 3886}; 3887 3888/** 3889 * Handler for the form redirection error. 3890 */ 3891Drupal.ajax.prototype.error = function (xmlhttprequest, uri, customMessage) { 3892 Drupal.displayAjaxError(Drupal.ajaxError(xmlhttprequest, uri, customMessage)); 3893 // Remove the progress element. 3894 if (this.progress.element) { 3895 $(this.progress.element).remove(); 3896 } 3897 if (this.progress.object) { 3898 this.progress.object.stopMonitoring(); 3899 } 3900 // Undo hide. 3901 $(this.wrapper).show(); 3902 // Re-enable the element. 3903 $(this.element).removeClass('progress-disabled').removeAttr('disabled'); 3904 // Reattach behaviors, if they were detached in beforeSerialize(). 3905 if (this.form) { 3906 var settings = this.settings || Drupal.settings; 3907 Drupal.attachBehaviors(this.form, settings); 3908 } 3909}; 3910 3911/** 3912 * Provide a series of commands that the server can request the client perform. 3913 */ 3914Drupal.ajax.prototype.commands = { 3915 /** 3916 * Command to insert new content into the DOM. 3917 */ 3918 insert: function (ajax, response, status) { 3919 // Get information from the response. If it is not there, default to 3920 // our presets. 3921 var wrapper = response.selector ? $(response.selector) : $(ajax.wrapper); 3922 var method = response.method || ajax.method; 3923 var effect = ajax.getEffect(response); 3924 3925 // We don't know what response.data contains: it might be a string of text 3926 // without HTML, so don't rely on jQuery correctly iterpreting 3927 // $(response.data) as new HTML rather than a CSS selector. Also, if 3928 // response.data contains top-level text nodes, they get lost with either 3929 // $(response.data) or $('<div></div>').replaceWith(response.data). 3930 var new_content_wrapped = $('<div></div>').html(response.data); 3931 var new_content = new_content_wrapped.contents(); 3932 3933 // For legacy reasons, the effects processing code assumes that new_content 3934 // consists of a single top-level element. Also, it has not been 3935 // sufficiently tested whether attachBehaviors() can be successfully called 3936 // with a context object that includes top-level text nodes. However, to 3937 // give developers full control of the HTML appearing in the page, and to 3938 // enable Ajax content to be inserted in places where DIV elements are not 3939 // allowed (e.g., within TABLE, TR, and SPAN parents), we check if the new 3940 // content satisfies the requirement of a single top-level element, and 3941 // only use the container DIV created above when it doesn't. For more 3942 // information, please see http://drupal.org/node/736066. 3943 if (new_content.length != 1 || new_content.get(0).nodeType != 1) { 3944 new_content = new_content_wrapped; 3945 } 3946 3947 // If removing content from the wrapper, detach behaviors first. 3948 switch (method) { 3949 case 'html': 3950 case 'replaceWith': 3951 case 'replaceAll': 3952 case 'empty': 3953 case 'remove': 3954 var settings = response.settings || ajax.settings || Drupal.settings; 3955 Drupal.detachBehaviors(wrapper, settings); 3956 } 3957 3958 // Add the new content to the page. 3959 wrapper[method](new_content); 3960 3961 // Immediately hide the new content if we're using any effects. 3962 if (effect.showEffect != 'show') { 3963 new_content.hide(); 3964 } 3965 3966 // Determine which effect to use and what content will receive the 3967 // effect, then show the new content. 3968 if ($('.ajax-new-content', new_content).length > 0) { 3969 $('.ajax-new-content', new_content).hide(); 3970 new_content.show(); 3971 $('.ajax-new-content', new_content)[effect.showEffect](effect.showSpeed); 3972 } 3973 else if (effect.showEffect != 'show') { 3974 new_content[effect.showEffect](effect.showSpeed); 3975 } 3976 3977 // Attach all JavaScript behaviors to the new content, if it was successfully 3978 // added to the page, this if statement allows #ajax['wrapper'] to be 3979 // optional. 3980 if (new_content.parents('html').length > 0) { 3981 // Apply any settings from the returned JSON if available. 3982 var settings = response.settings || ajax.settings || Drupal.settings; 3983 Drupal.attachBehaviors(new_content, settings); 3984 } 3985 }, 3986 3987 /** 3988 * Command to remove a chunk from the page. 3989 */ 3990 remove: function (ajax, response, status) { 3991 var settings = response.settings || ajax.settings || Drupal.settings; 3992 Drupal.detachBehaviors($(response.selector), settings); 3993 $(response.selector).remove(); 3994 }, 3995 3996 /** 3997 * Command to mark a chunk changed. 3998 */ 3999 changed: function (ajax, response, status) { 4000 if (!$(response.selector).hasClass('ajax-changed')) { 4001 $(response.selector).addClass('ajax-changed'); 4002 if (response.asterisk) { 4003 $(response.selector).find(response.asterisk).append(' <span class="ajax-changed">*</span> '); 4004 } 4005 } 4006 }, 4007 4008 /** 4009 * Command to provide an alert. 4010 */ 4011 alert: function (ajax, response, status) { 4012 alert(response.text, response.title); 4013 }, 4014 4015 /** 4016 * Command to provide the jQuery css() function. 4017 */ 4018 css: function (ajax, response, status) { 4019 $(response.selector).css(response.argument); 4020 }, 4021 4022 /** 4023 * Command to set the settings that will be used for other commands in this response. 4024 */ 4025 settings: function (ajax, response, status) { 4026 if (response.merge) { 4027 $.extend(true, Drupal.settings, response.settings); 4028 } 4029 else { 4030 ajax.settings = response.settings; 4031 } 4032 }, 4033 4034 /** 4035 * Command to attach data using jQuery's data API. 4036 */ 4037 data: function (ajax, response, status) { 4038 $(response.selector).data(response.name, response.value); 4039 }, 4040 4041 /** 4042 * Command to apply a jQuery method. 4043 */ 4044 invoke: function (ajax, response, status) { 4045 var $element = $(response.selector); 4046 $element[response.method].apply($element, response.arguments); 4047 }, 4048 4049 /** 4050 * Command to restripe a table. 4051 */ 4052 restripe: function (ajax, response, status) { 4053 // :even and :odd are reversed because jQuery counts from 0 and 4054 // we count from 1, so we're out of sync. 4055 // Match immediate children of the parent element to allow nesting. 4056 $('> tbody > tr:visible, > tr:visible', $(response.selector)) 4057 .removeClass('odd even') 4058 .filter(':even').addClass('odd').end() 4059 .filter(':odd').addClass('even'); 4060 }, 4061 4062 /** 4063 * Command to add css. 4064 * 4065 * Uses the proprietary addImport method if available as browsers which 4066 * support that method ignore @import statements in dynamically added 4067 * stylesheets. 4068 */ 4069 add_css: function (ajax, response, status) { 4070 // Add the styles in the normal way. 4071 $('head').prepend(response.data); 4072 // Add imports in the styles using the addImport method if available. 4073 var match, importMatch = /^@import url\("(.*)"\);$/igm; 4074 if (document.styleSheets[0].addImport && importMatch.test(response.data)) { 4075 importMatch.lastIndex = 0; 4076 while (match = importMatch.exec(response.data)) { 4077 document.styleSheets[0].addImport(match[1]); 4078 } 4079 } 4080 }, 4081 4082 /** 4083 * Command to update a form's build ID. 4084 */ 4085 updateBuildId: function(ajax, response, status) { 4086 $('input[name="form_build_id"][value="' + response['old'] + '"]').val(response['new']); 4087 } 4088}; 4089 4090})(jQuery); 4091;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.