[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/resources/src/jquery/ -> jquery.suggestions.js (source)

   1  /**
   2   * This plugin provides a generic way to add suggestions to a text box.
   3   *
   4   * Usage:
   5   *
   6   * Set options:
   7   *        $( '#textbox' ).suggestions( { option1: value1, option2: value2 } );
   8   *        $( '#textbox' ).suggestions( option, value );
   9   * Get option:
  10   *        value = $( '#textbox' ).suggestions( option );
  11   * Initialize:
  12   *        $( '#textbox' ).suggestions();
  13   *
  14   * Options:
  15   *
  16   * fetch(query): Callback that should fetch suggestions and set the suggestions property.
  17   *      Executed in the context of the textbox
  18   *        Type: Function
  19   * cancel: Callback function to call when any pending asynchronous suggestions fetches
  20   *      should be canceled. Executed in the context of the textbox
  21   *        Type: Function
  22   * special: Set of callbacks for rendering and selecting
  23   *        Type: Object of Functions 'render' and 'select'
  24   * result: Set of callbacks for rendering and selecting
  25   *        Type: Object of Functions 'render' and 'select'
  26   * $region: jQuery selection of element to place the suggestions below and match width of
  27   *        Type: jQuery Object, Default: $( this )
  28   * suggestions: Suggestions to display
  29   *        Type: Array of strings
  30   * maxRows: Maximum number of suggestions to display at one time
  31   *        Type: Number, Range: 1 - 100, Default: 7
  32   * delay: Number of ms to wait for the user to stop typing
  33   *        Type: Number, Range: 0 - 1200, Default: 120
  34   * cache: Whether to cache results from a fetch
  35   *        Type: Boolean, Default: false
  36   * cacheMaxAge: Number of ms to cache results from a fetch
  37   *        Type: Number, Range: 1 - Infinity, Default: 60000 (1 minute)
  38   * submitOnClick: Whether to submit the form containing the textbox when a suggestion is clicked
  39   *        Type: Boolean, Default: false
  40   * maxExpandFactor: Maximum suggestions box width relative to the textbox width. If set
  41   *      to e.g. 2, the suggestions box will never be grown beyond 2 times the width of the textbox.
  42   *        Type: Number, Range: 1 - infinity, Default: 3
  43   * expandFrom: Which direction to offset the suggestion box from.
  44   *      Values 'start' and 'end' translate to left and right respectively depending on the
  45   *      directionality of the current document, according to $( 'html' ).css( 'direction' ).
  46   *      Type: String, default: 'auto', options: 'left', 'right', 'start', 'end', 'auto'.
  47   * positionFromLeft: Sets expandFrom=left, for backwards compatibility
  48   *        Type: Boolean, Default: true
  49   * highlightInput: Whether to hightlight matched portions of the input or not
  50   *        Type: Boolean, Default: false
  51   */
  52  ( function ( $ ) {
  53  
  54  var hasOwn = Object.hasOwnProperty;
  55  
  56  $.suggestions = {
  57      /**
  58       * Cancel any delayed maybeFetch() call and callback the context so
  59       * they can cancel any async fetching if they use AJAX or something.
  60       */
  61      cancel: function ( context ) {
  62          if ( context.data.timerID !== null ) {
  63              clearTimeout( context.data.timerID );
  64          }
  65          if ( $.isFunction( context.config.cancel ) ) {
  66              context.config.cancel.call( context.data.$textbox );
  67          }
  68      },
  69  
  70      /**
  71       * Hide the element with suggestions and clean up some state.
  72       */
  73      hide: function ( context ) {
  74          // Remove any highlights, including on "special" items
  75          context.data.$container.find( '.suggestions-result-current' ).removeClass( 'suggestions-result-current' );
  76          // Hide the container
  77          context.data.$container.hide();
  78      },
  79  
  80      /**
  81       * Restore the text the user originally typed in the textbox, before it
  82       * was overwritten by highlight(). This restores the value the currently
  83       * displayed suggestions are based on, rather than the value just before
  84       * highlight() overwrote it; the former is arguably slightly more sensible.
  85       */
  86      restore: function ( context ) {
  87          context.data.$textbox.val( context.data.prevText );
  88      },
  89  
  90      /**
  91       * Ask the user-specified callback for new suggestions. Any previous delayed
  92       * call to this function still pending will be canceled. If the value in the
  93       * textbox is empty or hasn't changed since the last time suggestions were fetched,
  94       * this function does nothing.
  95       * @param {Boolean} delayed Whether or not to delay this by the currently configured amount of time
  96       */
  97      update: function ( context, delayed ) {
  98  		function maybeFetch() {
  99              var val = context.data.$textbox.val(),
 100                  cache = context.data.cache,
 101                  cacheHit;
 102  
 103              // Only fetch if the value in the textbox changed and is not empty, or if the results were hidden
 104              // if the textbox is empty then clear the result div, but leave other settings intouched
 105              if ( val.length === 0 ) {
 106                  $.suggestions.hide( context );
 107                  context.data.prevText = '';
 108              } else if (
 109                  val !== context.data.prevText ||
 110                  !context.data.$container.is( ':visible' )
 111              ) {
 112                  context.data.prevText = val;
 113                  // Try cache first
 114                  if ( context.config.cache && hasOwn.call( cache, val ) ) {
 115                      if ( +new Date() - cache[ val ].timestamp < context.config.cacheMaxAge ) {
 116                          context.data.$textbox.suggestions( 'suggestions', cache[ val ].suggestions );
 117                          cacheHit = true;
 118                      } else {
 119                          // Cache expired
 120                          delete cache[ val ];
 121                      }
 122                  }
 123                  if ( !cacheHit && typeof context.config.fetch === 'function' ) {
 124                      context.config.fetch.call(
 125                          context.data.$textbox,
 126                          val,
 127                          function ( suggestions ) {
 128                              context.data.$textbox.suggestions( 'suggestions', suggestions );
 129                              if ( context.config.cache ) {
 130                                  cache[ val ] = {
 131                                      suggestions: suggestions,
 132                                      timestamp: +new Date()
 133                                  };
 134                              }
 135                          }
 136                      );
 137                  }
 138              }
 139  
 140              // Always update special rendering
 141              $.suggestions.special( context );
 142          }
 143  
 144          // Cancels any delayed maybeFetch call, and invokes context.config.cancel.
 145          $.suggestions.cancel( context );
 146  
 147          if ( delayed ) {
 148              // To avoid many started/aborted requests while typing, we're gonna take a short
 149              // break before trying to fetch data.
 150              context.data.timerID = setTimeout( maybeFetch, context.config.delay );
 151          } else {
 152              maybeFetch();
 153          }
 154      },
 155  
 156      special: function ( context ) {
 157          // Allow custom rendering - but otherwise don't do any rendering
 158          if ( typeof context.config.special.render === 'function' ) {
 159              // Wait for the browser to update the value
 160              setTimeout( function () {
 161                  // Render special
 162                  var $special = context.data.$container.find( '.suggestions-special' );
 163                  context.config.special.render.call( $special, context.data.$textbox.val(), context );
 164              }, 1 );
 165          }
 166      },
 167  
 168      /**
 169       * Sets the value of a property, and updates the widget accordingly
 170       * @param property String Name of property
 171       * @param value Mixed Value to set property with
 172       */
 173      configure: function ( context, property, value ) {
 174          var newCSS,
 175              $result, $results, $spanForWidth, childrenWidth,
 176              i, expWidth, maxWidth, text;
 177  
 178          // Validate creation using fallback values
 179          switch ( property ) {
 180              case 'fetch':
 181              case 'cancel':
 182              case 'special':
 183              case 'result':
 184              case '$region':
 185              case 'expandFrom':
 186                  context.config[property] = value;
 187                  break;
 188              case 'suggestions':
 189                  context.config[property] = value;
 190                  // Update suggestions
 191                  if ( context.data !== undefined ) {
 192                      if ( context.data.$textbox.val().length === 0 ) {
 193                          // Hide the div when no suggestion exist
 194                          $.suggestions.hide( context );
 195                      } else {
 196                          // Rebuild the suggestions list
 197                          context.data.$container.show();
 198                          // Update the size and position of the list
 199                          newCSS = {
 200                              top: context.config.$region.offset().top + context.config.$region.outerHeight(),
 201                              bottom: 'auto',
 202                              width: context.config.$region.outerWidth(),
 203                              height: 'auto'
 204                          };
 205  
 206                          // Process expandFrom, after this it is set to left or right.
 207                          context.config.expandFrom = ( function ( expandFrom ) {
 208                              var regionWidth, docWidth, regionCenter, docCenter,
 209                                  docDir = $( document.documentElement ).css( 'direction' ),
 210                                  $region = context.config.$region;
 211  
 212                              // Backwards compatible
 213                              if ( context.config.positionFromLeft ) {
 214                                  expandFrom = 'left';
 215  
 216                              // Catch invalid values, default to 'auto'
 217                              } else if ( $.inArray( expandFrom, ['left', 'right', 'start', 'end', 'auto'] ) === -1 ) {
 218                                  expandFrom = 'auto';
 219                              }
 220  
 221                              if ( expandFrom === 'auto' ) {
 222                                  if ( $region.data( 'searchsuggest-expand-dir' ) ) {
 223                                      // If the markup explicitly contains a direction, use it.
 224                                      expandFrom = $region.data( 'searchsuggest-expand-dir' );
 225                                  } else {
 226                                      regionWidth = $region.outerWidth();
 227                                      docWidth = $( document ).width();
 228                                      if ( regionWidth > ( 0.85 * docWidth ) ) {
 229                                          // If the input size takes up more than 85% of the document horizontally
 230                                          // expand the suggestions to the writing direction's native end.
 231                                          expandFrom = 'start';
 232                                      } else {
 233                                          // Calculate the center points of the input and document
 234                                          regionCenter = $region.offset().left + regionWidth / 2;
 235                                          docCenter = docWidth / 2;
 236                                          if ( Math.abs( regionCenter - docCenter ) < ( 0.10 * docCenter ) ) {
 237                                              // If the input's center is within 10% of the document center
 238                                              // use the writing direction's native end.
 239                                              expandFrom = 'start';
 240                                          } else {
 241                                              // Otherwise expand the input from the closest side of the page,
 242                                              // towards the side of the page with the most free open space
 243                                              expandFrom = regionCenter > docCenter ? 'right' : 'left';
 244                                          }
 245                                      }
 246                                  }
 247                              }
 248  
 249                              if ( expandFrom === 'start' ) {
 250                                  expandFrom = docDir === 'rtl' ? 'right' : 'left';
 251  
 252                              } else if ( expandFrom === 'end' ) {
 253                                  expandFrom = docDir === 'rtl' ? 'left' : 'right';
 254                              }
 255  
 256                              return expandFrom;
 257  
 258                          }( context.config.expandFrom ) );
 259  
 260                          if ( context.config.expandFrom === 'left' ) {
 261                              // Expand from left
 262                              newCSS.left = context.config.$region.offset().left;
 263                              newCSS.right = 'auto';
 264                          } else {
 265                              // Expand from right
 266                              newCSS.left = 'auto';
 267                              newCSS.right = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() );
 268                          }
 269  
 270                          context.data.$container.css( newCSS );
 271                          $results = context.data.$container.children( '.suggestions-results' );
 272                          $results.empty();
 273                          expWidth = -1;
 274                          for ( i = 0; i < context.config.suggestions.length; i++ ) {
 275                              /*jshint loopfunc:true */
 276                              text = context.config.suggestions[i];
 277                              $result = $( '<div>' )
 278                                  .addClass( 'suggestions-result' )
 279                                  .attr( 'rel', i )
 280                                  .data( 'text', context.config.suggestions[i] )
 281                                  .mousemove( function () {
 282                                      context.data.selectedWithMouse = true;
 283                                      $.suggestions.highlight(
 284                                          context,
 285                                          $( this ).closest( '.suggestions-results .suggestions-result' ),
 286                                          false
 287                                      );
 288                                  } )
 289                                  .appendTo( $results );
 290                              // Allow custom rendering
 291                              if ( typeof context.config.result.render === 'function' ) {
 292                                  context.config.result.render.call( $result, context.config.suggestions[i], context );
 293                              } else {
 294                                  $result.text( text );
 295                              }
 296  
 297                              if ( context.config.highlightInput ) {
 298                                  $result.highlightText( context.data.prevText );
 299                              }
 300  
 301                              // Widen results box if needed (new width is only calculated here, applied later).
 302  
 303                              // The monstrosity below accomplishes two things:
 304                              // * Wraps the text contents in a DOM element, so that we can know its width. There is
 305                              //   no way to directly access the width of a text node, and we can't use the parent
 306                              //   node width as it has text-overflow: ellipsis; and overflow: hidden; applied to
 307                              //   it, which trims it to a smaller width.
 308                              // * Temporarily applies position: absolute; to the wrapper to pull it out of normal
 309                              //   document flow. Otherwise the CSS text-overflow: ellipsis; and overflow: hidden;
 310                              //   rules would cause some browsers (at least all versions of IE from 6 to 11) to
 311                              //   still report the "trimmed" width. This should not be done in regular CSS
 312                              //   stylesheets as we don't want this rule to apply to other <span> elements, like
 313                              //   the ones generated by jquery.highlightText.
 314                              $spanForWidth = $result.wrapInner( '<span>' ).children();
 315                              childrenWidth = $spanForWidth.css( 'position', 'absolute' ).outerWidth();
 316                              $spanForWidth.contents().unwrap();
 317  
 318                              if ( childrenWidth > $result.width() && childrenWidth > expWidth ) {
 319                                  // factor in any padding, margin, or border space on the parent
 320                                  expWidth = childrenWidth + ( context.data.$container.width() - $result.width() );
 321                              }
 322                          }
 323  
 324                          // Apply new width for results box, if any
 325                          if ( expWidth > context.data.$container.width() ) {
 326                              maxWidth = context.config.maxExpandFactor * context.data.$textbox.width();
 327                              context.data.$container.width( Math.min( expWidth, maxWidth ) );
 328                          }
 329                      }
 330                  }
 331                  break;
 332              case 'maxRows':
 333                  context.config[property] = Math.max( 1, Math.min( 100, value ) );
 334                  break;
 335              case 'delay':
 336                  context.config[property] = Math.max( 0, Math.min( 1200, value ) );
 337                  break;
 338              case 'cacheMaxAge':
 339                  context.config[property] = Math.max( 1, value );
 340                  break;
 341              case 'maxExpandFactor':
 342                  context.config[property] = Math.max( 1, value );
 343                  break;
 344              case 'cache':
 345              case 'submitOnClick':
 346              case 'positionFromLeft':
 347              case 'highlightInput':
 348                  context.config[property] = !!value;
 349                  break;
 350          }
 351      },
 352  
 353      /**
 354       * Highlight a result in the results table
 355       * @param result <tr> to highlight: jQuery object, or 'prev' or 'next'
 356       * @param updateTextbox If true, put the suggestion in the textbox
 357       */
 358      highlight: function ( context, result, updateTextbox ) {
 359          var selected = context.data.$container.find( '.suggestions-result-current' );
 360          if ( !result.get || selected.get( 0 ) !== result.get( 0 ) ) {
 361              if ( result === 'prev' ) {
 362                  if ( selected.hasClass( 'suggestions-special' ) ) {
 363                      result = context.data.$container.find( '.suggestions-result:last' );
 364                  } else {
 365                      result = selected.prev();
 366                      if ( !( result.length && result.hasClass( 'suggestions-result' ) ) ) {
 367                          // there is something in the DOM between selected element and the wrapper, bypass it
 368                          result = selected.parents( '.suggestions-results > *' ).prev().find( '.suggestions-result' ).eq( 0 );
 369                      }
 370  
 371                      if ( selected.length === 0 ) {
 372                          // we are at the beginning, so lets jump to the last item
 373                          if ( context.data.$container.find( '.suggestions-special' ).html() !== '' ) {
 374                              result = context.data.$container.find( '.suggestions-special' );
 375                          } else {
 376                              result = context.data.$container.find( '.suggestions-results .suggestions-result:last' );
 377                          }
 378                      }
 379                  }
 380              } else if ( result === 'next' ) {
 381                  if ( selected.length === 0 ) {
 382                      // No item selected, go to the first one
 383                      result = context.data.$container.find( '.suggestions-results .suggestions-result:first' );
 384                      if ( result.length === 0 && context.data.$container.find( '.suggestions-special' ).html() !== '' ) {
 385                          // No suggestion exists, go to the special one directly
 386                          result = context.data.$container.find( '.suggestions-special' );
 387                      }
 388                  } else {
 389                      result = selected.next();
 390                      if ( !( result.length && result.hasClass( 'suggestions-result' ) ) ) {
 391                          // there is something in the DOM between selected element and the wrapper, bypass it
 392                          result = selected.parents( '.suggestions-results > *' ).next().find( '.suggestions-result' ).eq( 0 );
 393                      }
 394  
 395                      if ( selected.hasClass( 'suggestions-special' ) ) {
 396                          result = $( [] );
 397                      } else if (
 398                          result.length === 0 &&
 399                          context.data.$container.find( '.suggestions-special' ).html() !== ''
 400                      ) {
 401                          // We were at the last item, jump to the specials!
 402                          result = context.data.$container.find( '.suggestions-special' );
 403                      }
 404                  }
 405              }
 406              selected.removeClass( 'suggestions-result-current' );
 407              result.addClass( 'suggestions-result-current' );
 408          }
 409          if ( updateTextbox ) {
 410              if ( result.length === 0 || result.is( '.suggestions-special' ) ) {
 411                  $.suggestions.restore( context );
 412              } else {
 413                  context.data.$textbox.val( result.data( 'text' ) );
 414                  // .val() doesn't call any event handlers, so
 415                  // let the world know what happened
 416                  context.data.$textbox.change();
 417              }
 418              context.data.$textbox.trigger( 'change' );
 419          }
 420      },
 421  
 422      /**
 423       * Respond to keypress event
 424       * @param key Integer Code of key pressed
 425       */
 426      keypress: function ( e, context, key ) {
 427          var selected,
 428              wasVisible = context.data.$container.is( ':visible' ),
 429              preventDefault = false;
 430  
 431          switch ( key ) {
 432              // Arrow down
 433              case 40:
 434                  if ( wasVisible ) {
 435                      $.suggestions.highlight( context, 'next', true );
 436                      context.data.selectedWithMouse = false;
 437                  } else {
 438                      $.suggestions.update( context, false );
 439                  }
 440                  preventDefault = true;
 441                  break;
 442              // Arrow up
 443              case 38:
 444                  if ( wasVisible ) {
 445                      $.suggestions.highlight( context, 'prev', true );
 446                      context.data.selectedWithMouse = false;
 447                  }
 448                  preventDefault = wasVisible;
 449                  break;
 450              // Escape
 451              case 27:
 452                  $.suggestions.hide( context );
 453                  $.suggestions.restore( context );
 454                  $.suggestions.cancel( context );
 455                  context.data.$textbox.trigger( 'change' );
 456                  preventDefault = wasVisible;
 457                  break;
 458              // Enter
 459              case 13:
 460                  preventDefault = wasVisible;
 461                  selected = context.data.$container.find( '.suggestions-result-current' );
 462                  $.suggestions.hide( context );
 463                  if ( selected.length === 0 || context.data.selectedWithMouse ) {
 464                      // If nothing is selected or if something was selected with the mouse
 465                      // cancel any current requests and allow the form to be submitted
 466                      // (simply don't prevent default behavior).
 467                      $.suggestions.cancel( context );
 468                      preventDefault = false;
 469                  } else if ( selected.is( '.suggestions-special' ) ) {
 470                      if ( typeof context.config.special.select === 'function' ) {
 471                          // Allow the callback to decide whether to prevent default or not
 472                          if ( context.config.special.select.call( selected, context.data.$textbox ) === true ) {
 473                              preventDefault = false;
 474                          }
 475                      }
 476                  } else {
 477                      $.suggestions.highlight( context, selected, true );
 478  
 479                      if ( typeof context.config.result.select === 'function' ) {
 480                          // Allow the callback to decide whether to prevent default or not
 481                          if ( context.config.result.select.call( selected, context.data.$textbox ) === true ) {
 482                              preventDefault = false;
 483                          }
 484                      }
 485                  }
 486                  break;
 487              default:
 488                  $.suggestions.update( context, true );
 489                  break;
 490          }
 491          if ( preventDefault ) {
 492              e.preventDefault();
 493              e.stopPropagation();
 494          }
 495      }
 496  };
 497  $.fn.suggestions = function () {
 498  
 499      // Multi-context fields
 500      var returnValue,
 501          args = arguments;
 502  
 503      $( this ).each( function () {
 504          var context, key;
 505  
 506          /* Construction / Loading */
 507  
 508          context = $( this ).data( 'suggestions-context' );
 509          if ( context === undefined || context === null ) {
 510              context = {
 511                  config: {
 512                      fetch: function () {},
 513                      cancel: function () {},
 514                      special: {},
 515                      result: {},
 516                      $region: $( this ),
 517                      suggestions: [],
 518                      maxRows: 7,
 519                      delay: 120,
 520                      cache: false,
 521                      cacheMaxAge: 60000,
 522                      submitOnClick: false,
 523                      maxExpandFactor: 3,
 524                      expandFrom: 'auto',
 525                      highlightInput: false
 526                  }
 527              };
 528          }
 529  
 530          /* API */
 531  
 532          // Handle various calling styles
 533          if ( args.length > 0 ) {
 534              if ( typeof args[0] === 'object' ) {
 535                  // Apply set of properties
 536                  for ( key in args[0] ) {
 537                      $.suggestions.configure( context, key, args[0][key] );
 538                  }
 539              } else if ( typeof args[0] === 'string' ) {
 540                  if ( args.length > 1 ) {
 541                      // Set property values
 542                      $.suggestions.configure( context, args[0], args[1] );
 543                  } else if ( returnValue === null || returnValue === undefined ) {
 544                      // Get property values, but don't give access to internal data - returns only the first
 545                      returnValue = ( args[0] in context.config ? undefined : context.config[args[0]] );
 546                  }
 547              }
 548          }
 549  
 550          /* Initialization */
 551  
 552          if ( context.data === undefined ) {
 553              context.data = {
 554                  // ID of running timer
 555                  timerID: null,
 556  
 557                  // Text in textbox when suggestions were last fetched
 558                  prevText: null,
 559  
 560                  // Cache of fetched suggestions
 561                  cache: {},
 562  
 563                  // Number of results visible without scrolling
 564                  visibleResults: 0,
 565  
 566                  // Suggestion the last mousedown event occurred on
 567                  mouseDownOn: $( [] ),
 568                  $textbox: $( this ),
 569                  selectedWithMouse: false
 570              };
 571  
 572              context.data.$container = $( '<div>' )
 573                  .css( 'display', 'none' )
 574                  .addClass( 'suggestions' )
 575                  .append(
 576                      $( '<div>' ).addClass( 'suggestions-results' )
 577                          // Can't use click() because the container div is hidden when the
 578                          // textbox loses focus. Instead, listen for a mousedown followed
 579                          // by a mouseup on the same div.
 580                          .mousedown( function ( e ) {
 581                              context.data.mouseDownOn = $( e.target ).closest( '.suggestions-results .suggestions-result' );
 582                          } )
 583                          .mouseup( function ( e ) {
 584                              var $result = $( e.target ).closest( '.suggestions-results .suggestions-result' ),
 585                                  $other = context.data.mouseDownOn;
 586  
 587                              context.data.mouseDownOn = $( [] );
 588                              if ( $result.get( 0 ) !== $other.get( 0 ) ) {
 589                                  return;
 590                              }
 591                              // Do not interfere with non-left clicks or if modifier keys are pressed (e.g. ctrl-click).
 592                              if ( !( e.which !== 1 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) ) {
 593                                  $.suggestions.highlight( context, $result, true );
 594                                  if ( typeof context.config.result.select === 'function' ) {
 595                                      context.config.result.select.call( $result, context.data.$textbox );
 596                                  }
 597                                  // This will hide the link we're just clicking on, which causes problems
 598                                  // when done synchronously in at least Firefox 3.6 (bug 62858).
 599                                  setTimeout( function () {
 600                                      $.suggestions.hide( context );
 601                                  }, 0 );
 602                              }
 603                              // Always bring focus to the textbox, as that's probably where the user expects it
 604                              // if they were just typing.
 605                              context.data.$textbox.focus();
 606                          } )
 607                  )
 608                  .append(
 609                      $( '<div>' ).addClass( 'suggestions-special' )
 610                          // Can't use click() because the container div is hidden when the
 611                          // textbox loses focus. Instead, listen for a mousedown followed
 612                          // by a mouseup on the same div.
 613                          .mousedown( function ( e ) {
 614                              context.data.mouseDownOn = $( e.target ).closest( '.suggestions-special' );
 615                          } )
 616                          .mouseup( function ( e ) {
 617                              var $special = $( e.target ).closest( '.suggestions-special' ),
 618                                  $other = context.data.mouseDownOn;
 619  
 620                              context.data.mouseDownOn = $( [] );
 621                              if ( $special.get( 0 ) !== $other.get( 0 ) ) {
 622                                  return;
 623                              }
 624                              // Do not interfere with non-left clicks or if modifier keys are pressed (e.g. ctrl-click).
 625                              if ( !( e.which !== 1 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) ) {
 626                                  if ( typeof context.config.special.select === 'function' ) {
 627                                      context.config.special.select.call( $special, context.data.$textbox );
 628                                  }
 629                                  // This will hide the link we're just clicking on, which causes problems
 630                                  // when done synchronously in at least Firefox 3.6 (bug 62858).
 631                                  setTimeout( function () {
 632                                      $.suggestions.hide( context );
 633                                  }, 0 );
 634                              }
 635                              // Always bring focus to the textbox, as that's probably where the user expects it
 636                              // if they were just typing.
 637                              context.data.$textbox.focus();
 638                          } )
 639                          .mousemove( function ( e ) {
 640                              context.data.selectedWithMouse = true;
 641                              $.suggestions.highlight(
 642                                  context, $( e.target ).closest( '.suggestions-special' ), false
 643                              );
 644                          } )
 645                  )
 646                  .appendTo( $( 'body' ) );
 647  
 648              $( this )
 649                  // Stop browser autocomplete from interfering
 650                  .attr( 'autocomplete', 'off' )
 651                  .keydown( function ( e ) {
 652                      // Store key pressed to handle later
 653                      context.data.keypressed = e.which;
 654                      context.data.keypressedCount = 0;
 655                  } )
 656                  .keypress( function ( e ) {
 657                      context.data.keypressedCount++;
 658                      $.suggestions.keypress( e, context, context.data.keypressed );
 659                  } )
 660                  .keyup( function ( e ) {
 661                      // Some browsers won't throw keypress() for arrow keys. If we got a keydown and a keyup without a
 662                      // keypress in between, solve it
 663                      if ( context.data.keypressedCount === 0 ) {
 664                          $.suggestions.keypress( e, context, context.data.keypressed );
 665                      }
 666                  } )
 667                  .blur( function () {
 668                      // When losing focus because of a mousedown
 669                      // on a suggestion, don't hide the suggestions
 670                      if ( context.data.mouseDownOn.length > 0 ) {
 671                          return;
 672                      }
 673                      $.suggestions.hide( context );
 674                      $.suggestions.cancel( context );
 675                  } );
 676          }
 677  
 678          // Store the context for next time
 679          $( this ).data( 'suggestions-context', context );
 680      } );
 681      return returnValue !== undefined ? returnValue : $( this );
 682  };
 683  
 684  }( jQuery ) );


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1