[ Index ]

PHP Cross Reference of BuddyPress

title

Body

[close]

/src/bp-messages/js/autocomplete/ -> jquery.autocomplete.js (source)

   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);


Generated: Thu Nov 21 01:00:57 2024 Cross-referenced by PHPXref 0.7.1