Skip to content
47 changes: 47 additions & 0 deletions modules/search/instant-search/components/scroll-button.jsx
Original file line number Diff line number Diff line change
@@ -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.enableLoadOnScroll && document.addEventListener( 'scroll', this.checkScroll );
}
componentDidUnmount() {
document.removeEventListener( 'scroll', this.checkScroll );
}

checkScroll = debounce( () => {
if (
this.props.enableLoadOnScroll &&
window.innerHeight + window.scrollY === document.body.offsetHeight
) {
this.props.onLoadNextPage();
}
}, 100 );

render() {
return (
<button
className="jetpack-instant-search__scroll-button"
disabled={ this.props.isLoading }
onClick={ this.props.onLoadNextPage }
>
{ this.props.isLoading ? (
<span>{ __( 'Loading…', 'jetpack' ) }</span>
) : (
<span>{ __( 'Load more', 'jetpack' ) }</span>
) }
</button>
);
}
}

export default ScrollButton;
93 changes: 60 additions & 33 deletions modules/search/instant-search/components/search-app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,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: {},
loading: false,
};
this.getResults = debounce( this.getResults, 200 );
this.getResults( this.state.query, getFilterQuery(), this.state.sort );
Expand All @@ -61,6 +64,10 @@ class SearchApp extends Component {
} );
}

hasNextPage() {
return !! this.state.response.page_handle;
}

onChangeQuery = event => {
const query = event.target.value;
this.setState( { query } );
Expand All @@ -78,39 +85,57 @@ 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;

this.setState( {
loading: true,
} );
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,
loading: false,
} );
}
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.aggregations = {
...( 'aggregations' in this.state.response && ! Array.isArray( this.state.response )
? this.state.response.aggregations
: {} ),
...( ! Array.isArray( newResponse.aggregations ) ? newResponse.aggregations : {} ),
};
response.results = [
...( 'results' in this.state.response ? this.state.response.results : [] ),
...newResponse.results,
];
}
this.setState( { response } );
}
this.setState( { isLoading: false } );
} );
} );
} else {
this.setState( {
results: [],
loading: false,
} );
this.setState( { response: {}, isLoading: false } );
}
};

loadNextPage = () => {
this.hasNextPage() &&
this.getResults(
this.state.query,
getFilterQuery(),
getSortQuery(),
this.state.response.page_handle
);
};

render() {
const { query, results } = this.state;
const searchForms = Array.from(
document.querySelectorAll( this.props.themeOptions.search_form_selector )
);
Expand All @@ -123,7 +148,7 @@ class SearchApp extends Component {
<SearchBox
onChangeQuery={ this.onChangeQuery }
appRef={ this.input }
query={ query }
query={ this.state.query }
/>
</div>
<div className="jetpack-search-sort-wrapper">
Expand All @@ -135,9 +160,9 @@ class SearchApp extends Component {
<SearchFiltersWidget
initialValues={ this.props.initialFilters }
onChange={ this.onChangeFilter }
loading={ this.state.loading }
loading={ this.state.isLoading }
postTypes={ this.props.options.postTypes }
results={ this.state.results }
results={ this.state.response }
widget={ widget }
/>
</div>
Expand All @@ -150,17 +175,19 @@ class SearchApp extends Component {
<SearchBox
onChangeQuery={ this.onChangeQuery }
appRef={ this.input }
query={ query }
query={ this.state.query }
/>
</Portal>
) ) }

<Portal into={ this.props.themeOptions.results_selector }>
<SearchResults
query={ query }
loading={ this.state.loading }
{ ...results }
result_format={ this.props.options.resultFormat }
hasNextPage={ this.hasNextPage() }
isLoading={ this.state.isLoading }
onLoadNextPage={ this.loadNextPage }
query={ this.state.query }
response={ this.state.response }
resultFormat={ this.props.options.resultFormat }
/>
</Portal>
</Preact.Fragment>
Expand Down
22 changes: 16 additions & 6 deletions modules/search/instant-search/components/search-results.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand All @@ -23,7 +24,9 @@ class SearchResults extends Component {
}

render() {
const { results = [], query, total = 0, corrected_query = false, loading = false } = this.props;
const { query } = this.props;
const { results = [], total = 0, corrected_query = false } = this.props.response;

if ( query === '' ) {
return <div className="jetpack-instant-search__search-results" />;
}
Expand All @@ -37,13 +40,13 @@ class SearchResults extends Component {
);
}
const num = new Intl.NumberFormat().format( total );
const cls =
loading === true
? 'jetpack-instant-search__search-results jetpack-instant-search__is-loading'
: 'jetpack-instant-search__search-results';

return (
<div className={ cls }>
<div
className={ `jetpack-instant-search__search-results ${
this.state.isLoading === true ? ' jetpack-instant-search__is-loading' : ''
}` }
>
<p className="jetpack-instant-search__search-results-real-query">
{ corrected_query !== false
? sprintf(
Expand All @@ -59,6 +62,13 @@ class SearchResults extends Component {
</p>
) }
{ results.map( result => this.render_result( result ) ) }
{ this.props.hasNextPage && (
<ScrollButton
enableLoadOnScroll
isLoading={ this.props.isLoading }
onLoadNextPage={ this.props.onLoadNextPage }
/>
) }
</div>
);
}
Expand Down
3 changes: 2 additions & 1 deletion modules/search/instant-search/lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand Down Expand Up @@ -145,6 +145,7 @@ export function search( { aggregations, filter, query, resultFormat, siteId, sor
filter: buildFilterObject( filter ),
query: encodeURIComponent( query ),
sort,
page_handle: pageHandle,
} )
);

Expand Down