wikimedia/mediawiki-extensions-Wikibase

View on GitHub
repo/resources/wikibase.ui.entitysearch.js

Summary

Maintainability
A
0 mins
Test Coverage
/**
 * Replacing the native MediaWiki search suggestions with the jQuery.wikibase.entitysearch widget.
 *
 * @license GPL-2.0-or-later
 * @author H. Snater < mediawiki@snater.com >
 */
( function ( mw ) {
    'use strict';
    require( './jquery.wikibase/jquery.wikibase.entitysearch.js' );

    $( function () {
        var $searchForm = $( '#searchform ' ),
            $input = $( '#searchInput' ),
            // Both inputs must be named "search" to support Firefox' smart keyword feature (T60467)
            $hiddenInput = $( '<input>' ).attr( { type: 'hidden', name: 'search' } );

        /**
         * @param {jQuery} $form
         * @return {string}
         */
        function getHref( $form ) {
            var href = $form.attr( 'action' ),
                params = {};

            href += href.indexOf( '?' ) === -1 ? '?' : '&';

            $form.serializeArray().forEach( function ( param ) {
                params[ param.name ] = param.value;
            } );

            params.search = $input.val();

            return href + $.param( params );
        }

        /**
         * Updates the suggestion list special item that triggers a full-text search.
         *
         * @param {jQuery.ui.ooMenu.CustomItem} containig
         */
        function updateSuggestionSpecial( containig ) {
            var $suggestionsSpecial = $( '.wb-entitysearch-suggestions .suggestions-special' );
            $suggestionsSpecial.find( '.special-query' ).text( $input.val() );

            containig.setLink( getHref( $searchForm ) + '&fulltext=1' );
        }

        /**
         * Removes the native search box suggestion list.
         *
         * @param {HTMLElement} input Search box node
         */
        function removeSuggestionContext( input ) {
            var context = $( input ).data( 'suggestionsContext' );

            if ( context && !context._wbDisabled ) {
                // Native fetch() updates/re-sets the data attribute with the suggestion context.
                context.config.fetch = function () {};
                // Flag as disabled so we don't keep overwriting `fetch`, but don't
                // delete context completely, in case there are some requests still
                // in-flight. T105637
                context._wbDisabled = true;
            }
        }

        var suggestionsPlaceholder = new $.ui.ooMenu.CustomItem(
            $( '<div>' ).append( $.createSpinner() )
        );

        var $searchContaining = $( '<div>' )
            .addClass( 'suggestions-special' )
            .append(
                $( '<div>' )
                    .addClass( 'special-label' )
                    .text( mw.msg( 'searchsuggest-containing' ) ),
                $( '<div>' )
                    .addClass( 'special-query' )
            );

        var searchContaining = new $.ui.ooMenu.CustomItem( $searchContaining, null, function () {
            mw.track( 'counter.MediaWiki.wikibase.repo.searchbox.footer.submit' );
            $searchForm.trigger( 'submit' );
        }, 'wb-entitysearch-suggestions' );

        var $searchMenu = $( '<ul>' ).ooMenu( {
            customItems: [ searchContaining ]
        } );

        // Must be placed in that order to support Firefox' smart keyword feature (T60467)
        $input.before( $hiddenInput );

        $input
        .one( 'eachchange.entitysearch', function () {
            removeSuggestionContext( this );
        } )
        .entitysearch( {
            url: mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/api.php',
            menu: $searchMenu.data( 'ooMenu' ),
            position: $.extend(
                {},
                $.wikibase.entityselector.prototype.options.position,
                { offset: '-1 2' }
            ),
            confineMinWidthTo: $searchForm,
            suggestionsPlaceholder: suggestionsPlaceholder
        } )
        .on( 'entityselectoropen', function ( event ) {
            updateSuggestionSpecial( searchContaining );
        } )
        .on( 'eachchange', function ( event, oldVal ) {
            $hiddenInput.val( '' );
            updateSuggestionSpecial( searchContaining );
        } )
        .on( 'entityselectorselected', function ( event, entityId ) {
            $hiddenInput.val( entityId );
        } );

        // TODO: Re-evaluate entity selector input (e.g. hitting "Go" after having hit "Search"
        // before. However, this will require triggering the entity selector's API call and waiting
        // for its response.

        $( '#searchButton' ).on( 'click keydown', function ( event ) {
            if ( !$input.data( 'entityselector' ) ) {
                return;
            }

            // If an entity is selected, redirect to that entity's page.
            if ( event.type === 'click'
                || event.keyCode === $.ui.keyCode.ENTER
                || event.keyCode === $.ui.keyCode.SPACE
            ) {
                var entity = $input.data( 'entityselector' ).selectedEntity();
                if ( entity && entity.url ) {
                    event.preventDefault(); // Prevent default form submit action.
                    window.location.href = entity.url;
                }
            }

        } );

        // Default form submit action: Imitate full-text search.
        // Since we are using the entity selector, if an entity is selected, the entity id is stored
        // in a hidden input element (which has ripped the "name" attribute from the original search
        // box). Therefore, the entity id needs to be replaced by the actual search box (entity
        // selector) content.
        $searchForm.on( 'submit', function ( event ) {
            $( this ).find( 'input[name="search"]' ).val( $input.val() );
        } );

    } );

}( mw ) );