Lab 6: Full Text Search

We now have a lot of users in our system. We want to enable our users to search through the list of users.

If you did Lab 5 previously you can remove the Thread.sleep(3000) from the UserService.findAll() method

Search Users

In this lab, we will stream through the list of users and filter it using the Java streams API. Create a new method searchUser in the UserService.

UserService.java
public List<EasyUser> searchUser(String searchString) {
  return easyUserList.stream().filter(
      it -> it.uuid.toString().contains(searchString)
            || it.username.contains(searchString)
            || it.password.contains(
          searchString)
  ).toList();
}

ListComponent

Create a new package de.tschuehly.easy.spring.auth.web.list and then create a ListComponent.java ViewComponent.

ListComponent.java
package de.tschuehly.easy.spring.auth.web.list;

import de.tschuehly.spring.viewcomponent.core.component.ViewComponent;
import de.tschuehly.spring.viewcomponent.jte.ViewContext;
import java.util.List;

@ViewComponent
public class ListComponent {

  public ViewContext render(List<ViewContext> viewContextList){ // (1)
    return new ListContext(viewContextList);
  }

  public record ListContext(List<ViewContext> viewContextList) 
    implements ViewContext {}
}

(1): The render method has a List<ViewContext> parameter and pass it to the ListContext

We then create a ListComponent.jte template and loop through the ViewContext List with the @for syntax and then render each ViewContext.

ListComponent.jte
@import de.tschuehly.easy.spring.auth.web.list.ListComponent.ListContext
@param ListContext listContext

@for(var context: listContext.viewContextList())
    ${context}
@endfor

Next, we will autowire the listComponent we just created in the UserTableComponent and create a renderSearch method.

UserTableComponent.java
public ViewContext renderSearch(String searchQuery) {
  List<ViewContext> userRowList = userService.searchUser(searchQuery) // (1)
      .stream().map(userRowComponent::render) // (2)
      .toList();
  return listComponent.render(userRowList); // (3)
}

(1): We will pass the searchQuery parameter to the userService.searchUser method

(2): We stream through the list of users and then render each user with the userRowComponent::render method.

(3): We then pass the userRowList to the listComponent.render method and return the result.

UserController

In the UserController we first need to autowire the UserTableComponent .

Then we create a GET_SEARCH_USER and SEARCH_PARAMconstant and a search user endpoint:

UserController.java
public static final String GET_SEARCH_USER = "/search-user";
public static final String SEARCH_PARAM = "searchQuery";

@HxRequest
@GetMapping(GET_SEARCH_USER)
public ViewContext searchUser(
    @RequestParam(SEARCH_PARAM) String searchQuery // (1)
) {
  return userTableComponent.renderSearch(searchQuery); // (2)
}

(1): The endpoint has a @RequestParam that has SEARCH_PARAM as value

(2): In the method we call the userTableComponent.renderSearch method and return the result.

In the UserTableComponent.jte template we first need to add two imports:

@import static de.tschuehly.easy.spring.auth.user.UserController.*
@import de.tschuehly.easy.spring.auth.htmx.HtmxUtil

Then we will create a new <tr> element as the first child of the <thead>:

<tr>
  <th colspan="4">
    <label>
      Search Users
      <input type="text"
        name="${SEARCH_PARAM}" <%-- (1) --%>
        hx-get="${GET_SEARCH_USER}" <%-- (2) --%>
        hx-trigger="input changed delay:500ms, search" <%-- (3) --%>
        hx-target="${HtmxUtil.idSelector(USER_TABLE_BODY_ID)}"> <%-- (4) --%>
    </label>
  </th>
</tr>

(1): The <input> field has the SEARCH_PARAM constant as name attribute, this will map to the @RequestParam we defined in the UserController.searchUser method.

(2): We then add a hx-get attribute and set it to GET_SEARCH_USER .

(3): We tell htmx to trigger the request when an input event is detected and the value has changed. Htmx then starts a delay timer of 500ms . If the event is seen again in the 500ms it will reset the delay.

(4): We target the body of the table with the USER_TABLE_BODY_ID and swap the innerHTML of it.

Success!

If we restart the application and navigate to http://localhost:8080 we can see that the search is working:

Lab-6 Checkpoint 1

If you are stuck you can resume at this checkpoint with:

git checkout tags/lab-6-checkpoint-1 -b lab-6-c1

Last updated