Skip to content

Commit

Permalink
Map: Add GET search
Browse files Browse the repository at this point in the history
This is useful when not all of the results fit on a single page.
  • Loading branch information
iandunn committed Dec 2, 2023
1 parent b0fff4b commit 68832f0
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 20 deletions.
7 changes: 7 additions & 0 deletions mu-plugins/blocks/google-map/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,10 @@ Filters can be setup for anything, but some common examples are watch parties fo

1. View the page where the block is used. That will create the cron job that updates the data automatically in the future.
1. Run `wp cron event run prime_event_filters` to test the filtering. Look at each title, and add any false positives to `$false_positives` in `filter_potential_events()`. If any events that should be included were ignored, add a keyword from the title to `$keywords`. Run the command after those changes and make sure it's correct now.


## Live Search vs GET Search

If the map/list is shown in a context where all of the events fit onto the same page, then it's generally best to use the default "live search" feature. The map markers and list items will be filtered down in real time as the user types.

If there's too many to fit on one page, then it's generally better to submit the search form to the server, so that all of the possible events can be searched, not just the ones on the current page. You can do that by setting the `searchFormAction` attribute to the URL of the page where search results should be displayed. That should be a page that has this block in the post content.
61 changes: 47 additions & 14 deletions mu-plugins/blocks/google-map/inc/event-filters.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,32 @@ function schedule_filter_cron( string $filter_slug, string $start_date, string $
* Get events matching the provider filter during the given timeframe.
*/
function get_events( string $filter_slug, int $start_timestamp, int $end_timestamp, bool $force_refresh = false ) : array {
$events = array();
$cache_key = get_cache_key( compact( 'filter_slug', 'start_timestamp', 'end_timestamp' ) );
$cached_events = get_transient( $cache_key );
$cacheable = true;
$events = array();
$facets = array_merge( array( 'search' => '' ), $_GET );

if ( $cached_events && ! $force_refresh ) {
$events = $cached_events;
array_walk( $facets, 'sanitize_text_field' );

} else {
if ( ! empty( $facets['search'] ) || count( $facets ) > 1 ) {
// Search terms vary so much that caching them probably wouldn't result in a significant degree of
// cache hits, but it would generate a lot of extra transients. With memcached, that could push
// more useful values out of the cache.
$cacheable = false;
}

if ( $cacheable && ! $force_refresh ) {
$cache_key = get_cache_key( compact( 'filter_slug', 'start_timestamp', 'end_timestamp' ) );
$cached_events = get_transient( $cache_key );

if ( $cached_events ) {
$events = $cached_events;
}
}

if ( ! $events ) {
switch ( $filter_slug ) {
case 'all-upcoming':
$events = get_all_upcoming_events();
$events = get_all_upcoming_events( $facets );
break;

case 'wp20':
Expand All @@ -56,7 +71,9 @@ function get_events( string $filter_slug, int $start_timestamp, int $end_timesta
}

// Store for a day to make sure it never expires before the priming cron job runs.
set_transient( $cache_key, $events, DAY_IN_SECONDS );
if ( $cacheable ) {
set_transient( $cache_key, $events, DAY_IN_SECONDS );
}
}

return $events;
Expand All @@ -78,24 +95,40 @@ function get_cache_key( array $parts ): string {
/**
* Get a list of all upcoming events across all sites.
*/
function get_all_upcoming_events(): array {
function get_all_upcoming_events( array $facets = array() ): array {
global $wpdb;

$query = '
$where_clauses = '';
$where_clause_values = array();

if ( $facets['search'] ) {
$where_clauses .= ' AND ( title LIKE "%%%s%%" OR description LIKE "%%%s%%" OR meetup LIKE "%%%s%%" OR location LIKE "%%%s%%" )';
$where_clause_values[] = $facets['search'];
$where_clause_values[] = $facets['search'];
$where_clause_values[] = $facets['search'];
$where_clause_values[] = $facets['search'];
}

$query = "
SELECT
id, `type`, title, url, meetup, location, latitude, longitude, date_utc,
date_utc_offset AS tz_offset
FROM `wporg_events`
WHERE
status = "scheduled" AND
status = 'scheduled' AND
(
( "wordcamp" = type AND date_utc BETWEEN NOW() AND ADDDATE( NOW(), 180 ) ) OR
( "meetup" = type AND date_utc BETWEEN NOW() AND ADDDATE( NOW(), 30 ) )
( 'wordcamp' = type AND date_utc BETWEEN NOW() AND ADDDATE( NOW(), 180 ) ) OR
( 'meetup' = type AND date_utc BETWEEN NOW() AND ADDDATE( NOW(), 30 ) )
)
$where_clauses
ORDER BY date_utc ASC
LIMIT 400'
LIMIT 400"
;

if ( $where_clause_values ) {
$query = $wpdb->prepare( $query, $where_clause_values );
}

if ( 'latin1' === DB_CHARSET ) {
$events = $wpdb->get_results( $query );
} else {
Expand Down
8 changes: 8 additions & 0 deletions mu-plugins/blocks/google-map/postcss/style.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

.wp-block-wporg-google-map {
& .wporg-marker-search__container {
display: flex;

/* Enqueuing the `wp-components` stylesheet add too much to the page load just for these styles, so they're duplicated here. */
& button.is-tertiary {
background: none;
border: none;
}

& .wporg-marker-search__icon {
width: 24px;
height: 24px;
Expand Down
4 changes: 4 additions & 0 deletions mu-plugins/blocks/google-map/src/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
"type": "boolean",
"default": true
},
"searchFormAction": {
"type": "string",
"default": ""
},
"listDisplayLimit": {
"type": "number",
"default": -1
Expand Down
21 changes: 18 additions & 3 deletions mu-plugins/blocks/google-map/src/components/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { getValidMarkers } from '../utilities/google-maps-api';
* @param {Object} props.markerIcon
* @param {string} props.searchIcon
* @param {Array} props.searchFields
* @param {string} props.searchFormAction
* @param {number} props.listDisplayLimit
*/
export default function Main( {
Expand All @@ -44,6 +45,7 @@ export default function Main( {
markerIcon,
searchIcon,
searchFields,
searchFormAction,
} ) {
const [ searchQuery, setSearchQuery ] = useState( '' );
const searchQueryInitialized = useRef( false );
Expand All @@ -61,6 +63,11 @@ export default function Main( {
const onQueryChange = useCallback( ( event ) => {
setSearchQuery( event.target.value );

// The form will submit a GET request, so don't do a live search.
if ( searchFormAction ) {
return;
}

/*
* Sometimes the map may be taking up most of the viewport, so the user won't see the list changing as
* they type their query. This helps direct them to the results.
Expand Down Expand Up @@ -106,10 +113,18 @@ export default function Main( {
} );
}, [ redrawMap ] );

const currentURL = new URL( document.location.href );
const noEventsFoundQuery = searchFormAction ? currentURL.searchParams.get( 'search' ) : searchQuery;

return (
<>
{ showSearch && (
<Search searchQuery={ searchQuery } onQueryChange={ onQueryChange } iconURL={ searchIcon } />
<Search
formAction={ searchFormAction }
searchQuery={ searchQuery }
onQueryChange={ onQueryChange }
iconURL={ searchIcon }
/>
) }

{ showMap && (
Expand All @@ -120,11 +135,11 @@ export default function Main( {
<List markers={ visibleMarkers } displayLimit={ listDisplayLimit } />
) }

{ visibleMarkers.length === 0 && searchQuery.length > 0 && (
{ visibleMarkers.length === 0 && noEventsFoundQuery.length > 0 && (
<p className="wporg-marker-list__container">
{
// Translators: %s is the search query.
sprintf( __( 'No events were found matching %s.', 'wporg' ), searchQuery )
sprintf( __( 'No events were found matching %s.', 'wporg' ), noEventsFoundQuery )
}
</p>
) }
Expand Down
36 changes: 33 additions & 3 deletions mu-plugins/blocks/google-map/src/components/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,48 @@
* WordPress dependencies
*/
import { __, _x } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { useCallback } from '@wordpress/element';

/**
* Render a list of the map markers.
*
* @param {Object} props
* @param {string} props.formAction
* @param {string} props.searchQuery
* @param {Function} props.onQueryChange
* @param {string} props.iconURL
*/
export default function Search( { searchQuery, onQueryChange, iconURL } ) {
export default function Search( { formAction, searchQuery, onQueryChange, iconURL } ) {
// Live searches shouldn't submit to the server.
const onFormSubmit = useCallback(
( event ) => {
if ( ! formAction ) {
event.preventDefault();
}
},
[ formAction ]
);

const searchIcon = (
<img className="wporg-marker-search__icon" src={ iconURL } alt={ __( 'Search', 'wporg' ) } />
);

const formActionURL = formAction ? new URL( formAction ) : undefined;

return (
<form className="wporg-marker-search__container">
<form
className="wporg-marker-search__container"
action={ formAction ? formActionURL.href : undefined }
onSubmit={ onFormSubmit }
>
<label htmlFor="wporg-marker-search__input">
<span>{ __( 'Search events:', 'wporg' ) }</span>

<input
className="wporg-marker-search__input"
type="text"
name="search"
value={ searchQuery }
// translators: Change this to a recognizable city in your locale.
placeholder={ _x( 'Springfield', 'Event query placeholder', 'wporg' ) }
Expand All @@ -28,7 +52,13 @@ export default function Search( { searchQuery, onQueryChange, iconURL } ) {
/>
</label>

<img className="wporg-marker-search__icon" src={ iconURL } alt={ __( 'Search', 'wporg' ) } />
{ formAction && (
<Button type="submit" variant="tertiary">
{ searchIcon }
</Button>
) }

{ ! formAction && searchIcon }
</form>
);
}

0 comments on commit 68832f0

Please sign in to comment.