[ Index ] |
PHP Cross Reference of BuddyPress |
[Summary view] [Print] [Text view]
1 /*! 2 * Autocomplete - jQuery plugin 1.0 Beta 3 * 4 * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer 5 * 6 * Dual licensed under the MIT and GPL licenses: 7 * http://www.opensource.org/licenses/mit-license.php 8 * http://www.gnu.org/licenses/gpl.html 9 * 10 * Revision: $Id: jquery.autocomplete.js 4485 2008-01-20 13:52:47Z joern.zaefferer $ 11 * 12 * Namespaced for BuddyPress as $.fn.autocompletebp, to avoid conflicts with other autocomplete scripts. 13 */ 14 15 ;(function($) { 16 17 $.fn.extend({ 18 autocompletebp: function(urlOrData, options) { 19 var isUrl = typeof urlOrData == "string"; 20 options = $.extend({}, $.Autocompleter.defaults, { 21 url: isUrl ? urlOrData : null, 22 data: isUrl ? null : urlOrData, 23 delay: isUrl ? $.Autocompleter.defaults.delay : 10, 24 max: options && !options.scroll ? 10 : 150 25 }, options); 26 27 // if highlight is set to false, replace it with a do-nothing function 28 options.highlight = options.highlight || function(value) { return value; }; 29 30 return this.each(function() { 31 new $.Autocompleter(this, options); 32 }); 33 }, 34 result: function(handler) { 35 return this.bind("result", handler); 36 }, 37 search: function(handler) { 38 return this.trigger("search", [handler]); 39 }, 40 flushCache: function() { 41 return this.trigger("flushCache"); 42 }, 43 setOptions: function(options){ 44 return this.trigger("setOptions", [options]); 45 }, 46 unautocomplete: function() { 47 return this.trigger("unautocomplete"); 48 } 49 }); 50 51 $.Autocompleter = function(input, options) { 52 53 var KEY = { 54 UP: 38, 55 DOWN: 40, 56 DEL: 46, 57 TAB: 9, 58 RETURN: 13, 59 ESC: 27, 60 COMMA: 188, 61 PAGEUP: 33, 62 PAGEDOWN: 34 63 }; 64 65 // Create $ object for input element 66 var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass); 67 68 var timeout; 69 var previousValue = ""; 70 var cache = $.Autocompleter.Cache(options); 71 var hasFocus = 0; 72 var lastKeyPressCode; 73 var config = { 74 mouseDownOnSelect: false 75 }; 76 var select = $.Autocompleter.Select(options, input, selectCurrent, config); 77 78 $input.keydown(function(event) { 79 // track last key pressed 80 lastKeyPressCode = event.keyCode; 81 switch(event.keyCode) { 82 83 case KEY.UP: 84 event.preventDefault(); 85 if ( select.visible() ) { 86 select.prev(); 87 } else { 88 onChange(0, true); 89 } 90 break; 91 92 case KEY.DOWN: 93 event.preventDefault(); 94 if ( select.visible() ) { 95 select.next(); 96 } else { 97 onChange(0, true); 98 } 99 break; 100 101 case KEY.PAGEUP: 102 event.preventDefault(); 103 if ( select.visible() ) { 104 select.pageUp(); 105 } else { 106 onChange(0, true); 107 } 108 break; 109 110 case KEY.PAGEDOWN: 111 event.preventDefault(); 112 if ( select.visible() ) { 113 select.pageDown(); 114 } else { 115 onChange(0, true); 116 } 117 break; 118 119 // matches also semicolon 120 case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA: 121 case KEY.TAB: 122 case KEY.RETURN: 123 if( selectCurrent() ){ 124 // make sure to blur off the current field 125 if( !options.multiple ) 126 $input.blur(); 127 event.preventDefault(); 128 $input.focus(); 129 } 130 break; 131 132 case KEY.ESC: 133 select.hide(); 134 break; 135 136 default: 137 clearTimeout(timeout); 138 timeout = setTimeout(onChange, options.delay); 139 break; 140 } 141 }).keypress(function() { 142 // having fun with opera - remove this binding and Opera submits the form when we select an entry via return 143 }).focus(function(){ 144 // track whether the field has focus, we shouldn't process any 145 // results if the field no longer has focus 146 hasFocus++; 147 }).blur(function() { 148 hasFocus = 0; 149 if (!config.mouseDownOnSelect) { 150 hideResults(); 151 } 152 }).click(function() { 153 // show select when clicking in a focused field 154 if ( hasFocus++ > 1 && !select.visible() ) { 155 onChange(0, true); 156 } 157 }).bind("search", function() { 158 // TODO why not just specifying both arguments? 159 var fn = (arguments.length > 1) ? arguments[1] : null; 160 function findValueCallback(q, data) { 161 var result; 162 if( data && data.length ) { 163 for (var i=0; i < data.length; i++) { 164 if( data[i].result.toLowerCase() == q.toLowerCase() ) { 165 result = data[i]; 166 break; 167 } 168 } 169 } 170 if( typeof fn == "function" ) fn(result); 171 else $input.trigger("result", result && [result.data, result.value]); 172 } 173 $.each(trimWords($input.val()), function(i, value) { 174 request(value, findValueCallback, findValueCallback); 175 }); 176 }).bind("flushCache", function() { 177 cache.flush(); 178 }).bind("setOptions", function() { 179 $.extend(options, arguments[1]); 180 // if we've updated the data, repopulate 181 if ( "data" in arguments[1] ) 182 cache.populate(); 183 }).bind("unautocomplete", function() { 184 select.unbind(); 185 $input.unbind(); 186 }); 187 188 189 function selectCurrent() { 190 var selected = select.selected(); 191 if( !selected ) 192 return false; 193 194 var v = selected.result; 195 previousValue = v; 196 197 if ( options.multiple ) { 198 var words = trimWords($input.val()); 199 if ( words.length > 1 ) { 200 v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v; 201 } 202 v += options.multipleSeparator; 203 } 204 205 $input.val(v); 206 hideResultsNow(); 207 $input.trigger("result", [selected.data, selected.value]); 208 return true; 209 } 210 211 function onChange(crap, skipPrevCheck) { 212 if( lastKeyPressCode == KEY.DEL ) { 213 select.hide(); 214 return; 215 } 216 217 var currentValue = $input.val(); 218 219 if ( !skipPrevCheck && currentValue == previousValue ) 220 return; 221 222 previousValue = currentValue; 223 224 currentValue = lastWord(currentValue); 225 if ( currentValue.length >= options.minChars) { 226 $input.addClass(options.loadingClass); 227 jQuery('#send-to-input').addClass('loading'); 228 if (!options.matchCase) 229 currentValue = currentValue.toLowerCase(); 230 request(currentValue, receiveData, hideResultsNow); 231 } else { 232 stopLoading(); 233 select.hide(); 234 } 235 }; 236 237 function trimWords(value) { 238 if ( !value ) { 239 return [""]; 240 } 241 var words = value.split( $.trim( options.multipleSeparator ) ); 242 var result = []; 243 $.each(words, function(i, value) { 244 if ( $.trim(value) ) 245 result[i] = $.trim(value); 246 }); 247 return result; 248 } 249 250 function lastWord(value) { 251 if ( !options.multiple ) 252 return value; 253 var words = trimWords(value); 254 return words[words.length - 1]; 255 } 256 257 // fills in the input box w/the first match (assumed to be the best match) 258 function autoFill(q, sValue){ 259 // autofill in the complete box w/the first match as long as the user hasn't entered in more data 260 // if the last user key pressed was backspace, don't autofill 261 if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != 8 ) { 262 // fill in the value (keep the case the user has typed) 263 $input.val($input.val() + sValue.substring(lastWord(previousValue).length)); 264 // select the portion of the value not typed by the user (so the next character will erase) 265 $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length); 266 } 267 }; 268 269 function hideResults() { 270 clearTimeout(timeout); 271 timeout = setTimeout(hideResultsNow, 200); 272 }; 273 274 function hideResultsNow() { 275 select.hide(); 276 clearTimeout(timeout); 277 stopLoading(); 278 if (options.mustMatch) { 279 // call search and run callback 280 $input.search( 281 function (result){ 282 // if no value found, clear the input box 283 if( !result ) $input.val(""); 284 } 285 ); 286 } 287 }; 288 289 function receiveData(q, data) { 290 if ( data && data.length && hasFocus ) { 291 stopLoading(); 292 select.display(data, q); 293 294 var newData = data[0].value.split(';'); 295 data.value = newData[0]; 296 297 autoFill(q, data.value); 298 select.show(); 299 } else { 300 hideResultsNow(); 301 } 302 }; 303 304 function request(term, success, failure) { 305 if (!options.matchCase) 306 term = term.toLowerCase(); 307 var data = cache.load(term); 308 // recieve the cached data 309 if (data && data.length) { 310 success(term, data); 311 // if an AJAX url has been supplied, try loading the data now 312 } else if( (typeof options.url == "string") && (options.url.length > 0) ){ 313 314 var extraParams = {}; 315 $.each(options.extraParams, function(key, param) { 316 extraParams[key] = typeof param == "function" ? param() : param; 317 }); 318 319 $.ajax({ 320 // try to leverage ajaxQueue plugin to abort previous requests 321 mode: "abort", 322 // limit abortion to this input 323 port: "autocomplete" + input.name, 324 dataType: options.dataType, 325 url: options.url, 326 data: $.extend({ 327 q: lastWord(term), 328 limit: options.max, 329 action: 'messages_autocomplete_results', 330 'cookie': getAutocompleteCookies() 331 }, extraParams), 332 success: function(data) { 333 var parsed = options.parse && options.parse(data) || parse(data); 334 cache.add(term, parsed); 335 success(term, parsed); 336 } 337 }); 338 } else { 339 failure(term); 340 } 341 }; 342 343 function parse(data) { 344 var parsed = []; 345 var rows = data.split("\n"); 346 for (var i=0; i < rows.length; i++) { 347 var row = $.trim(rows[i]); 348 if (row) { 349 row = row.split("|"); 350 parsed[parsed.length] = { 351 data: row, 352 value: row[0], 353 result: options.formatResult && options.formatResult(row, row[0]) || row[0] 354 }; 355 } 356 } 357 return parsed; 358 }; 359 360 function stopLoading() { 361 $input.removeClass(options.loadingClass); 362 jQuery('#send-to-input').removeClass('loading'); 363 }; 364 365 /* Returns a querystring of BP cookies (cookies beginning with 'bp-') */ 366 function getAutocompleteCookies() { 367 var allCookies = document.cookie.split(';'), // get all cookies and split into an array 368 bpCookies = {}, 369 cookiePrefix = 'bp-', 370 i, cookie, delimiter, name, value; 371 372 // loop through cookies 373 for (i = 0; i < allCookies.length; i++) { 374 cookie = allCookies[i]; 375 delimiter = cookie.indexOf('='); 376 name = jq.trim( unescape( cookie.slice(0, delimiter) ) ); 377 value = unescape( cookie.slice(delimiter + 1) ); 378 379 // if BP cookie, store it 380 if ( name.indexOf(cookiePrefix) === 0 ) { 381 bpCookies[name] = value; 382 } 383 } 384 385 // returns BP cookies as querystring 386 return encodeURIComponent( jq.param(bpCookies) ); 387 } 388 }; 389 390 $.Autocompleter.defaults = { 391 inputClass: "ac_input", 392 resultsClass: "ac_results", 393 loadingClass: "ac_loading", 394 minChars: 1, 395 delay: 400, 396 matchCase: false, 397 matchSubset: true, 398 matchContains: false, 399 cacheLength: 10, 400 max: 100, 401 mustMatch: false, 402 extraParams: {}, 403 selectFirst: true, 404 formatItem: function(row) { return row[0]; }, 405 autoFill: false, 406 width: 0, 407 multiple: false, 408 multipleSeparator: ", ", 409 highlight: function(value, term) { 410 return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>"); 411 }, 412 scroll: true, 413 scrollHeight: 250, 414 attachTo: 'body' 415 }; 416 417 $.Autocompleter.Cache = function(options) { 418 419 var data = {}; 420 var length = 0; 421 422 function matchSubset(s, sub) { 423 if (!options.matchCase) 424 s = s.toLowerCase(); 425 var i = s.indexOf(sub); 426 if (i == -1) return false; 427 return i == 0 || options.matchContains; 428 }; 429 430 function add(q, value) { 431 if (length > options.cacheLength){ 432 flush(); 433 } 434 if (!data[q]){ 435 length++; 436 } 437 data[q] = value; 438 } 439 440 function populate(){ 441 if( !options.data ) return false; 442 // track the matches 443 var stMatchSets = {}, 444 nullData = 0; 445 446 // no url was specified, we need to adjust the cache length to make sure it fits the local data store 447 if( !options.url ) options.cacheLength = 1; 448 449 // track all options for minChars = 0 450 stMatchSets[""] = []; 451 452 // loop through the array and create a lookup structure 453 for ( var i = 0, ol = options.data.length; i < ol; i++ ) { 454 var rawValue = options.data[i]; 455 // if rawValue is a string, make an array otherwise just reference the array 456 rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue; 457 458 var value = options.formatItem(rawValue, i+1, options.data.length); 459 if ( value === false ) 460 continue; 461 462 var firstChar = value.charAt(0).toLowerCase(); 463 // if no lookup array for this character exists, look it up now 464 if( !stMatchSets[firstChar] ) 465 stMatchSets[firstChar] = []; 466 467 // if the match is a string 468 var row = { 469 value: value, 470 data: rawValue, 471 result: options.formatResult && options.formatResult(rawValue) || value 472 }; 473 474 // push the current match into the set list 475 stMatchSets[firstChar].push(row); 476 477 // keep track of minChars zero items 478 if ( nullData++ < options.max ) { 479 stMatchSets[""].push(row); 480 } 481 }; 482 483 // add the data items to the cache 484 $.each(stMatchSets, function(i, value) { 485 // increase the cache size 486 options.cacheLength++; 487 // add to the cache 488 add(i, value); 489 }); 490 } 491 492 // populate any existing data 493 setTimeout(populate, 25); 494 495 function flush(){ 496 data = {}; 497 length = 0; 498 } 499 500 return { 501 flush: flush, 502 add: add, 503 populate: populate, 504 load: function(q) { 505 if (!options.cacheLength || !length) 506 return null; 507 /* 508 * if dealing w/local data and matchContains than we must make sure 509 * to loop through all the data collections looking for matches 510 */ 511 if( !options.url && options.matchContains ){ 512 // track all matches 513 var csub = []; 514 // loop through all the data grids for matches 515 for( var k in data ){ 516 // don't search through the stMatchSets[""] (minChars: 0) cache 517 // this prevents duplicates 518 if( k.length > 0 ){ 519 var c = data[k]; 520 $.each(c, function(i, x) { 521 // if we've got a match, add it to the array 522 if (matchSubset(x.value, q)) { 523 csub.push(x); 524 } 525 }); 526 } 527 } 528 return csub; 529 } else 530 // if the exact item exists, use it 531 if (data[q]){ 532 return data[q]; 533 } else 534 if (options.matchSubset) { 535 for (var i = q.length - 1; i >= options.minChars; i--) { 536 var c = data[q.substr(0, i)]; 537 if (c) { 538 var csub = []; 539 $.each(c, function(i, x) { 540 if (matchSubset(x.value, q)) { 541 csub[csub.length] = x; 542 } 543 }); 544 return csub; 545 } 546 } 547 } 548 return null; 549 } 550 }; 551 }; 552 553 $.Autocompleter.Select = function (options, input, select, config) { 554 var CLASSES = { 555 ACTIVE: "ac_over" 556 }; 557 558 var listItems, 559 active = -1, 560 data, 561 term = "", 562 needsInit = true, 563 element, 564 list; 565 566 // Create results 567 function init() { 568 if (!needsInit) 569 return; 570 element = $("<div/>") 571 .hide() 572 .addClass(options.resultsClass) 573 .css("position", "absolute") 574 .appendTo(options.attachTo); 575 576 list = $("<ul>").appendTo(element).mouseover( function(event) { 577 if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') { 578 active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event)); 579 $(target(event)).addClass(CLASSES.ACTIVE); 580 } 581 }).click(function(event) { 582 $(target(event)).addClass(CLASSES.ACTIVE); 583 select(); 584 input.focus(); 585 return false; 586 }).mousedown(function() { 587 config.mouseDownOnSelect = true; 588 }).mouseup(function() { 589 config.mouseDownOnSelect = false; 590 }); 591 592 if( options.width > 0 ) 593 element.css("width", options.width); 594 595 needsInit = false; 596 } 597 598 function target(event) { 599 var element = event.target; 600 while(element && element.tagName != "LI") 601 element = element.parentNode; 602 // more fun with IE, sometimes event.target is empty, just ignore it then 603 if(!element) 604 return []; 605 return element; 606 } 607 608 function moveSelect(step) { 609 listItems.slice(active, active + 1).removeClass(); 610 movePosition(step); 611 var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE); 612 if(options.scroll) { 613 var offset = 0; 614 listItems.slice(0, active).each(function() { 615 offset += this.offsetHeight; 616 }); 617 if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) { 618 list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight()); 619 } else if(offset < list.scrollTop()) { 620 list.scrollTop(offset); 621 } 622 } 623 }; 624 625 function movePosition(step) { 626 active += step; 627 if (active < 0) { 628 active = listItems.size() - 1; 629 } else if (active >= listItems.size()) { 630 active = 0; 631 } 632 } 633 634 function limitNumberOfItems(available) { 635 return options.max && options.max < available 636 ? options.max 637 : available; 638 } 639 640 function fillList() { 641 list.empty(); 642 var max = limitNumberOfItems(data.length); 643 for (var i=0; i < max; i++) { 644 if (!data[i]) 645 continue; 646 var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term); 647 if ( formatted === false ) 648 continue; 649 var li = $("<li>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_event" : "ac_odd").appendTo(list)[0]; 650 $.data(li, "ac_data", data[i]); 651 } 652 listItems = list.find("li"); 653 if ( options.selectFirst ) { 654 listItems.slice(0, 1).addClass(CLASSES.ACTIVE); 655 active = 0; 656 } 657 list.bgiframe(); 658 } 659 660 return { 661 display: function(d, q) { 662 init(); 663 data = d; 664 term = q; 665 fillList(); 666 }, 667 next: function() { 668 moveSelect(1); 669 }, 670 prev: function() { 671 moveSelect(-1); 672 }, 673 pageUp: function() { 674 if (active != 0 && active - 8 < 0) { 675 moveSelect( -active ); 676 } else { 677 moveSelect(-8); 678 } 679 }, 680 pageDown: function() { 681 if (active != listItems.size() - 1 && active + 8 > listItems.size()) { 682 moveSelect( listItems.size() - 1 - active ); 683 } else { 684 moveSelect(8); 685 } 686 }, 687 hide: function() { 688 element && element.hide(); 689 active = -1; 690 }, 691 visible : function() { 692 return element && element.is(":visible"); 693 }, 694 current: function() { 695 return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]); 696 }, 697 show: function() { 698 var offset = $(input).offset(); 699 element.css({ 700 width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(), 701 top: offset.top + input.offsetHeight, 702 left: offset.left 703 }).show(); 704 if(options.scroll) { 705 list.scrollTop(0); 706 list.css({ 707 maxHeight: options.scrollHeight, 708 overflow: 'auto' 709 }); 710 711 if($.browser.msie && typeof document.body.style.maxHeight === "undefined") { 712 var listHeight = 0; 713 listItems.each(function() { 714 listHeight += this.offsetHeight; 715 }); 716 var scrollbarsVisible = listHeight > options.scrollHeight; 717 list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight ); 718 if (!scrollbarsVisible) { 719 // IE doesn't recalculate width when scrollbar disappears 720 listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) ); 721 } 722 } 723 724 } 725 }, 726 selected: function() { 727 var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE); 728 return selected && selected.length && $.data(selected[0], "ac_data"); 729 }, 730 unbind: function() { 731 element && element.remove(); 732 } 733 }; 734 }; 735 736 $.Autocompleter.Selection = function(field, start, end) { 737 if( field.createTextRange ){ 738 var selRange = field.createTextRange(); 739 selRange.collapse(true); 740 selRange.moveStart("character", start); 741 selRange.moveEnd("character", end); 742 selRange.select(); 743 } else if( field.setSelectionRange ){ 744 field.setSelectionRange(start, end); 745 } else { 746 if( field.selectionStart ){ 747 field.selectionStart = start; 748 field.selectionEnd = end; 749 } 750 } 751 field.focus(); 752 }; 753 754 })(jQuery);
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Nov 21 01:00:57 2024 | Cross-referenced by PHPXref 0.7.1 |