Merge "mediawiki.page.gallery.resize: Remove weird mw.hook call"
[lhc/web/wiklou.git] / resources / src / jquery / jquery.suggestions.js
index 7d200ff..08ed5d9 100644 (file)
  *             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
@@ -47,6 +51,8 @@
  */
 ( function ( $ ) {
 
+var hasOwn = Object.hasOwnProperty;
+
 $.suggestions = {
        /**
         * Cancel any delayed maybeFetch() call and callback the context so
@@ -90,18 +96,44 @@ $.suggestions = {
         */
        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()
+                                                               };
+                                                       }
+                                               }
+                                       );
                                }
                        }
 
@@ -215,10 +247,10 @@ $.suggestions = {
                                                        }
 
                                                        if ( expandFrom === 'start' ) {
-                                                               expandFrom = docDir === 'rtl' ? 'right': 'left';
+                                                               expandFrom = docDir === 'rtl' ? 'right' : 'left';
 
                                                        } else if ( expandFrom === 'end' ) {
-                                                               expandFrom = docDir === 'rtl' ? 'left': 'right';
+                                                               expandFrom = docDir === 'rtl' ? 'left' : 'right';
                                                        }
 
                                                        return expandFrom;
@@ -232,7 +264,7 @@ $.suggestions = {
                                                } else {
                                                        // Expand from right
                                                        newCSS.left = 'auto';
-                                                       newCSS.right = $( document ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() );
+                                                       newCSS.right = $( 'body' ).width() - ( context.config.$region.offset().left + context.config.$region.outerWidth() );
                                                }
 
                                                context.data.$container.css( newCSS );
@@ -267,10 +299,22 @@ $.suggestions = {
                                                        }
 
                                                        // 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() );
@@ -291,13 +335,17 @@ $.suggestions = {
                        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;
                }
        },
@@ -469,6 +517,8 @@ $.fn.suggestions = function () {
                                        suggestions: [],
                                        maxRows: 7,
                                        delay: 120,
+                                       cache: false,
+                                       cacheMaxAge: 60000,
                                        submitOnClick: false,
                                        maxExpandFactor: 3,
                                        expandFrom: 'auto',
@@ -507,6 +557,9 @@ $.fn.suggestions = function () {
                                // Text in textbox when suggestions were last fetched
                                prevText: null,
 
+                               // Cache of fetched suggestions
+                               cache: {},
+
                                // Number of results visible without scrolling
                                visibleResults: 0,