diff --git a/modules/search/instant-search/components/scroll-button.jsx b/modules/search/instant-search/components/scroll-button.jsx new file mode 100644 index 0000000000000..9e6fb0721b500 --- /dev/null +++ b/modules/search/instant-search/components/scroll-button.jsx @@ -0,0 +1,47 @@ +/** @jsx h */ + +/** + * External dependencies + */ +import { h, Component } from 'preact'; +import { __ } from '@wordpress/i18n'; +// NOTE: We only import the debounce package here for to reduced bundle size. +// Do not import the entire lodash library! +// eslint-disable-next-line lodash/import-scope +import debounce from 'lodash/debounce'; + +class ScrollButton extends Component { + componentDidMount() { + this.props.enableAutoScrolling && document.addEventListener( 'scroll', this.checkScroll ); + } + componentDidUnmount() { + document.removeEventListener( 'scroll', this.checkScroll ); + } + + checkScroll = debounce( () => { + if ( + this.props.enableAutoScrolling && + window.innerHeight + window.scrollY === document.body.offsetHeight + ) { + this.props.onClick(); + } + }, 100 ); + + render() { + return ( + + ); + } +} + +export default ScrollButton; diff --git a/modules/search/instant-search/components/search-results.jsx b/modules/search/instant-search/components/search-results.jsx index 61f57c077a792..a115e1fc685e7 100644 --- a/modules/search/instant-search/components/search-results.jsx +++ b/modules/search/instant-search/components/search-results.jsx @@ -10,6 +10,7 @@ import { h, Component } from 'preact'; * Internal dependencies */ import SearchResultMinimal from './search-result-minimal'; +import ScrollButton from './scroll-button'; class SearchResults extends Component { render_result( result ) { @@ -24,6 +25,7 @@ class SearchResults extends Component { render() { const { results = [], query, total = 0, corrected_query = false } = this.props; + if ( query === '' ) { return
; } @@ -53,6 +55,13 @@ class SearchResults extends Component {

) } { results.map( result => this.render_result( result ) ) } + { this.props.hasNextPage && ( + + ) }
); } diff --git a/modules/search/instant-search/components/search-widget.jsx b/modules/search/instant-search/components/search-widget.jsx index 7c591fd0bc8f1..9696a5514b2c3 100644 --- a/modules/search/instant-search/components/search-widget.jsx +++ b/modules/search/instant-search/components/search-widget.jsx @@ -31,13 +31,17 @@ class SearchApp extends Component { super( ...arguments ); this.input = Preact.createRef(); this.requestId = 0; + + // TODO: Rework these lines. We shouldn't reassign properties. this.props.resultFormat = 'minimal'; this.props.aggregations = buildFilterAggregations( this.props.options.widgets ); this.props.widgets = this.props.options.widgets ? this.props.options.widgets : []; + this.state = { + isLoading: false, query: this.props.initialValue, + response: {}, sort: this.props.initialSort, - results: {}, }; this.getResults = debounce( this.getResults, 500 ); this.getResults( this.state.query, getFilterQuery(), this.state.sort ); @@ -72,30 +76,51 @@ class SearchApp extends Component { this.getResults( this.state.query, getFilterQuery(), getSortQuery() ); }; - getResults = ( query, filter, sort ) => { + getResults = ( query, filter, sort, pageHandle ) => { if ( query ) { this.requestId++; const requestId = this.requestId; - search( { - aggregations: this.props.aggregations, - filter, - query, - resultFormat: this.props.options.resultFormat, - siteId: this.props.options.siteId, - sort, - } ).then( results => { - if ( this.requestId === requestId ) { - this.setState( { results } ); - } + this.setState( { isLoading: true }, () => { + search( { + // Skip aggregations when requesting for paged results + aggregations: !! pageHandle ? this.props.aggregations : {}, + filter, + pageHandle, + query, + resultFormat: this.props.options.resultFormat, + siteId: this.props.options.siteId, + sort, + } ).then( newResponse => { + if ( this.requestId === requestId ) { + const response = { ...newResponse }; + if ( !! pageHandle ) { + response.results = [ + ...( 'results' in this.state.response ? this.state.response.results : [] ), + ...newResponse.results, + ]; + } + this.setState( { response } ); + } + this.setState( { isLoading: false } ); + } ); } ); } else { - this.setState( { results: [] } ); + this.setState( { response: {}, isLoading: false } ); } }; + loadNextPage = () => { + this.getResults( + this.state.query, + getFilterQuery(), + getSortQuery(), + this.state.response.page_handle + ); + }; + render() { - const { query, results } = this.state; + const { query, response, isLoading } = this.state; return ( { this.props.widgets.map( ( widget, index ) => ( @@ -127,7 +152,7 @@ class SearchApp extends Component { initialValues={ this.props.initialFilters } onChange={ this.onChangeFilter } postTypes={ this.props.options.postTypes } - results={ this.state.results } + results={ this.state.response } widget={ widget } /> @@ -136,9 +161,12 @@ class SearchApp extends Component { diff --git a/modules/search/instant-search/lib/api.js b/modules/search/instant-search/lib/api.js index c04c1f9b84519..d62e0f37d98f3 100644 --- a/modules/search/instant-search/lib/api.js +++ b/modules/search/instant-search/lib/api.js @@ -117,7 +117,7 @@ function buildFilterObject( filterQuery ) { return filter; } -export function search( { aggregations, filter, query, resultFormat, siteId, sort } ) { +export function search( { aggregations, filter, pageHandle, query, resultFormat, siteId, sort } ) { let fields = []; let highlight_fields = []; switch ( resultFormat ) { @@ -145,6 +145,7 @@ export function search( { aggregations, filter, query, resultFormat, siteId, sor filter: buildFilterObject( filter ), query: encodeURIComponent( query ), sort, + page_handle: pageHandle, } ) );