[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 ) );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |