Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query and Search blocks: support for Instant Search #63147

Open
wants to merge 85 commits into
base: trunk
Choose a base branch
from

Conversation

r-chrzan
Copy link

@r-chrzan r-chrzan commented Jul 4, 2024

What?

Initial implementation for Instant Search using the Search and Query Loop blocks. Added as a new experiment on the Gutenberg Experiments page.

Related to #63053

output_62afd8.mp4

How does it work?

  1. Enable the Instant Search and Query Block Gutenberg experiment.
  2. Insert a Query block anywhere on your site.
  3. Make sure to disable Force Page Reload i.e. use "enhanced pagination" in that Query block.
  4. Insert a Search block anywhere inside of that Query block.

When the Search block is inside of the Query block using "enhanced pagination", it automatically gets "updated" to an Instant Search block.

Any search input in the Instant Search block gets passed as the ?instant-search-<queryId>=<search-term> query search param in the URL.

Multiple Query + Search blocks

It's possible to have multiple Instant Search blocks in a page/post/template. In such a case, ensuring their functionality is isolated is essential. For this to work, the queryId is part of the param syntax:

  • instant-search-<queryId>=<search-term>

Search button

The search block can contain a search button (in some configurations). However, when using the Instant Search functionality, the button is redundant because the list of posts updates as you type. For this reason, in the editor, when the Search block is placed inside the Query Loop block with enhanced pagination ON, the Block Controls related to the Search button are removed. The displayed name of the block (via label) is changed to Instant Search (Search).

On the frontend, the Search button is also always removed for each (Instant) Search block.

output_70b3a0.mp4

Pagination

The instant search functionality respects the pagination of the Query block, which uses the query-<queryId>-page syntax.

  1. Every time the search is updated, the pagination of its respective query is reset back to page 1.
  2. The pagination numbers and next/previous, are updated to reflect the number of results in the search.
  3. Clearing the search also resets the pagination back to page 1.

Limitations

Alternatives

Further work

This is an initial prototype and the following features is intentionally not yet implemented:

  • Support for the Default queries, including multiple Default queries on a single page/post/template. Those have been prototyped already in Search and Query blocks: Add support for Default queries via pre_get_posts filter #67289.
  • Explore an alternative mechanism to convert the Search block into a Instant Search. Currently, this is done by nesting the Search inside of Query Loop with enhanced pagination ON, but a different, more explicit mechanism might be appropriate.

@github-actions github-actions bot added the First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository label Jul 4, 2024
Copy link

github-actions bot commented Jul 4, 2024

👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @r-chrzan! In case you missed it, we'd love to have you join us in our Slack community.

If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information.

@r-chrzan r-chrzan changed the title adding the necessary directives Instant search implementation Jul 4, 2024
@r-chrzan r-chrzan marked this pull request as ready for review July 5, 2024 20:54
@r-chrzan r-chrzan requested a review from ajitbohra as a code owner July 5, 2024 20:54
Copy link

github-actions bot commented Jul 5, 2024

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @[email protected], @ktmn.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: [email protected], ktmn.

Co-authored-by: luisherranz <[email protected]>
Co-authored-by: michalczaplinski <[email protected]>
Co-authored-by: sirreal <[email protected]>
Co-authored-by: cbravobernal <[email protected]>
Co-authored-by: r-chrzan <[email protected]>
Co-authored-by: gziolo <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@luisherranz luisherranz added [Block] Search Affects the Search Block - used to display a search field [Block] Query Loop Affects the Query Loop Block [Feature] Interactivity API API to add frontend interactivity to blocks. labels Jul 8, 2024
@luisherranz luisherranz linked an issue Jul 8, 2024 that may be closed by this pull request
@luisherranz luisherranz added the [Type] Feature New feature to highlight in changelogs. label Jul 8, 2024
@luisherranz luisherranz changed the title Instant search implementation Query and Search blocks: support for Instant Search Jul 8, 2024
@WordPress WordPress deleted a comment from github-actions bot Jul 8, 2024
@luisherranz
Copy link
Member

Great work!! 👏👏

Let's see what's next 🙂

  1. Modifying the query in inherited queries.

    I don't know if you've noticed, but the query_loop_block_query_vars filter only works when the "Inherit query from template" option is disabled.

    Screenshot 2024-07-08 at 16 33 20

    We should check if the query is inherited, and if so, apply the modification to the general query so it works in both cases.

  2. The search parameter in inherited queries.

    It's interesting that we can't use the search parameter ?s= because, in that case, the page that loads is the Search template.

    I'm going to think about it a bit, but it is true that it might not be possible to simply use ?s=. If we can't use it, then we need to think carefully about what the most suitable parameter is. Maybe ?is= for instant search?

  3. The search parameter in non-inherited queries.

    When the "Inherit query from template" option is disabled, the query pagination uses a custom search parameter instead of the regular ?paged=2//page/2 of the inherited queries: ?query-X-page=P where X is the query id ($block->context['queryId']).

    In that case, we should use a similar parameter: ?query-X-search=test.

  4. Pagination when searching.

    You commented in Slack:

    sometimes, when we are on e.g. page 3 and typing something, i see prev link button without results

    That's what happens when the new results don't have as many pages as the page you were on just before you started the search.

    I think we should try resetting the page number every time a new search is performed. In other words, if the user starts a search on page 3, we should remove the search param from the URL before navigating.

    • User is in /page/3 or ?query-X-page=3.
    • User types new characters.
    • We add the search parameter, but remove the pagination: /?is=test.

    This is probably going to present problems, as it might not be that easy to remove the pagination from the URL in the client for certain permalinks, but I think it's worth a try and we'll see what happens. If that's the case, one option to investigate would be to generate the URL of the first page on the server and send it to the client via the state.

  5. Search button closing itself during navigation.

    You commented in Slack:

    it doesn’t work very well when search is just a button. sometimes toggle closes while typing

    That's because the context has a property called isSearchInputVisible which is set to false on the server. So when a navigation occurs, it gets reset to false.

    The way to solve this is to transform that property into an initial property and have a client-only property so that the server property is only checked on the first page load and then only the client-only property is considered.

    I made a commit to implement this pattern by adding this type of getter:

    get isSearchInputVisible() {
      const ctx = getContext();
      if ( typeof ctx.isSearchInputVisible === 'undefined' ) {
        return ctx.isSearchInputInitiallyVisible;
      }
      return ctx.isSearchInputVisible;
    },

@gziolo
Copy link
Member

gziolo commented Nov 15, 2024

The open question is what would be the ultimate fix in WordPress core. The challenge is that the context is exposed based on the query attribute on the Query block, which is serialized in the database, so we would have to find a way to change the context based on the params passed in the URL matching the current query id.

I said that the search term is configurable, and the default value can be set in the editor through UI:

Screenshot 2024-11-15 at 09 23 00

In effect, the default value is always stored in the database and it doesn't have to be an empty string. Now, the functionality that is being explored has to offer a way to override that search value set in the block context by the Query block when rendering the block. Currently, the context for child block is equal to the value serialized in the database. Overriding on the frontend the context based on the URL param detected seems like the most intuitive approach as otherwise, every block has to check the URL to figure out if it should use that value or the one provided by theme/editor's user.

@michalczaplinski
Copy link
Contributor

I said that the search term is configurable, and the default value can be set in the editor through UI:

Right, that's correct. I think we're talking about the same thing 🙂. I just did not realize that one can update the search term in the UI in addition to simply editing the markup (I did the latter in this example).

The effect is the same either way: the query.search attribute gets updated.

It should now work as of baa6188. Whenever the user updates the search term (via UI or just directly editing the markup), the search term will appear in the Search input on the frontend upon loading the page.

What I was considering here:

The remaining doubt I have is how to handle the URL. Should we (on the fronted) modify the URL to include a query param like instant-search-11=?

is something different. I wondered: What to do when loading a page for the first time, including an Instant Search block with a custom search term. In the video in the previous comment I show that the word Surfing appears in the Search input, but the URL does NOT reflect that. I had thought that on initial load, we could (on the front-end in JS) update the URL to include the search term.

However, I gave it some more thought, and it's probably not a good idea and would generate more problems than solve, I think. We should not just update the links in JS after the page loads based on the page's contents.

@gziolo gziolo dismissed their stale review November 19, 2024 07:21

I will let other folks to help bring it to the finish line.

@gziolo
Copy link
Member

gziolo commented Nov 19, 2024

I discussed with @ntsekouras some options for the overall strategy of making the Query block filterable on the front end through URL params on WordPress Slack (https://make.wordpress.org/chat/):
https://wordpress.slack.com/archives/C02QB2JS7/p1731660635692969

@luisherranz
Copy link
Member

I was trying to test the UI, but I'm getting a Maximum update depth exceeded error in the <SearchEdit> component in the Editor.

Copy link
Member

@luisherranz luisherranz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested the UI yet (due to this problem), but here are a few suggestions/questions 🙂

packages/block-library/src/image/block.json Outdated Show resolved Hide resolved
packages/block-library/src/post-content/block.json Outdated Show resolved Hide resolved
Comment on lines +68 to +76
if ( $enhanced_pagination && $instant_search_enabled && ! empty( $search_query_direct ) ) {
$args = array_merge(
build_query_vars_from_query_block( $block, $page ),
array( 's' => $search_query_direct )
);
$custom_query = new WP_Query( $args );
} else {
$custom_query = new WP_Query( build_query_vars_from_query_block( $block, $page ) );
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the Pagination Next block needs this change, I understand that the Pagination Previous block will also need it, right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I had had thought initially as well but actually it won't 🙂

The Query Pagination, Next block has to create a new query to determine if it's on the last page of the results (if it is, then the block shouldn't show anything). We have to pass the search parameter to this new query.

In Query Pagination Previous, no such new query is created, so it just works out of the box!

Copy link
Contributor

@michalczaplinski michalczaplinski Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed an edge case:

If you navigate to a page with ?instant-search param AND a pagination parameter like http://gutenberg.local/?instant-search-18=qwponbfv&query-18-page=2, then the Pagination Previous block shows up although it should not.

I'll hold on with fixing this for now though until we make a decision on whether we should go ahead with this PR or the approach in #67181 or #67013.

$query_args['s'] = $search_query_direct;
}

$query = new WP_Query( $query_args );
}

if ( $query->post_count > 0 ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can include the contents of this block but hide them when there are no posts. That way, the CSS and JS of the inner blocks will be correctly loaded and the blocks will work when the user navigates to a "no results" page in the client.

We can remove this workaround if we finally improve how client-side navigation works before WP 6.8.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, nice. That's a good idea 👍

Can we add this in a follow-up PR though? I feel that it's not essential for the first version of the experimental block.


// Debounce the search by 300ms to prevent multiple navigations.
supersedePreviousSearch?.();
const { promise, resolve, reject } = Promise.withResolvers();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Support for Promise.withResolvers is still a bit low (87%) when compared with the rest of the things we use so far in the frontend (>93%). As this is not critical in any sense, let's find viable alternative that doesn't lower down our browser support.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'll update this 👍

$query_args['s'] = $search_query_direct;
}

$query = new WP_Query( $query_args );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to create a new Query (and do so multiple times) instead of updating the current one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what you mean.

We're not adding (another) new Query here. The code of the Query No Results block was already creating a new WP_Query() (line 36) - we're just passing the search parameter to it.

Copy link
Contributor

@michalczaplinski michalczaplinski Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll notice that in other blocks modified in this PR (post template, query-pagination-next, or query-pagination-numbers) we're also not creating any additional queries beyond what had existed before. In query-pagination-next and query-pagination-numbers at most one query will run - which one depends on whether Instant Search is enabled.

@luisherranz
Copy link
Member

  1. Pagination for Default queries uses the /page/2/ format when using pretty permalinks. Should it be removed from the URL? Here we have two options:

    • Remove it from the URL. Downside is possibly breaking links as we have to parse the URL with a regex and remove the /page/X/ part.
    • Keep it in the URL and use the ?paged URL param. This works fine in my tests and redirects the request to the correct page. E.g. /page/2/?paged=3 will redirect to /page/3/. The downside is that it requires a redirect so is slightly slower.

Regarding this, why don't we pass the root/canonical link from the server (using iAPI's state/config, for example) and use that link instead? I haven't looked at it, but I suppose it won't be difficult to obtain on the server.

@michalczaplinski
Copy link
Contributor

I was trying to test the UI, but I'm getting a Maximum update depth exceeded error in the component in the Editor.

Oh, apologies about this. It was the very last commit that broke it. I've reverted it now (in 3dcf20f).

@michalczaplinski
Copy link
Contributor

Regarding this, why don't we pass the root/canonical link from the server (using iAPI's state/config, for example) and use that link instead? I haven't looked at it, but I suppose it won't be difficult to obtain on the server.

For this we could use wp_get_canonical_url but I have doubts about whether this is a reliable method. We cannot know for sure that the URL returned wp_get_canonical_url is going to the same as the URL the client sees. The WP server can sit behind a proxy like Cloudflare that can modify the hostname, pathname or params.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Block] Query Loop Affects the Query Loop Block [Block] Search Affects the Search Block - used to display a search field [Feature] Interactivity API API to add frontend interactivity to blocks. First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository [Type] Feature New feature to highlight in changelogs.
Projects
Status: 🏈 Punted to 6.8
Development

Successfully merging this pull request may close these issues.

Query and Search blocks: support for Instant Search
6 participants