1/** 2 * Ajax Autocomplete for jQuery, version 1.2.24 3 * (c) 2015 Tomas Kirda 4 * 5 * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. 6 * For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete 7 */ 8 9/*jslint browser: true, white: true, plusplus: true, vars: true */ 10/*global define, window, document, jQuery, exports, require */ 11 12// Expose plugin as an AMD module if AMD loader is present: 13(function (factory) { 14 'use strict'; 15 if (typeof define === 'function' && define.amd) { 16 // AMD. Register as an anonymous module. 17 define(['jquery'], factory); 18 } else if (typeof exports === 'object' && typeof require === 'function') { 19 // Browserify 20 factory(require('jquery')); 21 } else { 22 // Browser globals 23 factory(jQuery); 24 } 25}(function ($) { 26 'use strict'; 27 28 var 29 utils = (function () { 30 return { 31 escapeRegExChars: function (value) { 32 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 33 }, 34 createNode: function (containerClass) { 35 var div = document.createElement('div'); 36 div.className = containerClass; 37 div.style.position = 'absolute'; 38 div.style.display = 'none'; 39 return div; 40 } 41 }; 42 }()), 43 44 keys = { 45 ESC: 27, 46 TAB: 9, 47 RETURN: 13, 48 LEFT: 37, 49 UP: 38, 50 RIGHT: 39, 51 DOWN: 40 52 }; 53 54 function Autocomplete(el, options) { 55 var noop = function () { }, 56 that = this, 57 defaults = { 58 ajaxSettings: {}, 59 autoSelectFirst: false, 60 appendTo: document.body, 61 serviceUrl: null, 62 lookup: null, 63 onSelect: null, 64 width: 'auto', 65 minChars: 1, 66 maxHeight: 300, 67 deferRequestBy: 0, 68 params: {}, 69 formatResult: Autocomplete.formatResult, 70 delimiter: null, 71 zIndex: 9999, 72 type: 'GET', 73 noCache: false, 74 onSearchStart: noop, 75 onSearchComplete: noop, 76 onSearchError: noop, 77 preserveInput: false, 78 containerClass: 'autocomplete-suggestions', 79 tabDisabled: false, 80 dataType: 'text', 81 currentRequest: null, 82 triggerSelectOnValidInput: true, 83 preventBadQueries: true, 84 lookupFilter: function (suggestion, originalQuery, queryLowerCase) { 85 return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1; 86 }, 87 paramName: 'query', 88 transformResult: function (response) { 89 return typeof response === 'string' ? $.parseJSON(response) : response; 90 }, 91 showNoSuggestionNotice: false, 92 noSuggestionNotice: 'No results', 93 orientation: 'bottom', 94 forceFixPosition: false 95 }; 96 97 // Shared variables: 98 that.element = el; 99 that.el = $(el); 100 that.suggestions = []; 101 that.badQueries = []; 102 that.selectedIndex = -1; 103 that.currentValue = that.element.value; 104 that.intervalId = 0; 105 that.cachedResponse = {}; 106 that.onChangeInterval = null; 107 that.onChange = null; 108 that.isLocal = false;
109 that.suggestionsContainer = null; 110 that.noSuggestionsContainer = null; 111 that.options = $.extend({}, defaults, options); 112 that.classes = { 113 selected: 'autocomplete-selected', 114 suggestion: 'autocomplete-suggestion' 115 }; 116 that.hint = null; 117 that.hintValue = ''; 118 that.selection = null; 119 120 // Initialize and set options: 121 that.initialize(); 122 that.setOptions(options); 123 } 124 125 Autocomplete.utils = utils; 126 127 $.Autocomplete = Autocomplete; 128 129 Autocomplete.formatResult = function (suggestion, currentValue) { 130 var pattern = '(' + utils.escapeRegExChars(currentValue) + ')'; 131 132 return suggestion.value 133 .replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>') 134 .replace(/&/g, '&') 135 .replace(/</g, '<') 136 .replace(/>/g, '>') 137 .replace(/"/g, '"') 138 .replace(/<(\/?strong)>/g, '<$1>'); 139 }; 140 141 Autocomplete.prototype = { 142 143 killerFn: null, 144 145 initialize: function () { 146 var that = this, 147 suggestionSelector = '.' + that.classes.suggestion, 148 selected = that.classes.selected, 149 options = that.options, 150 container; 151 152 // Remove autocomplete attribute to prevent native suggestions: 153 that.element.setAttribute('autocomplete', 'off'); 154 155 that.killerFn = function (e) { 156 if ($(e.target).closest('.' + that.options.containerClass).length === 0) { 157 that.killSuggestions(); 158 that.disableKillerFn(); 159 } 160 }; 161 162 // html() deals with many types: htmlString or Element or Array or jQuery
163 that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>') 164 .html(this.options.noSuggestionNotice).get(0); 165 166 that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass); 167 168 container = $(that.suggestionsContainer); 169 170 container.appendTo(options.appendTo); 171 172 // Only set width if it was provided: 173 if (options.width !== 'auto') { 174 container.width(options.width); 175 } 176 177 // Listen for mouse over event on suggestions list: 178 container.on('mouseover.autocomplete', suggestionSelector, function () { 179 that.activate($(this).data('index')); 180 }); 181 182 // Deselect active element when mouse leaves suggestions container: 183 container.on('mouseout.autocomplete', function () { 184 that.selectedIndex = -1; 185 container.children('.' + selected).removeClass(selected); 186 }); 187 188 // Listen for click event on suggestions list: 189 container.on('click.autocomplete', suggestionSelector, function () { 190 that.select($(this).data('index')); 191 }); 192 193 that.fixPositionCapture = function () { 194 if (that.visible) { 195 that.fixPosition(); 196 } 197 }; 198 199 $(window).on('resize.autocomplete', that.fixPositionCapture); 200 201 that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); }); 202 that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); }); 203 that.el.on('blur.autocomplete', function () { that.onBlur(); }); 204 that.el.on('focus.autocomplete', function () { that.onFocus(); }); 205 that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); }); 206 that.el.on('input.autocomplete', function (e) { that.onKeyUp(e); }); 207 }, 208 209 onFocus: function () { 210 var that = this; 211 that.fixPosition(); 212 if (that.options.minChars === 0 && that.el.val().length === 0) { 213 that.onValueChange(); 214 } 215 }, 216 217 onBlur: function () { 218 this.enableKillerFn(); 219 }, 220 221 abortAjax: function () { 222 var that = this; 223 if (that.currentRequest) { 224 that.currentRequest.abort(); 225 that.currentRequest = null; 226 } 227 }, 228 229 setOptions: function (suppliedOptions) { 230 var that = this, 231 options = that.options; 232 233 $.extend(options, suppliedOptions); 234 235 that.isLocal = $.isArray(options.lookup); 236 237 if (that.isLocal) { 238 options.lookup = that.verifySuggestionsFormat(options.lookup); 239 } 240 241 options.orientation = that.validateOrientation(options.orientation, 'bottom'); 242 243 // Adjust height, width and z-index: 244 $(that.suggestionsContainer).css({ 245 'max-height': options.maxHeight + 'px', 246 'width': options.width + 'px', 247 'z-index': options.zIndex 248 }); 249 }, 250 251 252 clearCache: function () { 253 this.cachedResponse = {}; 254 this.badQueries = []; 255 }, 256 257 clear: function () { 258 this.clearCache(); 259 this.currentValue = ''; 260 this.suggestions = []; 261 }, 262 263 disable: function () { 264 var that = this; 265 that.disabled = true; 266 clearInterval(that.onChangeInterval); 267 that.abortAjax(); 268 }, 269 270 enable: function () { 271 this.disabled = false; 272 }, 273 274 fixPosition: function () { 275 // Use only when container has already its content 276 277 var that = this, 278 $container = $(that.suggestionsContainer), 279 containerParent = $container.parent().get(0); 280 // Fix position automatically when appended to body. 281 // In other cases force parameter must be given. 282 if (containerParent !== document.body && !that.options.forceFixPosition) { 283 return; 284 } 285 286 // Choose orientation 287 var orientation = that.options.orientation, 288 containerHeight = $container.outerHeight(), 289 height = that.el.outerHeight(), 290 offset = that.el.offset(), 291 styles = { 'top': offset.top, 'left': offset.left }; 292 293 if (orientation === 'auto') { 294 var viewPortHeight = $(window).height(), 295 scrollTop = $(window).scrollTop(), 296 topOverflow = -scrollTop + offset.top - containerHeight, 297 bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight); 298 299 orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow) ? 'top' : 'bottom'; 300 } 301 302 if (orientation === 'top') { 303 styles.top += -containerHeight; 304 } else { 305 styles.top += height; 306 } 307 308 // If container is not positioned to body, 309 // correct its position using offset parent offset 310 if(containerParent !== document.body) { 311 var opacity = $container.css('opacity'), 312 parentOffsetDiff; 313 314 if (!that.visible){ 315 $container.css('opacity', 0).show(); 316 } 317 318 parentOffsetDiff = $container.offsetParent().offset(); 319 styles.top -= parentOffsetDiff.top; 320 styles.left -= parentOffsetDiff.left; 321 322 if (!that.visible){ 323 $container.css('opacity', opacity).hide(); 324 } 325 } 326 327 // -2px to account for suggestions border. 328 if (that.options.width === 'auto') { 329 styles.width = (that.el.outerWidth() - 2) + 'px'; 330 } 331 332 $container.css(styles); 333 }, 334 335 enableKillerFn: function () { 336 var that = this; 337 $(document).on('click.autocomplete', that.killerFn); 338 }, 339 340 disableKillerFn: function () { 341 var that = this; 342 $(document).off('click.autocomplete', that.killerFn); 343 }, 344 345 killSuggestions: function () { 346 var that = this; 347 that.stopKillSuggestions(); 348 that.intervalId = window.setInterval(function () { 349 if (that.visible) { 350 that.el.val(that.currentValue); 351 that.hide(); 352 } 353 354 that.stopKillSuggestions(); 355 }, 50); 356 }, 357 358 stopKillSuggestions: function () { 359 window.clearInterval(this.intervalId); 360 }, 361 362 isCursorAtEnd: function () { 363 var that = this, 364 valLength = that.el.val().length, 365 selectionStart = that.element.selectionStart, 366 range; 367 368 if (typeof selectionStart === 'number') { 369 return selectionStart === valLength; 370 } 371 if (document.selection) { 372 range = document.selection.createRange(); 373 range.moveStart('character', -valLength); 374 return valLength === range.text.length; 375 } 376 return true; 377 }, 378 379 onKeyPress: function (e) { 380 var that = this; 381 382 // If suggestions are hidden and user presses arrow down, display suggestions: 383 if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) { 384 that.suggest(); 385 return; 386 } 387 388 if (that.disabled || !that.visible) { 389 return; 390 } 391 392 switch (e.which) { 393 case keys.ESC: 394 that.el.val(that.currentValue); 395 that.hide(); 396 break; 397 case keys.RIGHT: 398 if (that.hint && that.options.onHint && that.isCursorAtEnd()) { 399 that.selectHint(); 400 break; 401 } 402 return; 403 case keys.TAB: 404 if (that.hint && that.options.onHint) { 405 that.selectHint(); 406 return; 407 } 408 if (that.selectedIndex === -1) { 409 that.hide(); 410 return; 411 } 412 that.select(that.selectedIndex); 413 if (that.options.tabDisabled === false) { 414 return; 415 } 416 break; 417 case keys.RETURN: 418 if (that.selectedIndex === -1) { 419 that.hide(); 420 return; 421 } 422 that.select(that.selectedIndex); 423 break; 424 case keys.UP: 425 that.moveUp(); 426 break; 427 case keys.DOWN: 428 that.moveDown(); 429 break; 430 default: 431 return; 432 } 433 434 // Cancel event if function did not return: 435 e.stopImmediatePropagation(); 436 e.preventDefault(); 437 }, 438 439 onKeyUp: function (e) { 440 var that = this; 441 442 if (that.disabled) { 443 return; 444 } 445 446 switch (e.which) { 447 case keys.UP:
448 case keys.DOWN: 449 return; 450 } 451 452 clearInterval(that.onChangeInterval); 453 454 if (that.currentValue !== that.el.val()) { 455 that.findBestHint(); 456 if (that.options.deferRequestBy > 0) { 457 // Defer lookup in case when value changes very quickly: 458 that.onChangeInterval = setInterval(function () { 459 that.onValueChange(); 460 }, that.options.deferRequestBy); 461 } else { 462 that.onValueChange(); 463 } 464 } 465 }, 466 467 onValueChange: function () { 468 var that = this, 469 options = that.options, 470 value = that.el.val(), 471 query = that.getQuery(value); 472 473 if (that.selection && that.currentValue !== query) { 474 that.selection = null; 475 (options.onInvalidateSelection || $.noop).call(that.element); 476 } 477 478 clearInterval(that.onChangeInterval); 479 that.currentValue = value; 480 that.selectedIndex = -1; 481 482 // Check existing suggestion for the match before proceeding: 483 if (options.triggerSelectOnValidInput && that.isExactMatch(query)) { 484 that.select(0); 485 return; 486 } 487 488 if (query.length < options.minChars) { 489 that.hide(); 490 } else { 491 that.getSuggestions(query); 492 } 493 }, 494 495 isExactMatch: function (query) { 496 var suggestions = this.suggestions; 497 498 return (suggestions.length === 1 && suggestions[0].value.toLowerCase() === query.toLowerCase()); 499 }, 500 501 getQuery: function (value) { 502 var delimiter = this.options.delimiter, 503 parts; 504 505 if (!delimiter) { 506 return value; 507 } 508 parts = value.split(delimiter); 509 return $.trim(parts[parts.length - 1]); 510 }, 511 512 getSuggestionsLocal: function (query) { 513 var that = this, 514 options = that.options, 515 queryLowerCase = query.toLowerCase(), 516 filter = options.lookupFilter, 517 limit = parseInt(options.lookupLimit, 10), 518 data; 519 520 data = { 521 suggestions: $.grep(options.lookup, function (suggestion) { 522 return filter(suggestion, query, queryLowerCase); 523 }) 524 }; 525 526 if (limit && data.suggestions.length > limit) { 527 data.suggestions = data.suggestions.slice(0, limit); 528 } 529 530 return data; 531 }, 532 533 getSuggestions: function (q) { 534 var response, 535 that = this, 536 options = that.options, 537 serviceUrl = options.serviceUrl, 538 params, 539 cacheKey, 540 ajaxSettings; 541 542 options.params[options.paramName] = q; 543 params = options.ignoreParams ? null : options.params; 544 545 if (options.onSearchStart.call(that.element, options.params) === false) { 546 return; 547 } 548 549 if ($.isFunction(options.lookup)){ 550 options.lookup(q, function (data) { 551 that.suggestions = data.suggestions; 552 that.suggest(); 553 options.onSearchComplete.call(that.element, q, data.suggestions); 554 }); 555 return; 556 } 557 558 if (that.isLocal) { 559 response = that.getSuggestionsLocal(q); 560 } else { 561 if ($.isFunction(serviceUrl)) { 562 serviceUrl = serviceUrl.call(that.element, q); 563 } 564 cacheKey = serviceUrl + '?' + $.param(params || {}); 565 response = that.cachedResponse[cacheKey]; 566 } 567 568 if (response && $.isArray(response.suggestions)) { 569 that.suggestions = response.suggestions; 570 that.suggest(); 571 options.onSearchComplete.call(that.element, q, response.suggestions); 572 } else if (!that.isBadQuery(q)) { 573 that.abortAjax(); 574 575 ajaxSettings = { 576 url: serviceUrl, 577 data: params, 578 type: options.type, 579 dataType: options.dataType 580 }; 581 582 $.extend(ajaxSettings, options.ajaxSettings); 583 584 that.currentRequest = $.ajax(ajaxSettings).done(function (data) { 585 var result; 586 that.currentRequest = null; 587 result = options.transformResult(data, q); 588 that.processResponse(result, q, cacheKey); 589 options.onSearchComplete.call(that.element, q, result.suggestions); 590 }).fail(function (jqXHR, textStatus, errorThrown) { 591 options.onSearchError.call(that.element, q, jqXHR, textStatus, errorThrown); 592 }); 593 } else { 594 options.onSearchComplete.call(that.element, q, []); 595 } 596 }, 597 598 isBadQuery: function (q) { 599 if (!this.options.preventBadQueries){ 600 return false; 601 } 602 603 var badQueries = this.badQueries, 604 i = badQueries.length; 605 606 while (i--) { 607 if (q.indexOf(badQueries[i]) === 0) { 608 return true; 609 } 610 } 611 612 return false;
613 }, 614 615 hide: function () { 616 var that = this, 617 container = $(that.suggestionsContainer); 618 619 if ($.isFunction(that.options.onHide) && that.visible) { 620 that.options.onHide.call(that.element, container); 621 } 622 623 that.visible = false; 624 that.selectedIndex = -1; 625 clearInterval(that.onChangeInterval); 626 $(that.suggestionsContainer).hide(); 627 that.signalHint(null); 628 }, 629 630 suggest: function () { 631 if (this.suggestions.length === 0) { 632 if (this.options.showNoSuggestionNotice) { 633 this.noSuggestions(); 634 } else { 635 this.hide(); 636 } 637 return; 638 } 639 640 var that = this, 641 options = that.options, 642 groupBy = options.groupBy, 643 formatResult = options.formatResult, 644 value = that.getQuery(that.currentValue), 645 className = that.classes.suggestion, 646 classSelected = that.classes.selected, 647 container = $(that.suggestionsContainer), 648 noSuggestionsContainer = $(that.noSuggestionsContainer), 649 beforeRender = options.beforeRender, 650 html = '', 651 category, 652 formatGroup = function (suggestion, index) { 653 var currentCategory = suggestion.data[groupBy]; 654 655 if (category === currentCategory){ 656 return ''; 657 } 658 659 category = currentCategory; 660 661 return '<div class="autocomplete-group"><strong>' + category + '</strong></div>'; 662 }; 663 664 if (options.triggerSelectOnValidInput && that.isExactMatch(value)) { 665 that.select(0); 666 return; 667 } 668 669 // Build suggestions inner HTML: 670 $.each(that.suggestions, function (i, suggestion) { 671 if (groupBy){ 672 html += formatGroup(suggestion, value, i); 673 } 674 675 html += '<div class="'+ ( PAKB.category ? 'autocomplete-with-category ' : '' ) + className + '" data-index="' + i + '"><span class="'+ suggestion.icon +'"></span> ' + formatResult(suggestion, value) + '</div>'; 676 }); 677 678 this.adjustContainerWidth(); 679 680 noSuggestionsContainer.detach(); 681 container.html(html); 682 683 if ($.isFunction(beforeRender)) { 684 beforeRender.call(that.element, container); 685 } 686 687 that.fixPosition(); 688 container.show(); 689 690 // Select first value by default: 691 if (options.autoSelectFirst) { 692 that.selectedIndex = 0; 693 container.scrollTop(0); 694 container.children('.' + className).first().addClass(classSelected); 695 } 696 697 that.visible = true; 698 that.findBestHint(); 699 }, 700 701 noSuggestions: function() { 702 var that = this, 703 container = $(that.suggestionsContainer), 704 noSuggestionsContainer = $(that.noSuggestionsContainer); 705 706 this.adjustContainerWidth(); 707 708 // Some explicit steps. Be careful here as it easy to get 709 // noSuggestionsContainer removed from DOM if not detached properly. 710 noSuggestionsContainer.detach(); 711 container.empty(); // clean suggestions if any 712 container.append(noSuggestionsContainer); 713 714 that.fixPosition(); 715 716 container.show(); 717 that.visible = true; 718 }, 719 720 adjustContainerWidth: function() { 721 var that = this, 722 options = that.options, 723 width, 724 container = $(that.suggestionsContainer); 725 726 // If width is auto, adjust width before displaying suggestions, 727 // because if instance was created before input had width, it will be zero. 728 // Also it adjusts if input width has changed. 729 // -2px to account for suggestions border. 730 if (options.width === 'auto') { 731 width = that.el.outerWidth() - 2; 732 container.width(width > 0 ? width : 300); 733 } 734 }, 735 736 findBestHint: function () { 737 var that = this, 738 value = that.el.val().toLowerCase(), 739 bestMatch = null; 740 741 if (!value) { 742 return; 743 } 744 745 $.each(that.suggestions, function (i, suggestion) { 746 var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0; 747 if (foundMatch) { 748 bestMatch = suggestion; 749 } 750 return !foundMatch; 751 }); 752 753 that.signalHint(bestMatch); 754 }, 755 756 signalHint: function (suggestion) { 757 var hintValue = '', 758 that = this; 759 if (suggestion) { 760 hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length); 761 } 762 if (that.hintValue !== hintValue) { 763 that.hintValue = hintValue; 764 that.hint = suggestion; 765 (this.options.onHint || $.noop)(hintValue); 766 } 767 }, 768 769 verifySuggestionsFormat: function (suggestions) {
770 // If suggestions is string array, convert them to supported format: 771 if (suggestions.length && typeof suggestions[0] === 'string') { 772 return $.map(suggestions, function (value) { 773 return { value: value, data: null }; 774 }); 775 } 776 777 return suggestions; 778 }, 779 780 validateOrientation: function(orientation, fallback) { 781 orientation = $.trim(orientation || '').toLowerCase(); 782 783 if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){ 784 orientation = fallback; 785 } 786 787 return orientation; 788 }, 789 790 processResponse: function (result, originalQuery, cacheKey) { 791 var that = this, 792 options = that.options; 793 794 result.suggestions = that.verifySuggestionsFormat(result.suggestions); 795 796 // Cache results if cache is not disabled: 797 if (!options.noCache) { 798 that.cachedResponse[cacheKey] = result; 799 if (options.preventBadQueries && result.suggestions.length === 0) { 800 that.badQueries.push(originalQuery); 801 } 802 } 803 804 // Return if originalQuery is not matching current query: 805 if (originalQuery !== that.getQuery(that.currentValue)) { 806 return; 807 } 808 809 that.suggestions = result.suggestions; 810 that.suggest(); 811 }, 812 813 activate: function (index) { 814 var that = this, 815 activeItem, 816 selected = that.classes.selected, 817 container = $(that.suggestionsContainer), 818 children = container.find('.' + that.classes.suggestion); 819 820 container.find('.' + selected).removeClass(selected); 821 822 that.selectedIndex = index; 823 824 if (that.selectedIndex !== -1 && children.length > that.selectedIndex) { 825 activeItem = children.get(that.selectedIndex); 826 $(activeItem).addClass(selected); 827 return activeItem; 828 } 829 830 return null; 831 }, 832 833 selectHint: function () { 834 var that = this, 835 i = $.inArray(that.hint, that.suggestions); 836 837 that.select(i); 838 }, 839 840 select: function (i) { 841 var that = this; 842 that.hide(); 843 that.onSelect(i); 844 }, 845 846 moveUp: function () { 847 var that = this; 848 849 if (that.selectedIndex === -1) { 850 return; 851 } 852 853 if (that.selectedIndex === 0) { 854 $(that.suggestionsContainer).children().first().removeClass(that.classes.selected); 855 that.selectedIndex = -1; 856 that.el.val(that.currentValue); 857 that.findBestHint(); 858 return; 859 } 860 861 that.adjustScroll(that.selectedIndex - 1); 862 }, 863 864 moveDown: function () { 865 var that = this; 866 867 if (that.selectedIndex === (that.suggestions.length - 1)) { 868 return; 869 } 870 871 that.adjustScroll(that.selectedIndex + 1); 872 }, 873 874 adjustScroll: function (index) { 875 var that = this, 876 activeItem = that.activate(index); 877 878 if (!activeItem) { 879 return; 880 } 881 882 var offsetTop, 883 upperBound, 884 lowerBound, 885 heightDelta = $(activeItem).outerHeight(); 886 887 offsetTop = activeItem.offsetTop; 888 upperBound = $(that.suggestionsContainer).scrollTop(); 889 lowerBound = upperBound + that.options.maxHeight - heightDelta; 890 891 if (offsetTop < upperBound) { 892 $(that.suggestionsContainer).scrollTop(offsetTop); 893 } else if (offsetTop > lowerBound) { 894 $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta); 895 } 896 897 if (!that.options.preserveInput) { 898 that.el.val(that.getValue(that.suggestions[index].value)); 899 } 900 that.signalHint(null); 901 }, 902 903 onSelect: function (index) { 904 var that = this, 905 onSelectCallback = that.options.onSelect, 906 suggestion = that.suggestions[index]; 907 908 that.currentValue = that.getValue(suggestion.value); 909 910 if (that.currentValue !== that.el.val() && !that.options.preserveInput) { 911 that.el.val(that.currentValue); 912 } 913 914 that.signalHint(null); 915 that.suggestions = []; 916 that.selection = suggestion; 917 918 if ($.isFunction(onSelectCallback)) { 919 onSelectCallback.call(that.element, suggestion); 920 } 921 }, 922 923 getValue: function (value) { 924 var that = this, 925 delimiter = that.options.delimiter, 926 currentValue, 927 parts; 928 929 if (!delimiter) { 930 return value; 931 } 932 933 currentValue = that.currentValue; 934 parts = currentValue.split(delimiter); 935 936 if (parts.length === 1) { 937 return value; 938 } 939 940 return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value; 941 }, 942 943 dispose: function () { 944 var that = this; 945 that.el.off('.autocomplete').removeData('autocomplete'); 946 that.disableKillerFn(); 947 $(window).off('resize.autocomplete', that.fixPositionCapture); 948 $(that.suggestionsContainer).remove(); 949 } 950 }; 951 952 // Create chainable jQuery plugin: 953 $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) { 954 var dataKey = 'autocomplete'; 955 // If function invoked without argument return 956 // instance of the first matched element: 957 if (arguments.length === 0) { 958 return this.first().data(dataKey); 959 } 960 961 return this.each(function () { 962 var inputElement = $(this), 963 instance = inputElement.data(dataKey); 964 965 if (typeof options === 'string') { 966 if (instance && typeof instance[options] === 'function') { 967 instance[options](args); 968 } 969 } else { 970 // If instance already exists, destroy it: 971 if (instance && instance.dispose) { 972 instance.dispose(); 973 } 974 instance = new Autocomplete(this, options); 975 inputElement.data(dataKey, instance); 976 } 977 }); 978 }; 979})); 980/* 981 982Tooltipster 3.3.0 | 2014-11-08 983A rockin' custom tooltip jQuery plugin 984 985Developed by Caleb Jacob under the MIT license http://opensource.org/licenses/MIT 986 987THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 988 989*/ 990 991;(function ($, window, document) { 992 993 var pluginName = "tooltipster", 994 defaults = { 995 animation: 'fade', 996 arrow: true, 997 arrowColor: '', 998 autoClose: true, 999 content: null,
1000 contentAsHTML: false, 1001 contentCloning: true, 1002 debug: true, 1003 delay: 200, 1004 minWidth: 0, 1005 maxWidth: null, 1006 functionInit: function(origin, content) {}, 1007 functionBefore: function(origin, continueTooltip) { 1008 continueTooltip(); 1009 }, 1010 functionReady: function(origin, tooltip) {}, 1011 functionAfter: function(origin) {}, 1012 hideOnClick: false, 1013 icon: '(?)', 1014 iconCloning: true, 1015 iconDesktop: false, 1016 iconTouch: false, 1017 iconTheme: 'tooltipster-icon', 1018 interactive: false, 1019 interactiveTolerance: 350, 1020 multiple: false, 1021 offsetX: 0, 1022 offsetY: 0, 1023 onlyOne: false, 1024 position: 'top', 1025 positionTracker: false, 1026 positionTrackerCallback: function(origin){ 1027 // the default tracker callback will close the tooltip when the trigger is 1028 // 'hover' (see https://github.com/iamceege/tooltipster/pull/253) 1029 if(this.option('trigger') == 'hover' && this.option('autoClose')) { 1030 this.hide(); 1031 } 1032 }, 1033 restoration: 'current', 1034 speed: 350, 1035 timer: 0, 1036 theme: 'tooltipster-default', 1037 touchDevices: true, 1038 trigger: 'hover', 1039 updateAnimation: true 1040 }; 1041 1042 function Plugin(element, options) { 1043 1044 // list of instance variables 1045 1046 this.bodyOverflowX; 1047 // stack of custom callbacks provided as parameters to API methods 1048 this.callbacks = { 1049 hide: [], 1050 show: [] 1051 }; 1052 this.checkInterval = null; 1053 // this will be the user content shown in the tooltip. A capital "C" is used because there is also a method called content() 1054 this.Content; 1055 // this is the original element which is being applied the tooltipster plugin 1056 this.$el = $(element); 1057 // this will be the element which triggers the appearance of the tooltip on hover/click/custom events. 1058 // it will be the same as this.$el if icons are not used (see in the options), otherwise it will correspond to the created icon 1059 this.$elProxy; 1060 this.elProxyPosition; 1061 this.enabled = true; 1062 this.options = $.extend({}, defaults, options); 1063 this.mouseIsOverProxy = false; 1064 // a unique namespace per instance, for easy selective unbinding 1065 this.namespace = 'tooltipster-'+ Math.round(Math.random()*100000); 1066 // Status (capital S) can be either : appearing, shown, disappearing, hidden 1067 this.Status = 'hidden'; 1068 this.timerHide = null; 1069 this.timerShow = null; 1070 // this will be the tooltip element (jQuery wrapped HTML element) 1071 this.$tooltip; 1072 1073 // for backward compatibility 1074 this.options.iconTheme = this.options.iconTheme.replace('.', ''); 1075 this.options.theme = this.options.theme.replace('.', ''); 1076 1077 // launch 1078 1079 this._init(); 1080 } 1081 1082 Plugin.prototype = { 1083 1084 _init: function() { 1085 1086 var self = this; 1087 1088 // disable the plugin on old browsers (including IE7 and lower) 1089 if (document.querySelector) { 1090 1091 // note : the content is null (empty) by default and can stay that way if the plugin remains initialized but not fed any content. The tooltip will just not appear. 1092 1093 // let's save the initial value of the title attribute for later restoration if need be. 1094 var initialTitle = null; 1095 // it will already have been saved in case of multiple tooltips 1096 if (self.$el.data('tooltipster-initialTitle') === undefined) { 1097 1098 initialTitle = self.$el.attr('title'); 1099 1100 // we do not want initialTitle to have the value "undefined" because of how jQuery's .data() method works 1101 if (initialTitle === undefined) initialTitle = null; 1102 1103 self.$el.data('tooltipster-initialTitle', initialTitle); 1104 } 1105 1106 // if content is provided in the options, its has precedence over the title attribute. 1107 // Note : an empty string is considered content, only 'null' represents the absence of content. 1108 // Also, an existing title="" attribute will result in an empty string content 1109 if (self.options.content !== null){ 1110 self._content_set(self.options.content); 1111 } 1112 else { 1113 self._content_set(initialTitle); 1114 } 1115 1116 var c = self.options.functionInit.call(self.$el, self.$el, self.Content); 1117 if(typeof c !== 'undefined') self._content_set(c); 1118 1119 self.$el 1120 // strip the title off of the element to prevent the default tooltips from popping up 1121 .removeAttr('title') 1122 // to be able to find all instances on the page later (upon window events in particular) 1123 .addClass('tooltipstered'); 1124 1125 // detect if we're changing the tooltip origin to an icon 1126 // note about this condition : if the device has touch capability and self.options.iconTouch is false, you'll have no icons event though you may consider your device as a desktop if it also has a mouse. Not sure why someone would have this use case though. 1127 if ((!deviceHasTouchCapability && self.options.iconDesktop) || (deviceHasTouchCapability && self.options.iconTouch)) { 1128 1129 // TODO : the tooltip should be automatically be given an absolute position to be near the origin. Otherwise, when the origin
1129is floating or what, it's going to be nowhere near it and disturb the position flow of the page elements. It will imply that the icon also detects when its origin moves, to follow it : not trivial. 1130 // Until it's done, the icon feature does not really make sense since the user still has most of the work to do by himself 1131 1132 // if the icon provided is in the form of a string 1133 if(typeof self.options.icon === 'string'){ 1134 // wrap it in a span with the icon class 1135 self.$elProxy = $('<span class="'+ self.options.iconTheme +'"></span>'); 1136 self.$elProxy.text(self.options.icon); 1137 } 1138 // if it is an object (sensible choice) 1139 else { 1140 // (deep) clone the object if iconCloning == true, to make sure every instance has its own proxy. We use the icon without wrapping, no need to. We do not give it a class either, as the user will undoubtedly style the object on his own and since our css properties may conflict with his own 1141 if (self.options.iconCloning) self.$elProxy = self.options.icon.clone(true); 1142 else self.$elProxy = self.options.icon; 1143 } 1144 1145 self.$elProxy.insertAfter(self.$el); 1146 } 1147 else { 1148 self.$elProxy = self.$el; 1149 } 1150 1151 // for 'click' and 'hover' triggers : bind on events to open the tooltip. Closing is now handled in _showNow() because of its bindings. 1152 // Notes about touch events : 1153 // - mouseenter, mouseleave and clicks happen even on pure touch devices because they are emulated. deviceIsPureTouch() is a simple attempt to detect them. 1154 // - on hybrid devices, we do not prevent touch gesture from opening tooltips. It would be too complex to differentiate real mouse events from emulated ones. 1155 // - we check deviceIsPureTouch() at each event rather than prior to binding because the situation may change during browsing 1156 if (self.options.trigger == 'hover') { 1157 1158 // these binding are for mouse interaction only 1159 self.$elProxy 1160 .on('mouseenter.'+ self.namespace, function() { 1161 if (!deviceIsPureTouch() || self.options.touchDevices) { 1162 self.mouseIsOverProxy = true; 1163 self._show(); 1164 } 1165 }) 1166 .on('mouseleave.'+ self.namespace, function() { 1167 if (!deviceIsPureTouch() || self.options.touchDevices) { 1168 self.mouseIsOverProxy = false; 1169 } 1170 }); 1171 1172 // for touch interaction only 1173 if (deviceHasTouchCapability && self.options.touchDevices) { 1174 1175 // for touch devices, we immediately display the tooltip because we cannot rely on mouseleave to handle the delay 1176 self.$elProxy.on('touchstart.'+ self.namespace, function() { 1177 self._showNow(); 1178 }); 1179 } 1180 } 1181 else if (self.options.trigger == 'click') { 1182 1183 // note : for touch devices, we do not bind on touchstart, we only rely on the emulated clicks (triggered by taps) 1184 self.$elProxy.on('click.'+ self.namespace, function() { 1185 if (!deviceIsPureTouch() || self.options.touchDevices) { 1186 self._show(); 1187 } 1188 }); 1189 } 1190 } 1191 }, 1192 1193 // this function will schedule the opening of the tooltip after the delay, if there is one 1194 _show: function() { 1195 1196 var self = this; 1197 1198 if (self.Status != 'shown' && self.Status != 'appearing') { 1199 1200 if (self.options.delay) { 1201 self.timerShow = setTimeout(function(){ 1202 1203 // for hover trigger, we check if the mouse is still over the proxy, otherwise we do not show anything 1204 if (self.options.trigger == 'click' || (self.options.trigger == 'hover' && self.mouseIsOverProxy)) { 1205 self._showNow(); 1206 } 1207 }, self.options.delay); 1208 } 1209 else self._showNow(); 1210 } 1211 }, 1212 1213 // this function will open the tooltip right away 1214 _showNow: function(callback) { 1215 1216 var self = this; 1217 1218 // call our constructor custom function before continuing 1219 self.options.functionBefore.call(self.$el, self.$el, function() { 1220 1221 // continue only if the tooltip is enabled and has any content 1222 if (self.enabled && self.Content !== null) { 1223 1224 // save the method callback and cancel hide method callbacks 1225 if (callback) self.callbacks.show.push(callback); 1226 self.callbacks.hide = []; 1227 1228 //get rid of any appearance timer 1229 clearTimeout(self.timerShow); 1230 self.timerShow = null; 1231 clearTimeout(self.timerHide); 1232 self.timerHide = null; 1233 1234 // if we only want one tooltip open at a time, close all auto-closing tooltips currently open and not already disappearing 1235 if (self.options.onlyOne) { 1236 $('.tooltipstered').not(self.$el).each(function(i,el) { 1237 1238 var $el = $(el), 1239 nss = $el.data('tooltipster-ns'); 1240 1241 // iterate on all tooltips of the element 1242 $.each(nss, function(i, ns){ 1243 var instance = $el.data(ns), 1244 // we have to use the public methods here 1245 s = instance.status(), 1246 ac = instance.option('autoClose'); 1247 1248 if (s !== 'hidden' && s !== 'disappearing' && ac) { 1249 instance.hide(); 1250 } 1251 });
1252 }); 1253 } 1254 1255 var finish = function() { 1256 self.Status = 'shown'; 1257 1258 // trigger any show method custom callbacks and reset them 1259 $.each(self.callbacks.show, function(i,c) { c.call(self.$el); }); 1260 self.callbacks.show = []; 1261 }; 1262 1263 // if this origin already has its tooltip open 1264 if (self.Status !== 'hidden') { 1265 1266 // the timer (if any) will start (or restart) right now 1267 var extraTime = 0; 1268 1269 // if it was disappearing, cancel that 1270 if (self.Status === 'disappearing') { 1271 1272 self.Status = 'appearing'; 1273 1274 if (supportsTransitions()) { 1275 1276 self.$tooltip 1277 .clearQueue() 1278 .removeClass('tooltipster-dying') 1279 .addClass('tooltipster-'+ self.options.animation +'-show'); 1280 1281 if (self.options.speed > 0) self.$tooltip.delay(self.options.speed); 1282 1283 self.$tooltip.queue(finish); 1284 } 1285 else { 1286 // in case the tooltip was currently fading out, bring it back to life 1287 self.$tooltip 1288 .stop() 1289 .fadeIn(finish); 1290 } 1291 } 1292 // if the tooltip is already open, we still need to trigger the method custom callback 1293 else if(self.Status === 'shown') { 1294 finish(); 1295 } 1296 } 1297 // if the tooltip isn't already open, open that sucker up! 1298 else { 1299 1300 self.Status = 'appearing'; 1301 1302 // the timer (if any) will start when the tooltip has fully appeared after its transition 1303 var extraTime = self.options.speed; 1304 1305 // disable horizontal scrollbar to keep overflowing tooltips from jacking with it and then restore it to its previous value 1306 self.bodyOverflowX = $('body').css('overflow-x'); 1307 $('body').css('overflow-x', 'hidden'); 1308 1309 // get some other settings related to building the tooltip 1310 var animation = 'tooltipster-' + self.options.animation, 1311 animationSpeed = '-webkit-transition-duration: '+ self.options.speed +'ms; -webkit-animation-duration: '+ self.options.speed +'ms; -moz-transition-duration: '+ self.options.speed +'ms; -moz-animation-duration: '+ self.options.speed +'ms; -o-transition-duration: '+ self.options.speed +'ms; -o-animation-duration: '+ self.options.speed +'ms; -ms-transition-duration: '+ self.options.speed +'ms; -ms-animation-duration: '+ self.options.speed +'ms; transition-duration: '+ self.options.speed +'ms; animation-duration: '+ self.options.speed +'ms;', 1312 minWidth = self.options.minWidth ? 'min-width:'+ Math.round(self.options.minWidth) +'px;' : '', 1313 maxWidth = self.options.maxWidth ? 'max-width:'+ Math.round(self.options.maxWidth) +'px;' : '', 1314 pointerEvents = self.options.interactive ? 'pointer-events: auto;' : ''; 1315 1316 // build the base of our tooltip 1317 self.$tooltip = $('<div class="tooltipster-base '+ self.options.theme +'" style="'+ minWidth +' '+ maxWidth +' '+ pointerEvents +' '+ animationSpeed +'"><div class="tooltipster-content"></div></div>'); 1318 1319 // only add the animation class if the user has a browser that supports animations 1320 if (supportsTransitions()) self.$tooltip.addClass(animation); 1321 1322 // insert the content 1323 self._content_insert(); 1324 1325 // attach 1326 self.$tooltip.appendTo('body'); 1327 1328 // do all the crazy calculations and positioning 1329 self.reposition(); 1330 1331 // call our custom callback since the content of the tooltip is now part of the DOM 1332 self.options.functionReady.call(self.$el, self.$el, self.$tooltip); 1333 1334 // animate in the tooltip 1335 if (supportsTransitions()) { 1336 1337 self.$tooltip.addClass(animation + '-show'); 1338 1339 if(self.options.speed > 0) self.$tooltip.delay(self.options.speed); 1340 1341 self.$tooltip.queue(finish); 1342 } 1343 else { 1344 self.$tooltip.css('display', 'none').fadeIn(self.options.speed, finish); 1345 } 1346 1347 // will check if our tooltip origin is removed while the tooltip is shown 1348 self._interval_set(); 1349 1350 // reposition on scroll (otherwise position:fixed element's tooltips will move away form their origin) and on resize (in case position can/has to be changed) 1351 $(window).on('scroll.'+ self.namespace +' resize.'+ self.namespace, function() { 1352 self.reposition(); 1353 });
1354 1355 // auto-close bindings 1356 if (self.options.autoClose) { 1357 1358 // in case a listener is already bound for autoclosing (mouse or touch, hover or click), unbind it first 1359 $('body').off('.'+ self.namespace); 1360 1361 // here we'll have to set different sets of bindings for both touch and mouse 1362 if (self.options.trigger == 'hover') { 1363 1364 // if the user touches the body, hide 1365 if (deviceHasTouchCapability) { 1366 // timeout 0 : explanation below in click section 1367 setTimeout(function() { 1368 // we don't want to bind on click here because the initial touchstart event has not yet triggered its click event, which is thus about to happen 1369 $('body').on('touchstart.'+ self.namespace, function() { 1370 self.hide(); 1371 }); 1372 }, 0); 1373 } 1374 1375 // if we have to allow interaction 1376 if (self.options.interactive) { 1377 1378 // touch events inside the tooltip must not close it 1379 if (deviceHasTouchCapability) { 1380 self.$tooltip.on('touchstart.'+ self.namespace, function(event) { 1381 event.stopPropagation(); 1382 }); 1383 } 1384 1385 // as for mouse interaction, we get rid of the tooltip only after the mouse has spent some time out of it 1386 var tolerance = null; 1387 1388 self.$elProxy.add(self.$tooltip) 1389 // hide after some time out of the proxy and the tooltip 1390 .on('mouseleave.'+ self.namespace + '-autoClose', function() { 1391 clearTimeout(tolerance); 1392 tolerance = setTimeout(function(){ 1393 self.hide(); 1394 }, self.options.interactiveTolerance); 1395 }) 1396 // suspend timeout when the mouse is over the proxy or the tooltip 1397 .on('mouseenter.'+ self.namespace + '-autoClose', function() { 1398 clearTimeout(tolerance); 1399 }); 1400 } 1401 // if this is a non-interactive tooltip, get rid of it if the mouse leaves 1402 else { 1403 self.$elProxy.on('mouseleave.'+ self.namespace + '-autoClose', function() { 1404 self.hide(); 1405 }); 1406 } 1407 1408 // close the tooltip when the proxy gets a click (common behavior of native tooltips) 1409 if (self.options.hideOnClick) { 1410 1411 self.$elProxy.on('click.'+ self.namespace + '-autoClose', function() { 1412 self.hide(); 1413 }); 1414 } 1415 } 1416 // here we'll set the same bindings for both clicks and touch on the body to hide the tooltip 1417 else if(self.options.trigger == 'click'){ 1418 1419 // use a timeout to prevent immediate closing if the method was called on a click event and if options.delay == 0 (because of bubbling) 1420 setTimeout(function() { 1421 $('body').on('click.'+ self.namespace +' touchstart.'+ self.namespace, function() { 1422 self.hide(); 1423 }); 1424 }, 0); 1425 1426 // if interactive, we'll stop the events that were emitted from inside the tooltip to stop autoClosing 1427 if (self.options.interactive) { 1428 1429 // note : the touch events will just not be used if the plugin is not enabled on touch devices 1430 self.$tooltip.on('click.'+ self.namespace +' touchstart.'+ self.namespace, function(event) { 1431 event.stopPropagation(); 1432 }); 1433 } 1434 } 1435 } 1436 } 1437 1438 // if we have a timer set, let the countdown begin 1439 if (self.options.timer > 0) { 1440 1441 self.timerHide = setTimeout(function() { 1442 self.timerHide = null; 1443 self.hide(); 1444 }, self.options.timer + extraTime); 1445 } 1446 } 1447 }); 1448 }, 1449 1450 _interval_set: function() { 1451 1452 var self = this; 1453 1454 self.checkInterval = setInterval(function() { 1455 1456 // if the tooltip and/or its interval should be stopped 1457 if ( 1458 // if the origin has been removed 1459 $('body').find(self.$el).length === 0 1460 // if the elProxy has been removed 1461 || $('body').find(self.$elProxy).length === 0 1462 // if the tooltip has been closed 1463 || self.Status == 'hidden' 1464 // if the tooltip has somehow been removed 1465 || $('body').find(self.$tooltip).length === 0 1466 ) { 1467 // remove the tooltip if it's still here 1468 if (self.Status == 'shown' || self.Status == 'appearing') self.hide(); 1469 1470 // clear this interval as it is no longer necessary 1471 self._interval_cancel(); 1472 } 1473 // if everything is alright 1474 else { 1475 // compare the former and current positions of the elProxy to reposition the tooltip if need be 1476 if(self.options.positionTracker){ 1477 1478 var p = self._repositionInfo(self.$elProxy), 1479 identical = false; 1480 1481 // compare size first (a change requires repositioning too) 1482 if(areEqual(p.dimension, self.elProxyPosition.dimension)){ 1483 1484 // for elements with a fixed position, we track the top and left properties (relative to window) 1485 if(self.$elProxy.css('position') === 'fixed'){ 1486 if(areEqual(p.position, self.elProxyPosition.position)) identical = true; 1487 } 1488 // otherwise, track total offset (relative to document) 1489 else { 1490 if(areEqual(p.offset, self.elProxyPosition.offset)) identical = true; 1491 } 1492 } 1493 1494 if(!identical){ 1495 self.reposition(); 1496 self.options.positionTrackerCallback.call(self, self.$el); 1497 } 1498 } 1499 } 1500 }, 200); 1501 }, 1502 1503 _interval_cancel: function() { 1504 clearInterval(this.checkInterval); 1505 // clean delete 1506 this.checkInterval = null; 1507 }, 1508
1509 _content_set: function(content) { 1510 // clone if asked. Cloning the object makes sure that each instance has its own version of the content (in case a same object were provided for several instances) 1511 // reminder : typeof null === object 1512 if (typeof content === 'object' && content !== null && this.options.contentCloning) { 1513 content = content.clone(true); 1514 } 1515 this.Content = content; 1516 }, 1517 1518 _content_insert: function() { 1519 1520 var self = this, 1521 $d = this.$tooltip.find('.tooltipster-content'); 1522 1523 if (typeof self.Content === 'string' && !self.options.contentAsHTML) { 1524 $d.text(self.Content); 1525 } 1526 else { 1527 $d 1528 .empty() 1529 .append(self.Content); 1530 } 1531 }, 1532 1533 _update: function(content) { 1534 1535 var self = this; 1536 1537 // change the content 1538 self._content_set(content); 1539 1540 if (self.Content !== null) { 1541 1542 // update the tooltip if it is open 1543 if (self.Status !== 'hidden') { 1544 1545 // reset the content in the tooltip 1546 self._content_insert(); 1547 1548 // reposition and resize the tooltip 1549 self.reposition(); 1550 1551 // if we want to play a little animation showing the content changed 1552 if (self.options.updateAnimation) { 1553 1554 if (supportsTransitions()) { 1555 1556 self.$tooltip.css({ 1557 'width': '', 1558 '-webkit-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms', 1559 '-moz-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms', 1560 '-o-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms', 1561 '-ms-transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms', 1562 'transition': 'all ' + self.options.speed + 'ms, width 0ms, height 0ms, left 0ms, top 0ms' 1563 }).addClass('tooltipster-content-changing'); 1564 1565 // reset the CSS transitions and finish the change animation 1566 setTimeout(function() { 1567 1568 if(self.Status != 'hidden'){ 1569 1570 self.$tooltip.removeClass('tooltipster-content-changing'); 1571 1572 // after the changing animation has completed, reset the CSS transitions 1573 setTimeout(function() { 1574 1575 if(self.Status !== 'hidden'){ 1576 self.$tooltip.css({ 1577 '-webkit-transition': self.options.speed + 'ms', 1578 '-moz-transition': self.options.speed + 'ms', 1579 '-o-transition': self.options.speed + 'ms', 1580 '-ms-transition': self.options.speed + 'ms', 1581 'transition': self.options.speed + 'ms' 1582 }); 1583 } 1584 }, self.options.speed); 1585 } 1586 }, self.options.speed); 1587 } 1588 else { 1589 self.$tooltip.fadeTo(self.options.speed, 0.5, function() { 1590 if(self.Status != 'hidden'){ 1591 self.$tooltip.fadeTo(self.options.speed, 1); 1592 } 1593 }); 1594 } 1595 } 1596 } 1597 } 1598 else { 1599 self.hide(); 1600 } 1601 }, 1602 1603 _repositionInfo: function($el) { 1604 return { 1605 dimension: { 1606 height: $el.outerHeight(false), 1607 width: $el.outerWidth(false) 1608 }, 1609 offset: $el.offset(), 1610 position: { 1611 left: parseInt($el.css('left')), 1612 top: parseInt($el.css('top')) 1613 } 1614 }; 1615 }, 1616 1617 hide: function(callback) { 1618 1619 var self = this; 1620 1621 // save the method custom callback and cancel any show method custom callbacks 1622 if (callback) self.callbacks.hide.push(callback); 1623 self.callbacks.show = []; 1624 1625 // get rid of any appearance timeout 1626 clearTimeout(self.timerShow); 1627 self.timerShow = null; 1628 clearTimeout(self.timerHide); 1629 self.timerHide = null; 1630 1631 var finishCallbacks = function() { 1632 // trigger any hide method custom callbacks and reset them 1633 $.each(self.callbacks.hide, function(i,c) { c.call(self.$el); }); 1634 self.callbacks.hide = []; 1635 }; 1636 1637 // hide 1638 if (self.Status == 'shown' || self.Status == 'appearing') { 1639 1640 self.Status = 'disappearing'; 1641 1642 var finish = function() { 1643 1644 self.Status = 'hidden'; 1645 1646 // detach our content object first, so the next jQuery's remove() call does not unbind its event handlers 1647 if (typeof self.Content == 'object' && self.Content !== null) { 1648 self.Content.detach(); 1649 } 1650 1651 self.$tooltip.remove(); 1652 self.$tooltip = null; 1653 1654 // unbind orientationchange, scroll and resize listeners 1655 $(window).off('.'+ self.namespace); 1656 1657 $('body') 1658 // unbind any auto-closing click/touch listeners 1659 .off('.'+ self.namespace) 1660 .css('overflow-x', self.bodyOverflowX); 1661 1662 // unbind any auto-closing click/touch listeners 1663 $('body').off('.'+ self.namespace); 1664 1665 // unbind any auto-closing hover listeners 1666 self.$elProxy.off('.'+ self.namespace + '-autoClose'); 1667 1668 // call our constructor custom callback function 1669 self.options.functionAfter.call(self.$el, self.$el); 1670 1671 // call our method custom callbacks functions 1672 finishCallbacks(); 1673 }; 1674 1675 if (supportsTransitions()) { 1676 1677 self.$tooltip 1678 .clearQueue() 1679 .removeClass('tooltipster-' + self.options.animation + '-show') 1680 // for transitions only 1681 .addClass('tooltipster-dying'); 1682 1683 if(self.options.speed > 0) self.$tooltip.delay(self.options.speed); 1684 1685 self.$tooltip.queue(finish); 1686 } 1687 else { 1688 self.$tooltip 1689 .stop() 1690 .fadeOut(self.options.speed, finish); 1691 } 1692 } 1693 // if the tooltip is already hidden, we still need to trigger the method custom callback 1694 else if(self.Status == 'hidden') { 1695 finishCallbacks(); 1696 } 1697 1698 return self; 1699 }, 1700 1701 // the public show() method is actually an alias for the private showNow() method 1702 show: function(callback) { 1703 this._showNow(callback); 1704 return this; 1705 }, 1706
1707 // 'update' is deprecated in favor of 'content' but is kept for backward compatibility 1708 update: function(c) { 1709 return this.content(c); 1710 }, 1711 content: function(c) { 1712 // getter method 1713 if(typeof c === 'undefined'){ 1714 return this.Content; 1715 } 1716 // setter method 1717 else { 1718 this._update(c); 1719 return this; 1720 } 1721 }, 1722 1723 reposition: function() { 1724 1725 var self = this; 1726 1727 // in case the tooltip has been removed from DOM manually 1728 if ($('body').find(self.$tooltip).length !== 0) { 1729 1730 // reset width 1731 self.$tooltip.css('width', ''); 1732 1733 // find variables to determine placement 1734 self.elProxyPosition = self._repositionInfo(self.$elProxy); 1735 var arrowReposition = null, 1736 windowWidth = $(window).width(), 1737 // shorthand 1738 proxy = self.elProxyPosition, 1739 tooltipWidth = self.$tooltip.outerWidth(false), 1740 tooltipInnerWidth = self.$tooltip.innerWidth() + 1, // this +1 stops FireFox from sometimes forcing an additional text line 1741 tooltipHeight = self.$tooltip.outerHeight(false); 1742 1743 // if this is an <area> tag inside a <map>, all hell breaks loose. Recalculate all the measurements based on coordinates 1744 if (self.$elProxy.is('area')) { 1745 var areaShape = self.$elProxy.attr('shape'), 1746 mapName = self.$elProxy.parent().attr('name'), 1747 map = $('img[usemap="#'+ mapName +'"]'), 1748 mapOffsetLeft = map.offset().left, 1749 mapOffsetTop = map.offset().top, 1750 areaMeasurements = self.$elProxy.attr('coords') !== undefined ? self.$elProxy.attr('coords').split(',') : undefined; 1751 1752 if (areaShape == 'circle') { 1753 var areaLeft = parseInt(areaMeasurements[0]), 1754 areaTop = parseInt(areaMeasurements[1]), 1755 areaWidth = parseInt(areaMeasurements[2]); 1756 proxy.dimension.height = areaWidth * 2; 1757 proxy.dimension.width = areaWidth * 2; 1758 proxy.offset.top = mapOffsetTop + areaTop - areaWidth; 1759 proxy.offset.left = mapOffsetLeft + areaLeft - areaWidth; 1760 } 1761 else if (areaShape == 'rect') { 1762 var areaLeft = parseInt(areaMeasurements[0]), 1763 areaTop = parseInt(areaMeasurements[1]), 1764 areaRight = parseInt(areaMeasurements[2]), 1765 areaBottom = parseInt(areaMeasurements[3]); 1766 proxy.dimension.height = areaBottom - areaTop; 1767 proxy.dimension.width = areaRight - areaLeft; 1768 proxy.offset.top = mapOffsetTop + areaTop; 1769 proxy.offset.left = mapOffsetLeft + areaLeft; 1770 } 1771 else if (areaShape == 'poly') { 1772 var areaXs = [], 1773 areaYs = [], 1774 areaSmallestX = 0, 1775 areaSmallestY = 0, 1776 areaGreatestX = 0, 1777 areaGreatestY = 0, 1778 arrayAlternate = 'even'; 1779 1780 for (var i = 0; i < areaMeasurements.length; i++) { 1781 var areaNumber = parseInt(areaMeasurements[i]); 1782 1783 if (arrayAlternate == 'even') { 1784 if (areaNumber > areaGreatestX) { 1785 areaGreatestX = areaNumber; 1786 if (i === 0) { 1787 areaSmallestX = areaGreatestX; 1788 } 1789 } 1790 1791 if (areaNumber < areaSmallestX) { 1792 areaSmallestX = areaNumber; 1793 } 1794 1795 arrayAlternate = 'odd'; 1796 } 1797 else { 1798 if (areaNumber > areaGreatestY) { 1799 areaGreatestY = areaNumber; 1800 if (i == 1) { 1801 areaSmallestY = areaGreatestY; 1802 } 1803 } 1804 1805 if (areaNumber < areaSmallestY) { 1806 areaSmallestY = areaNumber; 1807 } 1808 1809 arrayAlternate = 'even'; 1810 } 1811 } 1812 1813 proxy.dimension.height = areaGreatestY - areaSmallestY; 1814 proxy.dimension.width = areaGreatestX - areaSmallestX; 1815 proxy.offset.top = mapOffsetTop + areaSmallestY; 1816 proxy.offset.left = mapOffsetLeft + areaSmallestX; 1817 } 1818 else { 1819 proxy.dimension.height = map.outerHeight(false); 1820 proxy.dimension.width = map.outerWidth(false); 1821 proxy.offset.top = mapOffsetTop; 1822 proxy.offset.left = mapOffsetLeft; 1823 } 1824 } 1825 1826 // our function and global vars for positioning our tooltip 1827 var myLeft = 0, 1828 myLeftMirror = 0, 1829 myTop = 0, 1830 offsetY = parseInt(self.options.offsetY), 1831 offsetX = parseInt(self.options.offsetX), 1832 // this is the arrow position that will eventually be used. It may differ from the position option if the tooltip cannot be displayed in this position 1833 practicalPosition = self.options.position; 1834 1835 // a function to detect if the tooltip is going off the screen horizontally. If so, reposition the crap out of it! 1836 function dontGoOffScreenX() { 1837 1838 var windowLeft = $(window).scrollLeft(); 1839 1840 // if the tooltip goes off the left side of the screen, line it up with the left side of the window 1841 if((myLeft - windowLeft) < 0) { 1842 arrowReposition = myLeft - windowLeft; 1843 myLeft = windowLeft; 1844 } 1845 1846 // if the tooltip goes off the right of the screen, line it up with the right side of the window 1847 if (((myLeft + tooltipWidth) - windowLeft) > windowWidth) { 1848 arrowReposition = myLeft - ((windowWidth + windowLeft) - tooltipWidth); 1849 myLeft = (windowWidth + windowLeft) - tooltipWidth; 1850 } 1851 } 1852 1853 // a function to detect if the tooltip is going off the screen vertically. If so, switch to the opposite! 1854 function dontGoOffScreenY(switchTo, switchFrom) { 1855 // if it goes off the top off the page 1856 if(((proxy.offset.top - $(window).scrollTop() - tooltipHeight - offsetY - 12) < 0) && (switchFrom.indexOf('top') > -1)) { 1857 practicalPosition = switchTo; 1858 } 1859 1860 // if it goes off the bottom of the page 1861 if (((proxy.offset.top + proxy.dimension.height + tooltipHeight + 12 + offsetY) > ($(window).scrollTop() + $(window).height())) && (switchFrom.indexOf('bottom') > -1)) { 1862 practicalPosition = switchTo; 1863 myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12; 1864 } 1865 } 1866 1867 if(practicalPosition == 'top') { 1868 var leftDifference = (proxy.offset.left + tooltipWidth) - (proxy.offset.left + proxy.dimension.width); 1869 myLeft = (proxy.offset.left + offsetX) - (leftDifference / 2); 1870 myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12; 1871 dontGoOffScreenX(); 1872 dontGoOffScreenY('bottom', 'top'); 1873 } 1874 1875 if(practicalPosition == 'top-left') { 1876 myLeft = proxy.offset.left + offsetX; 1877 myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12; 1878 dontGoOffScreenX(); 1879 dontGoOffScreenY('bottom-left', 'top-left'); 1880 } 1881 1882 if(practicalPosition == 'top-right') { 1883 myLeft = (proxy.offset.left + proxy.dimension.width + offsetX) - tooltipWidth; 1884 myTop = (proxy.offset.top - tooltipHeight) - offsetY - 12; 1885 dontGoOffScreenX(); 1886 dontGoOffScreenY('bottom-right', 'top-right'); 1887 } 1888 1889 if(practicalPosition == 'bottom') { 1890 var leftDifference = (proxy.offset.left + tooltipWidth) - (proxy.offset.left + proxy.dimension.width); 1891 myLeft = proxy.offset.left - (leftDifference / 2) + offsetX; 1892 myTop = (proxy.offset.top + proxy.dimension.height) + offsetY + 12; 1893 dontGoOffScreenX(); 1894 dontGoOffScreenY('top', 'bottom'); 1895 } 1896 1897 if(practicalPosition == 'bottom-left') { 1898 myLeft = proxy.offset.left + offsetX; 1899 myTop = (proxy.offset.top + proxy.dimension.height) + offsetY + 12; 1900 dontGoOffScreenX(); 1901 dontGoOffScreenY('top-left', 'bottom-left'); 1902 } 1903 1904 if(practicalPosition == 'bottom-right') { 1905 myLeft = (proxy.offset.left + proxy.dimension.width + offsetX) - tooltipWidth; 1906 myTop = (proxy.offset.top + proxy.dimension.height) + offsetY + 12; 1907 dontGoOffScreenX(); 1908 dontGoOffScreenY('top-right', 'bottom-right'); 1909 } 1910 1911 if(practicalPosition == 'left') { 1912 myLeft = proxy.offset.left - offsetX - tooltipWidth - 12; 1913 myLeftMirror = proxy.offset.left + offsetX + proxy.dimension.width + 12; 1914 var topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height); 1915 myTop = proxy.offset.top - (topDifference / 2) - offsetY; 1916 1917 // if the tooltip goes off boths sides of the page 1918 if((myLeft < 0) && ((myLeftMirror + tooltipWidth) > windowWidth)) { 1919 var borderWidth = parseFloat(self.$tooltip.css('border-width')) * 2, 1920 newWidth = (tooltipWidth + myLeft) - borderWidth; 1921 self.$tooltip.css('width', newWidth + 'px'); 1922 1923 tooltipHeight = self.$tooltip.outerHeight(false); 1924 myLeft = proxy.offset.left - offsetX - newWidth - 12 - borderWidth; 1925 topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height); 1926 myTop = proxy.offset.top - (topDifference / 2) - offsetY; 1927 } 1928 1929 // if it only goes off one side, flip it to the other side
1930 else if(myLeft < 0) { 1931 myLeft = proxy.offset.left + offsetX + proxy.dimension.width + 12; 1932 arrowReposition = 'left'; 1933 } 1934 } 1935 1936 if(practicalPosition == 'right') { 1937 myLeft = proxy.offset.left + offsetX + proxy.dimension.width + 12; 1938 myLeftMirror = proxy.offset.left - offsetX - tooltipWidth - 12; 1939 var topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height); 1940 myTop = proxy.offset.top - (topDifference / 2) - offsetY; 1941 1942 // if the tooltip goes off boths sides of the page 1943 if(((myLeft + tooltipWidth) > windowWidth) && (myLeftMirror < 0)) { 1944 var borderWidth = parseFloat(self.$tooltip.css('border-width')) * 2, 1945 newWidth = (windowWidth - myLeft) - borderWidth; 1946 self.$tooltip.css('width', newWidth + 'px'); 1947 1948 tooltipHeight = self.$tooltip.outerHeight(false); 1949 topDifference = (proxy.offset.top + tooltipHeight) - (proxy.offset.top + proxy.dimension.height); 1950 myTop = proxy.offset.top - (topDifference / 2) - offsetY; 1951 } 1952 1953 // if it only goes off one side, flip it to the other side 1954 else if((myLeft + tooltipWidth) > windowWidth) { 1955 myLeft = proxy.offset.left - offsetX - tooltipWidth - 12; 1956 arrowReposition = 'right'; 1957 } 1958 } 1959 1960 // if arrow is set true, style it and append it 1961 if (self.options.arrow) { 1962 1963 var arrowClass = 'tooltipster-arrow-' + practicalPosition; 1964 1965 // set color of the arrow 1966 if(self.options.arrowColor.length < 1) { 1967 var arrowColor = self.$tooltip.css('background-color'); 1968 } 1969 else { 1970 var arrowColor = self.options.arrowColor; 1971 } 1972 1973 // if the tooltip was going off the page and had to re-adjust, we need to update the arrow's position 1974 if (!arrowReposition) { 1975 arrowReposition = ''; 1976 } 1977 else if (arrowReposition == 'left') { 1978 arrowClass = 'tooltipster-arrow-right'; 1979 arrowReposition = ''; 1980 } 1981 else if (arrowReposition == 'right') { 1982 arrowClass = 'tooltipster-arrow-left'; 1983 arrowReposition = ''; 1984 } 1985 else { 1986 arrowReposition = 'left:'+ Math.round(arrowReposition) +'px;'; 1987 } 1988 1989 // building the logic to create the border around the arrow of the tooltip 1990 if ((practicalPosition == 'top') || (practicalPosition == 'top-left') || (practicalPosition == 'top-right')) { 1991 var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-bottom-width')), 1992 tooltipBorderColor = self.$tooltip.css('border-bottom-color'); 1993 } 1994 else if ((practicalPosition == 'bottom') || (practicalPosition == 'bottom-left') || (practicalPosition == 'bottom-right')) { 1995 var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-top-width')), 1996 tooltipBorderColor = self.$tooltip.css('border-top-color'); 1997 } 1998 else if (practicalPosition == 'left') { 1999 var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-right-width')), 2000 tooltipBorderColor = self.$tooltip.css('border-right-color'); 2001 } 2002 else if (practicalPosition == 'right') { 2003 var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-left-width')), 2004 tooltipBorderColor = self.$tooltip.css('border-left-color'); 2005 } 2006 else { 2007 var tooltipBorderWidth = parseFloat(self.$tooltip.css('border-bottom-width')), 2008 tooltipBorderColor = self.$tooltip.css('border-bottom-color'); 2009 } 2010 2011 if (tooltipBorderWidth > 1) { 2012 tooltipBorderWidth++; 2013 } 2014 2015 var arrowBorder = ''; 2016 if (tooltipBorderWidth !== 0) { 2017 var arrowBorderSize = '', 2018 arrowBorderColor = 'border-color: '+ tooltipBorderColor +';'; 2019 if (arrowClass.indexOf('bottom') !== -1) { 2020 arrowBorderSize = 'margin-top: -'+ Math.round(tooltipBorderWidth) +'px;'; 2021 } 2022 else if (arrowClass.indexOf('top') !== -1) { 2023 arrowBorderSize = 'margin-bottom: -'+ Math.round(tooltipBorderWidth) +'px;'; 2024 } 2025 else if (arrowClass.indexOf('left') !== -1) { 2026 arrowBorderSize = 'margin-right: -'+ Math.round(tooltipBorderWidth) +'px;'; 2027 } 2028 else if (arrowClass.indexOf('right') !== -1) { 2029 arrowBorderSize = 'margin-left: -'+ Math.round(tooltipBorderWidth) +'px;'; 2030 } 2031 arrowBorder = '<span class="tooltipster-arrow-border" style="'+ arrowBorderSize +' '+ arrowBorderColor +';"></span>'; 2032 } 2033 2034 // if the arrow already exists, remove and replace it 2035 self.$tooltip.find('.tooltipster-arrow').remove(); 2036 2037 // build out the arrow and append it 2038 var arrowConstruct = '<div class="'+ arrowClass +' tooltipster-arrow" style="'+ arrowReposition +'">'+ arrowBorder +'<span style="border-color:'+ arrowColor +';"></span></div>'; 2039 self.$tooltip.append(arrowConstruct); 2040 } 2041 2042 // position the tooltip 2043 self.$tooltip.css({'top': Math.round(myTop) + 'px', 'left': Math.round(myLeft) + 'px'}); 2044 } 2045 2046 return self; 2047 }, 2048 2049 enable: function() { 2050 this.enabled = true; 2051 return this; 2052 }, 2053 2054 disable: function() { 2055 // hide first, in case the tooltip would not disappear on its own (autoClose false) 2056 this.hide(); 2057 this.enabled = false; 2058 return this; 2059 }, 2060 2061 destroy: function() { 2062 2063 var self = this; 2064 2065 self.hide(); 2066 2067 // remove the icon, if any 2068 if (self.$el[0] !== self.$elProxy[0]) { 2069 self.$elProxy.remove(); 2070 } 2071 2072 self.$el 2073 .removeData(self.namespace) 2074 .off('.'+ self.namespace); 2075 2076 var ns = self.$el.data('tooltipster-ns'); 2077 2078 // if there are no more tooltips on this element 2079 if(ns.length === 1){ 2080 2081 // optional restoration of a title attribute 2082 var title = null; 2083 if (self.options.restoration === 'previous'){ 2084 title = self.$el.data('tooltipster-initialTitle'); 2085 } 2086 else if(self.options.restoration === 'current'){ 2087 2088 // old school technique to stringify when outerHTML is not supported 2089 title = 2090 (typeof self.Content === 'string') ? 2091 self.Content : 2092 $('<div></div>').append(self.Content).html(); 2093 } 2094 2095 if (title) { 2096 self.$el.attr('title', title); 2097 } 2098 2099 // final cleaning 2100 self.$el 2101 .removeClass('tooltipstered') 2102 .removeData('tooltipster-ns') 2103 .removeData('tooltipster-initialTitle'); 2104 } 2105 else { 2106 // remove the instance namespace from the list of namespaces of tooltips present on the element 2107 ns = $.grep(ns, function(el, i){ 2108 return el !== self.namespace; 2109 }); 2110 self.$el.data('tooltipster-ns', ns); 2111 } 2112 2113 return self; 2114 }, 2115 2116 elementIcon: function() { 2117 return (this.$el[0] !== this.$elProxy[0]) ? this.$elProxy[0] : undefined; 2118 }, 2119 2120 elementTooltip: function() { 2121 return this.$tooltip ? this.$tooltip[0] : undefined; 2122 }, 2123 2124 // public methods but for internal use only 2125 // getter if val is ommitted, setter otherwise 2126 option: function(o, val) { 2127 if (typeof val == 'undefined') return this.options[o]; 2128 else { 2129 this.options[o] = val; 2130 return this; 2131 } 2132 }, 2133 status: function() { 2134 return this.Status; 2135 } 2136 }; 2137 2138 $.fn[pluginName] = function () { 2139 2140 // for using in closures 2141 var args = arguments; 2142 2143 // if we are not in the context of jQuery wrapped HTML element(s) : 2144 // this happens when calling static methods in the form $.fn.tooltipster('methodName'), or when calling $(sel).tooltipster('methodName or options') where $(sel) does not match anything 2145 if (this.length === 0) { 2146
2147 // if the first argument is a method name 2148 if (typeof args[0] === 'string') { 2149 2150 var methodIsStatic = true; 2151 2152 // list static methods here (usable by calling $.fn.tooltipster('methodName');) 2153 switch (args[0]) { 2154 2155 case 'setDefaults': 2156 // change default options for all future instances 2157 $.extend(defaults, args[1]); 2158 break; 2159 2160 default: 2161 methodIsStatic = false; 2162 break; 2163 } 2164 2165 // $.fn.tooltipster('methodName') calls will return true 2166 if (methodIsStatic) return true; 2167 // $(sel).tooltipster('methodName') calls will return the list of objects event though it's empty because chaining should work on empty lists 2168 else return this; 2169 } 2170 // the first argument is undefined or an object of options : we are initalizing but there is no element matched by selector 2171 else { 2172 // still chainable : same as above 2173 return this; 2174 } 2175 } 2176 // this happens when calling $(sel).tooltipster('methodName or options') where $(sel) matches one or more elements 2177 else { 2178 2179 // method calls 2180 if (typeof args[0] === 'string') { 2181 2182 var v = '#*$~&'; 2183 2184 this.each(function() { 2185 2186 // retrieve the namepaces of the tooltip(s) that exist on that element. We will interact with the first tooltip only. 2187 var ns = $(this).data('tooltipster-ns'), 2188 // self represents the instance of the first tooltipster plugin associated to the current HTML object of the loop 2189 self = ns ? $(this).data(ns[0]) : null; 2190 2191 // if the current element holds a tooltipster instance 2192 if (self) { 2193 2194 if (typeof self[args[0]] === 'function') { 2195 // note : args[1] and args[2] may not be defined 2196 var resp = self[args[0]](args[1], args[2]); 2197 } 2198 else { 2199 throw new Error('Unknown method .tooltipster("' + args[0] + '")'); 2200 } 2201 2202 // if the function returned anything other than the instance itself (which implies chaining) 2203 if (resp !== self){ 2204 v = resp; 2205 // return false to stop .each iteration on the first element matched by the selector 2206 return false; 2207 } 2208 } 2209 else { 2210 throw new Error('You called Tooltipster\'s "' + args[0] + '" method on an uninitialized element'); 2211 } 2212 }); 2213 2214 return (v !== '#*$~&') ? v : this; 2215 } 2216 // first argument is undefined or an object : the tooltip is initializing 2217 else { 2218 2219 var instances = [], 2220 // is there a defined value for the multiple option in the options object ? 2221 multipleIsSet = args[0] && typeof args[0].multiple !== 'undefined', 2222 // if the multiple option is set to true, or if it's not defined but set to true in the defaults 2223 multiple = (multipleIsSet && args[0].multiple) || (!multipleIsSet && defaults.multiple), 2224 // same for debug 2225 debugIsSet = args[0] && typeof args[0].debug !== 'undefined', 2226 debug = (debugIsSet && args[0].debug) || (!debugIsSet && defaults.debug); 2227 2228 // initialize a tooltipster instance for each element if it doesn't already have one or if the multiple option is set, and attach the object to it 2229 this.each(function () { 2230 2231 var go = false, 2232 ns = $(this).data('tooltipster-ns'), 2233 instance = null; 2234 2235 if (!ns) { 2236 go = true; 2237 } 2238 else if (multiple) { 2239 go = true; 2240 } 2241 else if (debug) { 2242 console.log('Tooltipster: one or more tooltips are already attached to this element: ignoring. Use the "multiple" option to attach more tooltips.'); 2243 } 2244 2245 if (go) { 2246 instance = new Plugin(this, args[0]); 2247 2248 // save the reference of the new instance 2249 if (!ns) ns = []; 2250 ns.push(instance.namespace); 2251 $(this).data('tooltipster-ns', ns) 2252 2253 // save the instance itself 2254 $(this).data(instance.namespace, instance); 2255 } 2256 2257 instances.push(instance); 2258 }); 2259 2260 if (multiple) return instances; 2261 else return this; 2262 } 2263 } 2264 }; 2265 2266 // quick & dirty compare function (not bijective nor multidimensional) 2267 function areEqual(a,b) { 2268 var same = true; 2269 $.each(a, function(i, el){ 2270 if(typeof b[i] === 'undefined' || a[i] !== b[i]){ 2271 same = false; 2272 return false; 2273 } 2274 }); 2275 return same; 2276 } 2277 2278 // detect if this device can trigger touch events 2279 var deviceHasTouchCapability = !!('ontouchstart' in window); 2280 2281 // we'll assume the device has no mouse until we detect any mouse movement 2282 var deviceHasMouse = false; 2283 $('body').one('mousemove', function() { 2284 deviceHasMouse = true; 2285 }); 2286 2287 function deviceIsPureTouch() { 2288 return (!deviceHasMouse && deviceHasTouchCapability); 2289 } 2290 2291 // detecting support for CSS transitions 2292 function supportsTransitions() { 2293 var b = document.body || document.documentElement, 2294 s = b.style, 2295 p = 'transition'; 2296 2297 if(typeof s[p] == 'string') {return true; } 2298 2299 v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'], 2300 p = p.charAt(0).toUpperCase() + p.substr(1); 2301 for(var i=0; i<v.length; i++) { 2302 if(typeof s[v[i] + p] == 'string') { return true; } 2303 } 2304 return false; 2305 } 2306})( jQuery, window, document ); 2307 2308(function( $ ) { 2309 'use strict'; 2310 var Obj = { 2311 // All pages 2312 'common' : { 2313 init : function() { 2314 // JavaScript to be fired on all pages 2315 $( function() { 2316 Obj.common.voting(); 2317 } ); 2318 }, 2319 finalize : function() { 2320 // JavaScript to be fired on all pages, after page specific JS is fired 2321 }, 2322 2323 voting : function() { 2324 // Like 2325 $('a.pakb-like-btn').click(function(){ 2326 var response_div = $(this).parent(); 2327 $.ajax({ 2328 url : PAKB.base_url, 2329 data : {'pakb_vote_like':$(this).attr('post_id')}, 2330 beforeSend : function(){ 2331 }, 2332 success : function(data){ 2333 response_div.html(data).fadeIn(900); 2334 }, 2335 complete : function(){ 2336 2337 } 2338 }); 2339 }); 2340 2341 // Dislike 2342 $('a.pakb-dislike-btn').click(function(){ 2343 var response_div = $(this).parent(); 2344 $.ajax({ 2345 url : PAKB.base_url, 2346 data : {'pakb_vote_dislike':$(this).attr('post_id')}, 2347 beforeSend : function(){ 2348 2349 }, 2350 success : function(data){ 2351 response_div.html(data).fadeIn(900); 2352 }, 2353 complete : function(){ 2354 2355 } 2356 }); 2357 }); 2358 2359 // Autosuggest 2360 //will check if display category is enabled through localize script 2361 if ( PAKB.category ) { 2362 $('#kb-s.autosuggest').autocomplete({ 2363 serviceUrl : PAKB.ajaxurl, 2364 params : {'action':'search_title'}, 2365 minChars : 1, 2366 maxHeight : 450, 2367 groupBy : 'category', 2368 preventBadQueries : false, 2369 onSelect : function(suggestion) { 2370 window.location = suggestion.url; 2371 } 2372 }); 2373 } else { 2374 $('#kb-s.autosuggest').autocomplete({ 2375 serviceUrl : PAKB.ajaxurl, 2376 params : {'action':'search_title'}, 2377 minChars : 1, 2378 maxHeight : 450, 2379 preventBadQueries : false, 2380 onSelect : function(suggestion) { 2381 window.location = suggestion.url; 2382 } 2383 }); 2384 } 2385 2386 $('.pakb-tooltip').tooltipster(); 2387 2388 // Break header out of container 2389 /* 2390 var scroll_resize = function(){ 2391 2392 var headerPos = $('.pakb-header').offset(); // returns position 2393 var windowWidth = jQuery(window).width() 2394 var widthHeader = parseInt($(".pakb-header").css("width")); 2395 //var paddingRight = windowWidth - headerPos.left - widthHeader; 2396 2397 //if ( 767 < windowWidth ) { 2398 $(".pakb-header-wrapper").css( {"width": windowWidth, "margin-left": - headerPos.left, "padding-left": + headerPos.left, "padding-right": + headerPos.left} ); 2399 //} 2400 return; 2401 }; 2402 2403 $(document).ready(scroll_resize); 2404 $(window).resize(scroll_resize); 2405 */ 2406 2407 } // end of voting 2408 2409 } 2410 2411 }; 2412 2413 // The routing fires all common scripts, followed by the page specific scripts. 2414 // Add additional events for more control over timing e.g. a finalize event 2415 var UTIL = { 2416 fire : function( func, funcname, args ) { 2417 var fire; 2418 var namespace = Obj; 2419 funcname = (funcname === undefined) ? 'init' : funcname; 2420 fire = func !== ''; 2421 fire = fire && namespace[ func ]; 2422 fire = fire && typeof namespace[ func ][ funcname ] === 'function'; 2423 2424 if ( fire ) { 2425 namespace[ func ][ funcname ]( args ); 2426 } 2427 }, 2428 loadEvents : function() { 2429 // Fire common init JS 2430 UTIL.fire( 'common' ); 2431 2432 // Fire page-specific init JS, and then finalize JS 2433 $.each( document.body.className.replace( /-/g, '_' ).split( /\s+/ ), function( i, classnm ) { 2434 UTIL.fire( classnm ); 2435 UTIL.fire( classnm, 'finalize' ); 2436 } ); 2437 2438 // Fire common finalize JS 2439 UTIL.fire( 'common', 'finalize' ); 2440 } 2441 }; 2442 2443 // Load Events 2444 $( document ).ready( UTIL.loadEvents ); 2445 2446})( jQuery ); // Fully reference jQuery after this point.
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.