css
8 years ago
chosen.jquery.js
8 years ago
chosen.jquery.min.js
10 years ago
jquery.ddslick.min.js
10 years ago
jquery.mjs.pmxe_nestedSortable.js
7 years ago
jquery.timepicker.js
8 years ago
jquery.tipsy.js
5 years ago
moment.js
9 years ago
select2.min.js
12 years ago
ui.autocomplete.js
12 years ago
ui.datepicker.js
12 years ago
jquery.tipsy.js
418 lines
| 1 | // tipsy, facebook style tooltips for jquery |
| 2 | // version 1.0.2 |
| 3 | // (c) 2008-2010 jason frame [jason@onehackoranother.com] |
| 4 | // released under the MIT license |
| 5 | |
| 6 | (function($, window, undefined) { |
| 7 | |
| 8 | function maybeCall(thing, ctx) { |
| 9 | return (typeof thing == 'function') ? (thing.call(ctx)) : thing; |
| 10 | } |
| 11 | |
| 12 | function isElementInDOM(ele) { |
| 13 | while (ele = ele.parentNode) { |
| 14 | if (ele == document) return true; |
| 15 | } |
| 16 | return false; |
| 17 | } |
| 18 | |
| 19 | // Returns true if it is a DOM element |
| 20 | // http://stackoverflow.com/a/384380/999 |
| 21 | function isElement(o){ |
| 22 | return ( |
| 23 | typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2 |
| 24 | o && typeof o === "object" && o.nodeType === 1 && typeof o.nodeName==="string" |
| 25 | ); |
| 26 | } |
| 27 | |
| 28 | var tipsyIDcounter = 0; |
| 29 | function tipsyID() { |
| 30 | return "tipsyuid" + (tipsyIDcounter++); |
| 31 | } |
| 32 | |
| 33 | function Tipsy(element, options) { |
| 34 | this.$element = $(element); |
| 35 | this.options = options; |
| 36 | this.enabled = true; |
| 37 | this.fixTitle(); |
| 38 | } |
| 39 | |
| 40 | Tipsy.prototype = { |
| 41 | show: function() { |
| 42 | if (!isElementInDOM(this.$element[0])) { |
| 43 | return; |
| 44 | } |
| 45 | |
| 46 | if (isElement(this.$element) && !this.$element.is(':visible')) { |
| 47 | return; |
| 48 | } |
| 49 | |
| 50 | var title; |
| 51 | if (this.enabled && (title = this.getTitle())) { |
| 52 | var $tip = this.tip(); |
| 53 | |
| 54 | $tip.find('.tipsy-inner' + this.options.theme)[this.options.html ? 'html' : 'text'](title); |
| 55 | |
| 56 | $tip[0].className = 'tipsy' + this.options.theme; // reset classname in case of dynamic gravity |
| 57 | if (this.options.className) { |
| 58 | $tip.addClass(maybeCall(this.options.className, this.$element[0])); |
| 59 | } |
| 60 | |
| 61 | $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block', 'z-index': '1000000000000'}).prependTo(document.body); |
| 62 | |
| 63 | var pos = $.extend({}, this.$element.offset()); |
| 64 | |
| 65 | // If the element is contained in a SVG object, use getBBox |
| 66 | if (this.$element.parents('svg').size() > 0) { |
| 67 | pos = $.extend(pos, this.$element[0].getBBox()); |
| 68 | } else { |
| 69 | pos = $.extend(pos, { |
| 70 | width: this.$element[0].offsetWidth || 0, |
| 71 | height: this.$element[0].offsetHeight || 0 |
| 72 | }); |
| 73 | } |
| 74 | |
| 75 | var actualWidth = $tip[0].offsetWidth, |
| 76 | actualHeight = $tip[0].offsetHeight, |
| 77 | gravity = maybeCall(this.options.gravity, this.$element[0]); |
| 78 | |
| 79 | var tp; |
| 80 | switch (gravity.charAt(0)) { |
| 81 | case 'n': |
| 82 | tp = {top: pos.top + pos.height + this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}; |
| 83 | break; |
| 84 | case 's': |
| 85 | tp = {top: pos.top - actualHeight - this.options.offset, left: pos.left + pos.width / 2 - actualWidth / 2}; |
| 86 | break; |
| 87 | case 'e': |
| 88 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth - this.options.offset}; |
| 89 | break; |
| 90 | case 'w': |
| 91 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width + this.options.offset}; |
| 92 | break; |
| 93 | default: |
| 94 | break; |
| 95 | } |
| 96 | |
| 97 | if (gravity.length == 2) { |
| 98 | if (gravity.charAt(1) == 'w') { |
| 99 | tp.left = pos.left + pos.width / 2 - 15; |
| 100 | } else { |
| 101 | tp.left = pos.left + pos.width / 2 - actualWidth + 15; |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | $tip.css(tp).addClass('tipsy-' + gravity + this.options.theme); |
| 106 | $tip.find('.tipsy-arrow' + this.options.theme)[0].className = 'tipsy-arrow' + this.options.theme + ' tipsy-arrow-' + gravity.charAt(0) + this.options.theme; |
| 107 | |
| 108 | if (this.options.fade) { |
| 109 | if(this.options.shadow) |
| 110 | $(".tipsy-inner").css({'box-shadow': '0px 0px '+this.options.shadowBlur+'px '+this.options.shadowSpread+'px rgba(0, 0, 0, '+this.options.shadowOpacity+')', '-webkit-box-shadow': '0px 0px '+this.options.shadowBlur+'px '+this.options.shadowSpread+'px rgba(0, 0, 0, '+this.options.shadowOpacity+')'}); |
| 111 | $tip.stop().css({opacity: 0, display: 'block', visibility: 'visible'}).animate({opacity: this.options.opacity}, this.options.fadeInTime); |
| 112 | } else { |
| 113 | $tip.css({visibility: 'visible', opacity: this.options.opacity}); |
| 114 | } |
| 115 | |
| 116 | if (this.options.aria) { |
| 117 | var $tipID = tipsyID(); |
| 118 | $tip.attr("id", $tipID); |
| 119 | this.$element.attr("aria-describedby", $tipID); |
| 120 | } |
| 121 | } |
| 122 | }, |
| 123 | |
| 124 | hide: function() { |
| 125 | if (this.options.fade) { |
| 126 | this.tip().stop().fadeOut(this.options.fadeOutTime, function() { $(this).remove(); }); |
| 127 | } else { |
| 128 | this.tip().remove(); |
| 129 | } |
| 130 | if (this.options.aria) { |
| 131 | this.$element.removeAttr("aria-describedby"); |
| 132 | } |
| 133 | }, |
| 134 | |
| 135 | fixTitle: function() { |
| 136 | var $e = this.$element, |
| 137 | id = maybeCall(this.options.id, this.$element[0]); |
| 138 | if ($e.prop('title') || typeof($e.prop('original-title')) != 'string') { |
| 139 | $e.prop('original-title', $e.prop('title') || '').removeAttr('title'); |
| 140 | // add aria-describedby pointing to the tooltip's id |
| 141 | $e.attr('aria-describedby', id); |
| 142 | // if it doesn't already have a tabindex, force the trigger element into the tab cycle |
| 143 | // to make it keyboard accessible with tabindex=0. this automatically makes elements |
| 144 | // that are not normally keyboard accessible (div or span) that have been tipsy-fied |
| 145 | // also operable with the keyboard. |
| 146 | if ($e.attr('tabindex') === undefined) { |
| 147 | $e.attr('tabindex', 0); |
| 148 | } |
| 149 | } |
| 150 | }, |
| 151 | |
| 152 | getTitle: function() { |
| 153 | var title, $e = this.$element, o = this.options; |
| 154 | this.fixTitle(); |
| 155 | if (typeof o.title == 'string') { |
| 156 | title = $e.prop(o.title == 'title' ? 'original-title' : o.title); |
| 157 | } else if (typeof o.title == 'function') { |
| 158 | title = o.title.call($e[0]); |
| 159 | } |
| 160 | title = ('' + title).replace(/(^\s*|\s*$)/, ""); |
| 161 | return title || o.fallback; |
| 162 | }, |
| 163 | |
| 164 | tip: function() { |
| 165 | var id = maybeCall(this.options.id, this.$element[0]); |
| 166 | |
| 167 | if (!this.$tip) { |
| 168 | // generate tooltip, with appropriate ARIA role and an 'id' (can be set in options), |
| 169 | // so it can be targetted by aria-describedby in the trigger element |
| 170 | this.$tip = $('<div class="tipsy' + this.options.theme + '" id="'+id+'" role="tooltip"></div>').html('<div class="tipsy-arrow' + this.options.theme + '"></div><div class="tipsy-inner' + this.options.theme + '"></div>').attr("role","tooltip"); |
| 171 | this.$tip.data('tipsy-pointee', this.$element[0]); |
| 172 | } |
| 173 | return this.$tip; |
| 174 | }, |
| 175 | |
| 176 | validate: function() { |
| 177 | if (!this.$element[0].parentNode) { |
| 178 | this.hide(); |
| 179 | this.$element = null; |
| 180 | this.options = null; |
| 181 | } |
| 182 | }, |
| 183 | |
| 184 | enable: function() { this.enabled = true; }, |
| 185 | disable: function() { this.enabled = false; }, |
| 186 | toggleEnabled: function() { this.enabled = !this.enabled; } |
| 187 | }; |
| 188 | |
| 189 | $.fn.tipsy = function(options) { |
| 190 | |
| 191 | $.fn.tipsy.enable(); |
| 192 | |
| 193 | if (options === true) { |
| 194 | return this.data('tipsy'); |
| 195 | } else if (typeof options == 'string') { |
| 196 | var tipsy = this.data('tipsy'); |
| 197 | if (tipsy) tipsy[options](); |
| 198 | return this; |
| 199 | } |
| 200 | |
| 201 | options = $.extend({}, $.fn.tipsy.defaults, options); |
| 202 | |
| 203 | // Establish theme |
| 204 | options.theme = (options.theme && options.theme !== '') ? '-' + options.theme : ''; |
| 205 | |
| 206 | function get(ele) { |
| 207 | var tipsy = $.data(ele, 'tipsy'); |
| 208 | if (!tipsy) { |
| 209 | tipsy = new Tipsy(ele, $.fn.tipsy.elementOptions(ele, options)); |
| 210 | $.data(ele, 'tipsy', tipsy); |
| 211 | } |
| 212 | return tipsy; |
| 213 | } |
| 214 | |
| 215 | function enter() { |
| 216 | if ($.fn.tipsy.enabled !== true) { |
| 217 | return; |
| 218 | } |
| 219 | var tipsy = get(this); |
| 220 | tipsy.hoverState = 'in'; |
| 221 | if (options.delayIn === 0) { |
| 222 | tipsy.show(); |
| 223 | } else { |
| 224 | tipsy.fixTitle(); |
| 225 | setTimeout(function() { |
| 226 | if (tipsy.hoverState == 'in' && isElementInDOM(tipsy.$element)) { |
| 227 | tipsy.show(); |
| 228 | } |
| 229 | }, options.delayIn); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | function leave() { |
| 234 | var tipsy = get(this); |
| 235 | tipsy.hoverState = 'out'; |
| 236 | if (options.delayOut === 0) { |
| 237 | tipsy.hide(); |
| 238 | } else { |
| 239 | setTimeout(function() { if (tipsy.hoverState == 'out' || !tipsy.$element || !tipsy.$element.is(':visible')) tipsy.hide(); }, options.delayOut); |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | if (!options.live) this.each(function() { get(this); }); |
| 244 | |
| 245 | if (options.trigger != 'manual') { |
| 246 | // one of the biggest changes from 1.0.0a: by default, bind to BOTH mouseenter/mouseleave AND focus/blur |
| 247 | // this ensures out-of-the-box keyboard accessibility, showing tooltips when users TAB to a (focusable) element |
| 248 | // sites that still use 'hover' will also get this new interactive behavior automatically, only those who |
| 249 | // explicitly set 'focus' will only get focus/blur (for forms, for instance) |
| 250 | |
| 251 | if (options.live && options.live !== true) { |
| 252 | if (options.trigger != 'focus') { |
| 253 | $(this).on('mouseenter', options.live, enter); |
| 254 | $(this).on('mouseleave', options.live, leave); |
| 255 | } |
| 256 | if (options.trigger != 'blur') { |
| 257 | $(this).on('focus', options.live, enter); |
| 258 | $(this).on('blur', options.live, leave); |
| 259 | } |
| 260 | } else { |
| 261 | if (options.live && !$.live) { |
| 262 | //live === true and using jQuery >= 1.9 |
| 263 | throw "Since jQuery 1.9, pass selector as live argument. eg. $(document).tipsy({live: 'a.live'});"; |
| 264 | } |
| 265 | var binder = options.live ? 'live' : 'bind'; |
| 266 | if (options.trigger != 'focus') { |
| 267 | this[binder]('mouseenter', enter)[binder]('mouseleave', leave); |
| 268 | } |
| 269 | if (options.trigger != 'blur') { |
| 270 | this[binder]('focus', enter)[binder]('blur', leave); |
| 271 | } |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | return this; |
| 276 | |
| 277 | }; |
| 278 | |
| 279 | $.fn.tipsy.defaults = { |
| 280 | aria: false, |
| 281 | className: null, |
| 282 | id: 'tipsy', |
| 283 | delayIn: 0, |
| 284 | delayOut: 0, |
| 285 | fade: false, |
| 286 | fadeInTime: 400, |
| 287 | fadeOutTime: 400, |
| 288 | shadow: false, |
| 289 | shadowBlur: 8, |
| 290 | shadowOpacity: 1, |
| 291 | shadowSpread: 0, |
| 292 | fallback: '', |
| 293 | gravity: 'n', |
| 294 | html: false, |
| 295 | live: false, |
| 296 | offset: 0, |
| 297 | opacity: 0.8, |
| 298 | title: 'title', |
| 299 | trigger: 'interactive', |
| 300 | theme: '' |
| 301 | }; |
| 302 | |
| 303 | $.fn.tipsy.revalidate = function() { |
| 304 | $('.tipsy').each(function() { |
| 305 | var pointee = $.data(this, 'tipsy-pointee'); |
| 306 | if (!pointee || !isElementInDOM(pointee)) { |
| 307 | $(this).remove(); |
| 308 | } |
| 309 | }); |
| 310 | }; |
| 311 | |
| 312 | $.fn.tipsy.enable = function() { |
| 313 | $.fn.tipsy.enabled = true; |
| 314 | }; |
| 315 | |
| 316 | $.fn.tipsy.disable = function() { |
| 317 | $.fn.tipsy.enabled = false; |
| 318 | }; |
| 319 | |
| 320 | // Overwrite this method to provide options on a per-element basis. |
| 321 | // For example, you could store the gravity in a 'tipsy-gravity' attribute: |
| 322 | // return $.extend({}, options, {gravity: $(ele).prop('tipsy-gravity') || 'n' }); |
| 323 | // (remember - do not modify 'options' in place!) |
| 324 | $.fn.tipsy.elementOptions = function(ele, options) { |
| 325 | return $.metadata ? $.extend({}, options, $(ele).metadata()) : options; |
| 326 | }; |
| 327 | |
| 328 | $.fn.tipsy.autoNS = function() { |
| 329 | return $(this).offset().top > ($(document).scrollTop() + $(window).height() / 2) ? 's' : 'n'; |
| 330 | }; |
| 331 | |
| 332 | $.fn.tipsy.autoWE = function() { |
| 333 | return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'e' : 'w'; |
| 334 | }; |
| 335 | |
| 336 | $.fn.tipsy.autoNWNE = function() { |
| 337 | return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'ne' : 'nw'; |
| 338 | }; |
| 339 | |
| 340 | $.fn.tipsy.autoSWSE = function() { |
| 341 | return $(this).offset().left > ($(document).scrollLeft() + $(window).width() / 2) ? 'se' : 'sw'; |
| 342 | }; |
| 343 | |
| 344 | /** |
| 345 | * yields a closure of the supplied parameters, producing a function that takes |
| 346 | * no arguments and is suitable for use as an autogravity function like so: |
| 347 | * |
| 348 | * @param marginNorth (int) - distance from the viewable region top edge that an |
| 349 | * element should be before setting its tooltip's gravity to be away |
| 350 | * from that edge. |
| 351 | * @param marginEast (int) - distance from the viewable region right edge that an |
| 352 | * element should be before setting its tooltip's gravity to be away |
| 353 | * from that edge. |
| 354 | * @param prefer (string, e.g. 'n', 'sw', 'w') - the direction to prefer |
| 355 | * if there are no viewable region edges effecting the tooltip's |
| 356 | * gravity. It will try to vary from this minimally, for example, |
| 357 | * if 'sw' is preferred and an element is near the right viewable |
| 358 | * region edge, but not the top edge, it will set the gravity for |
| 359 | * that element's tooltip to be 'se', preserving the southern |
| 360 | * component. |
| 361 | */ |
| 362 | $.fn.tipsy.autoBounds = function(marginNorth, marginEast, prefer) { |
| 363 | return function() { |
| 364 | var dir = {ns: prefer[0], ew: (prefer.length > 1 ? prefer[1] : false)}, |
| 365 | boundTop = $(document).scrollTop() + marginNorth, |
| 366 | boundLeft = $(document).scrollLeft() + marginEast, |
| 367 | $this = $(this); |
| 368 | |
| 369 | if ($this.offset().top < boundTop) dir.ns = 'n'; |
| 370 | if ($this.offset().left < boundLeft) dir.ew = 'w'; |
| 371 | if ($(window).width() + $(document).scrollLeft() - $this.offset().left < marginEast) dir.ew = 'e'; |
| 372 | if ($(window).height() + $(document).scrollTop() - $this.offset().top < marginNorth) dir.ns = 's'; |
| 373 | |
| 374 | return dir.ns + (dir.ew ? dir.ew : ''); |
| 375 | }; |
| 376 | }; |
| 377 | |
| 378 | /** |
| 379 | * Improved version of autoBounds for automatic placement of chunky tips |
| 380 | * The original autoBounds failed in two regards: 1. it would never return a 'w' or 'e', gravity even if they |
| 381 | * were preferred and/or optimal, 2. it only respected the margin between the left hand side of an element and |
| 382 | * left hand side of the viewport, and the top of an element and the top of the viewport. This version checks |
| 383 | * to see if the bottom of an element is too close to the bottom of the screen, similarly for the right hand side |
| 384 | */ |
| 385 | $.fn.tipsy.autoBounds2 = function(margin, prefer) { |
| 386 | return function() { |
| 387 | var dir = {}, |
| 388 | boundTop = $(document).scrollTop() + margin, |
| 389 | boundLeft = $(document).scrollLeft() + margin, |
| 390 | $this = $(this); |
| 391 | |
| 392 | // bi-directional string (ne, se, sw, etc...) |
| 393 | if (prefer.length > 1) { |
| 394 | dir.ns = prefer[0]; |
| 395 | dir.ew = prefer[1]; |
| 396 | } else { |
| 397 | // single direction string (e, w, n or s) |
| 398 | if (prefer[0] == 'e' || prefer[0] == 'w') { |
| 399 | dir.ew = prefer[0]; |
| 400 | } else { |
| 401 | dir.ns = prefer[0]; |
| 402 | } |
| 403 | } |
| 404 | |
| 405 | if ($this.offset().top < boundTop) dir.ns = 'n'; |
| 406 | if ($this.offset().left < boundLeft) dir.ew = 'w'; |
| 407 | if ($(window).width() + $(document).scrollLeft() - ($this.offset().left + $this.width()) < margin) dir.ew = 'e'; |
| 408 | if ($(window).height() + $(document).scrollTop() - ($this.offset().top + $this.height()) < margin) dir.ns = 's'; |
| 409 | |
| 410 | if (dir.ns) { |
| 411 | return dir.ns + (dir.ew ? dir.ew : ''); |
| 412 | } |
| 413 | return dir.ew; |
| 414 | } |
| 415 | }; |
| 416 | |
| 417 | })(jQuery, window); |
| 418 |