* Type: Number, Range: 1 - 100, Default: 7
* delay: Number of ms to wait for the user to stop typing
* Type: Number, Range: 0 - 1200, Default: 120
+ * cache: Whether to cache results from a fetch
+ * Type: Boolean, Default: false
+ * cacheMaxAge: Number of ms to cache results from a fetch
+ * Type: Number, Range: 1 - Infinity, Default: 60000 (1 minute)
* submitOnClick: Whether to submit the form containing the textbox when a suggestion is clicked
* Type: Boolean, Default: false
* maxExpandFactor: Maximum suggestions box width relative to the textbox width. If set
*/
( function ( $ ) {
+var hasOwn = Object.hasOwnProperty;
+
$.suggestions = {
/**
* Cancel any delayed maybeFetch() call and callback the context so
*/
update: function ( context, delayed ) {
function maybeFetch() {
+ var val = context.data.$textbox.val(),
+ cache = context.data.cache,
+ cacheHit;
+
// Only fetch if the value in the textbox changed and is not empty, or if the results were hidden
// if the textbox is empty then clear the result div, but leave other settings intouched
- if ( context.data.$textbox.val().length === 0 ) {
+ if ( val.length === 0 ) {
$.suggestions.hide( context );
context.data.prevText = '';
} else if (
- context.data.$textbox.val() !== context.data.prevText ||
+ val !== context.data.prevText ||
!context.data.$container.is( ':visible' )
) {
- if ( typeof context.config.fetch === 'function' ) {
- context.data.prevText = context.data.$textbox.val();
- context.config.fetch.call( context.data.$textbox, context.data.$textbox.val() );
+ context.data.prevText = val;
+ // Try cache first
+ if ( context.config.cache && hasOwn.call( cache, val ) ) {
+ if ( +new Date() - cache[ val ].timestamp < context.config.cacheMaxAge ) {
+ context.data.$textbox.suggestions( 'suggestions', cache[ val ].suggestions );
+ cacheHit = true;
+ } else {
+ // Cache expired
+ delete cache[ val ];
+ }
+ }
+ if ( !cacheHit && typeof context.config.fetch === 'function' ) {
+ context.config.fetch.call(
+ context.data.$textbox,
+ val,
+ function ( suggestions ) {
+ context.data.$textbox.suggestions( 'suggestions', suggestions );
+ if ( context.config.cache ) {
+ cache[ val ] = {
+ suggestions: suggestions,
+ timestamp: +new Date()
+ };
+ }
+ }
+ );
}
}
} else {
regionWidth = $region.outerWidth();
docWidth = $( document ).width();
- if ( ( regionWidth / docWidth ) > 0.85 ) {
+ if ( regionWidth > ( 0.85 * docWidth ) ) {
// If the input size takes up more than 85% of the document horizontally
// expand the suggestions to the writing direction's native end.
expandFrom = 'start';
// Calculate the center points of the input and document
regionCenter = $region.offset().left + regionWidth / 2;
docCenter = docWidth / 2;
- if ( Math.abs( regionCenter - docCenter ) / docCenter < 0.10 ) {
+ if ( Math.abs( regionCenter - docCenter ) < ( 0.10 * docCenter ) ) {
// If the input's center is within 10% of the document center
// use the writing direction's native end.
expandFrom = 'start';
}
// Widen results box if needed (new width is only calculated here, applied later).
- // We need this awful hack to calculate the actual pre-ellipsis width.
+
+ // The monstrosity below accomplishes two things:
+ // * Wraps the text contents in a DOM element, so that we can know its width. There is
+ // no way to directly access the width of a text node, and we can't use the parent
+ // node width as it has text-overflow: ellipsis; and overflow: hidden; applied to
+ // it, which trims it to a smaller width.
+ // * Temporarily applies position: absolute; to the wrapper to pull it out of normal
+ // document flow. Otherwise the CSS text-overflow: ellipsis; and overflow: hidden;
+ // rules would cause some browsers (at least all versions of IE from 6 to 11) to
+ // still report the "trimmed" width. This should not be done in regular CSS
+ // stylesheets as we don't want this rule to apply to other <span> elements, like
+ // the ones generated by jquery.highlightText.
$spanForWidth = $result.wrapInner( '<span>' ).children();
- childrenWidth = $spanForWidth.outerWidth();
+ childrenWidth = $spanForWidth.css( 'position', 'absolute' ).outerWidth();
$spanForWidth.contents().unwrap();
+
if ( childrenWidth > $result.width() && childrenWidth > expWidth ) {
// factor in any padding, margin, or border space on the parent
expWidth = childrenWidth + ( context.data.$container.width() - $result.width() );
case 'delay':
context.config[property] = Math.max( 0, Math.min( 1200, value ) );
break;
+ case 'cacheMaxAge':
+ context.config[property] = Math.max( 1, value );
+ break;
case 'maxExpandFactor':
context.config[property] = Math.max( 1, value );
break;
+ case 'cache':
case 'submitOnClick':
case 'positionFromLeft':
case 'highlightInput':
- context.config[property] = value ? true : false;
+ context.config[property] = !!value;
break;
}
},
suggestions: [],
maxRows: 7,
delay: 120,
+ cache: false,
+ cacheMaxAge: 60000,
submitOnClick: false,
maxExpandFactor: 3,
expandFrom: 'auto',
// Text in textbox when suggestions were last fetched
prevText: null,
+ // Cache of fetched suggestions
+ cache: {},
+
// Number of results visible without scrolling
visibleResults: 0,