setTimeout( function () {
// Render special
var $special = context.data.$container.find( '.suggestions-special' );
- context.config.special.render.call( $special, context.data.$textbox.val() );
+ context.config.special.render.call( $special, context.data.$textbox.val(), context );
}, 1 );
}
},
*/
configure: function ( context, property, value ) {
var newCSS,
- $autoEllipseMe, $result, $results, $span,
+ $autoEllipseMe, $result, $results, childrenWidth,
i, expWidth, matchedText, maxWidth, text;
// Validate creation using fallback values
context.data.selectedWithMouse = true;
$.suggestions.highlight(
context,
- $(this).closest( '.suggestions-results div' ),
+ $(this).closest( '.suggestions-results .suggestions-result' ),
false
);
} )
.appendTo( $results );
// Allow custom rendering
if ( typeof context.config.result.render === 'function' ) {
- context.config.result.render.call( $result, context.config.suggestions[i] );
+ context.config.result.render.call( $result, context.config.suggestions[i], context );
} else {
// Add <span> with text
- if( context.config.highlightInput ) {
- matchedText = context.data.prevText;
- }
$result.append( $( '<span>' )
.css( 'whiteSpace', 'nowrap' )
.text( text )
);
+ }
- // Widen results box if needed
- // New width is only calculated here, applied later
- $span = $result.children( 'span' );
- if ( $span.outerWidth() > $result.width() && $span.outerWidth() > expWidth ) {
- // factor in any padding, margin, or border space on the parent
- expWidth = $span.outerWidth() + ( context.data.$container.width() - $span.parent().width());
- }
- $autoEllipseMe = $autoEllipseMe.add( $result );
+ if ( context.config.highlightInput ) {
+ matchedText = context.data.prevText;
}
+
+ // Widen results box if needed
+ // New width is only calculated here, applied later
+ childrenWidth = $result.children().outerWidth();
+ 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() );
+ }
+ $autoEllipseMe = $autoEllipseMe.add( $result );
}
// Apply new width for results box, if any
if ( expWidth > context.data.$container.width() ) {
result = context.data.$container.find( '.suggestions-result:last' );
} else {
result = selected.prev();
+ if ( !( result.length && result.hasClass( '.suggestions-result' ) ) ) {
+ // there is something in the DOM between selected element and the wrapper, bypass it
+ result = selected.parents( '.suggestions-results > *' ).prev().find( '.suggestions-result' ).eq(0);
+ }
+
if ( selected.length === 0 ) {
// we are at the beginning, so lets jump to the last item
if ( context.data.$container.find( '.suggestions-special' ).html() !== '' ) {
result = context.data.$container.find( '.suggestions-special' );
} else {
- result = context.data.$container.find( '.suggestions-results div:last' );
+ result = context.data.$container.find( '.suggestions-results .suggestions-result:last' );
}
}
}
} else if ( result === 'next' ) {
if ( selected.length === 0 ) {
// No item selected, go to the first one
- result = context.data.$container.find( '.suggestions-results div:first' );
+ result = context.data.$container.find( '.suggestions-results .suggestions-result:first' );
if ( result.length === 0 && context.data.$container.find( '.suggestions-special' ).html() !== '' ) {
// No suggestion exists, go to the special one directly
result = context.data.$container.find( '.suggestions-special' );
}
} else {
result = selected.next();
+ if ( !( result.length && result.hasClass( '.suggestions-result' ) ) ) {
+ // there is something in the DOM between selected element and the wrapper, bypass it
+ result = selected.parents( '.suggestions-results > *' ).next().find( '.suggestions-result' ).eq(0);
+ }
+
if ( selected.is( '.suggestions-special' ) ) {
result = $( [] );
} else if (
// textbox loses focus. Instead, listen for a mousedown followed
// by a mouseup on the same div.
.mousedown( function ( e ) {
- context.data.mouseDownOn = $( e.target ).closest( '.suggestions-results div' );
+ context.data.mouseDownOn = $( e.target ).closest( '.suggestions-results .suggestions-result' );
} )
.mouseup( function ( e ) {
- var $result = $( e.target ).closest( '.suggestions-results div' ),
+ var $result = $( e.target ).closest( '.suggestions-results .suggestions-result' ),
$other = context.data.mouseDownOn;
context.data.mouseDownOn = $( [] );
if ( $result.get( 0 ) !== $other.get( 0 ) ) {
return;
}
- $.suggestions.highlight( context, $result, true );
- context.data.$container.hide();
- if ( typeof context.config.result.select === 'function' ) {
- context.config.result.select.call( $result, context.data.$textbox );
+ // do not interfere with non-left clicks or if modifier keys are pressed (e.g. ctrl-click)
+ if ( !( e.which !== 1 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) ) {
+ $.suggestions.highlight( context, $result, true );
+ context.data.$container.hide();
+ if ( typeof context.config.result.select === 'function' ) {
+ context.config.result.select.call( $result, context.data.$textbox );
+ }
}
+ // but still restore focus to the textbox, so that the suggestions will be hidden properly
context.data.$textbox.focus();
} )
)
if ( $special.get( 0 ) !== $other.get( 0 ) ) {
return;
}
- context.data.$container.hide();
- if ( typeof context.config.special.select === 'function' ) {
- context.config.special.select.call( $special, context.data.$textbox );
+ // do not interfere with non-left clicks or if modifier keys are pressed (e.g. ctrl-click)
+ if ( !( e.which !== 1 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) ) {
+ context.data.$container.hide();
+ if ( typeof context.config.special.select === 'function' ) {
+ context.config.special.select.call( $special, context.data.$textbox );
+ }
}
+ // but still restore focus to the textbox, so that the suggestions will be hidden properly
context.data.$textbox.focus();
} )
.mousemove( function ( e ) {
*/
( function ( mw, $ ) {
$( document ).ready( function ( $ ) {
- var map, searchboxesSelectors,
+ var map, resultRenderCache, searchboxesSelectors,
// Region where the suggestions box will appear directly below
// (using the same width). Can be a container element or the input
// itself, depending on what suits best in the environment.
return;
}
+ // Compute form data for search suggestions functionality.
+ function computeResultRenderCache( context ) {
+ var $form, formAction, baseHref, linkParams;
+
+ // Compute common parameters for links' hrefs
+ $form = context.config.$region.closest( 'form' );
+
+ formAction = $form.attr( 'action' );
+ baseHref = formAction + ( formAction.match(/\?/) ? '&' : '?' );
+
+ linkParams = {};
+ $.each( $form.serializeArray(), function ( idx, obj ) {
+ linkParams[ obj.name ] = obj.value;
+ } );
+
+ return {
+ textParam: context.data.$textbox.attr( 'name' ),
+ linkParams: linkParams,
+ baseHref: baseHref
+ };
+ }
+
+ // The function used to render the suggestions.
+ function renderFunction( text, context ) {
+ if ( !resultRenderCache ) {
+ resultRenderCache = computeResultRenderCache( context );
+ }
+
+ // linkParams object is modified and reused
+ resultRenderCache.linkParams[ resultRenderCache.textParam ] = text;
+
+ // this is the container <div>, jQueryfied
+ this
+ .append(
+ // the <span> is needed for $.autoEllipsis to work
+ $( '<span>' )
+ .css( 'whiteSpace', 'nowrap' )
+ .text( text )
+ )
+ .wrap(
+ $( '<a>' )
+ .attr( 'href', resultRenderCache.baseHref + $.param( resultRenderCache.linkParams ) )
+ .addClass( 'mw-searchSuggest-link' )
+ );
+ }
+
+ function specialRenderFunction( query, context ) {
+ var $el = this;
+
+ if ( !resultRenderCache ) {
+ resultRenderCache = computeResultRenderCache( context );
+ }
+
+ // linkParams object is modified and reused
+ resultRenderCache.linkParams[ resultRenderCache.textParam ] = query;
+
+ if ( $el.children().length === 0 ) {
+ $el
+ .append(
+ $( '<div>' )
+ .addClass( 'special-label' )
+ .text( mw.msg( 'searchsuggest-containing' ) ),
+ $( '<div>' )
+ .addClass( 'special-query' )
+ .text( query )
+ .autoEllipsis()
+ )
+ .show();
+ } else {
+ $el.find( '.special-query' )
+ .text( query )
+ .autoEllipsis();
+ }
+
+ if ( $el.parent().hasClass( 'mw-searchSuggest-link' ) ) {
+ $el.parent().attr( 'href', resultRenderCache.baseHref + $.param( resultRenderCache.linkParams ) + '&fulltext=1' );
+ } else {
+ $el.wrap(
+ $( '<a>' )
+ .attr( 'href', resultRenderCache.baseHref + $.param( resultRenderCache.linkParams ) + '&fulltext=1' )
+ .addClass( 'mw-searchSuggest-link' )
+ );
+ }
+ }
+
// General suggestions functionality for all search boxes
searchboxesSelectors = [
// Primary searchbox on every page in standard skins
}
},
result: {
+ render: renderFunction,
select: function ( $input ) {
$input.closest( 'form' ).submit();
}
// Special suggestions functionality for skin-provided search box
$searchInput.suggestions( {
result: {
+ render: renderFunction,
select: function ( $input ) {
$input.closest( 'form' ).submit();
}
},
special: {
- render: function ( query ) {
- var $el = this;
- if ( $el.children().length === 0 ) {
- $el
- .append(
- $( '<div>' )
- .addClass( 'special-label' )
- .text( mw.msg( 'searchsuggest-containing' ) ),
- $( '<div>' )
- .addClass( 'special-query' )
- .text( query )
- .autoEllipsis()
- )
- .show();
- } else {
- $el.find( '.special-query' )
- .text( query )
- .autoEllipsis();
- }
- },
+ render: specialRenderFunction,
select: function ( $input ) {
$input.closest( 'form' ).append(
$( '<input type="hidden" name="fulltext" value="1"/>' )