public class SampleSearchParameters {
public static final String PROPERTY_PARTNER_NUMBER = "partnerNumber";
// and other search parameters
private String partnerNumber;
public String getPartnerNumber() {
return partnerNumber;
}
public void setPartnerNumber(String partnerNumber) {
this.partnerNumber = partnerNumber;
}
}
UI Components
Search
Dependency to artifact linkki-search-vaadin-flow is needed.
|
linkki provides search components that can be used by any product for performing search operations.
The search can be integrated in two ways:
-
As a separate view (also referred to as context-free)
-
Within another view as a dialog (also referred to as context-dependent)
Necessary classes
Regardless of the kind of search, there are several classes that need to be created:
Search parameter |
Model object for search parameters SampleSearchParameters |
PMO for search parameter |
A PMO for the search parameter input SampleSearchParametersPmo
Annotating the first element with @BindAutoFocus sets the focus on the first input field, giving the user the ability to start typing immediately instead of having to click on the input field first. |
Collected search results |
Wrapper for a list of search results and messages of possibly occurred errors SampleSearchResult
|
Single search result |
Model object of a single search result SampleModelObject
|
- PMO for a single search result
-
PMO for visualizing the search result as a row in the result table
SampleSearchResultRowPmo
public class SampleSearchResultRowPmo { private SampleModelObject modelObject; private List<MenuItemDefinition> additionalActions; public SampleSearchResultRowPmo(SampleModelObject result, List<MenuItemDefinition> additionalActions) { this.modelObject = result; this.additionalActions = additionalActions; } public SampleModelObject getModelObject() { return modelObject; } @UILink(position = 10, label = "PartnerNumber") public String getPartnerNumber() { return modelObject.getLink(); } public String getPartnerNumberCaption() { return modelObject.getPartnerNummber(); } @UISearchResultAction public List<MenuItemDefinition> getActions() { return additionalActions; } }
The row PMO can be annotated with
@UISearchResultAction
to define actions for a table row. These actions are available at the end of a row in a menu.
SearchController
The SearchController
is responsible for the execution of the actual search.
Based on the search type (context-free or context-dependent), its implementation and usage differs.
Details can be found within the chapters regarding context-free search and context-dependent search.
Creating the search component
The search component is created using the SearchLayoutBuilder
class.
SearchLayoutBuilder<PARAM, RESULT, MODEL_OBJECT, ROW>
return SearchLayoutBuilder
.<SampleSearchParameters, SampleSearchResult, SampleModelObject, SampleSearchResultRowPmo> with()
.searchParametersPmo(SampleSearchParametersPmo::new)
.searchResultRowPmo(m -> new SampleSearchResultRowPmo(m, getAdditionalActions(m)),
SampleSearchResultRowPmo::getModelObject,
SampleSearchResultRowPmo.class)
.searchController(searchController, SampleSearchResult::getResult)
.primaryAction(ResultActions::navigateToPartner)
.maxResult(SampleSearchResult.DEFAULT_MAX_RESULT_SIZE)
.caption("Search for Partners")
.build();
The "primary action" is executed upon double-clicking the search result row. When using a context-free search, it is common to open the entry in a new page. The common action for a context-dependent search is to select the entry.
Search as a separate view
While searching on a separate view, it is desirable to have search parameters that are reflected within the URL. Thereby, the search parameters can be preconfigured by the URL, and can also be shared with other by sending the URL.
The class RoutingSearchController
provides functionalities to achieve this.
SearchView
Since the search view must be reachable per URL, a @Route
annotation is needed.
Besides that, both the BeforeEnterObserver
or AfterNavigationObserver
interface must be implemented to enable URL parameter processing.
@Route(value = ContextFreeSearchView.NAME, layout = PlaygroundAppLayout.class)
public class ContextFreeSearchView extends VerticalLayout implements AfterNavigationObserver {
The Vaadin Navigation Lifecycle documentation proposes the usage of the AfterNavigationEvent to initialise the UI.
However, this event prevents redirecting to other sites.
In cases where the search result should directly be opened, using the BeforeEnterEvent is preferable.
|
The content of the view must be a SearchLayoutPmo
that is created using SearchLayoutBuilder
.
Creating a SearchLayoutPmo
is independent of the actual search and is described in detail in "Creating the search component".
private final BindingContext bindingContext = new BindingContext();
public ContextFreeSearchView() {
searchController = createSearchController();
add(VaadinUiCreator.createComponent(createSearchLayoutPmo(), bindingContext));
setSizeFull();
}
The search controller must be initialised and needs the value of the @Route
annotation as first parameter.
return new RoutingSearchController<>(ContextFreeSearchView.NAME,
searchService::search,
new SampleSearchParametersMapper(),
SampleSearchResult::getMessages);
Finally, within afterNavigation
, the AfterNavigationEvents
Location
must be used to initialize
the RoutingSearchController
.
The search controller then reads the search parameters of the URL and executes the search if necessary.
The search parameters can be visualised by updating the BindingContext containing the SearchlayoutPmo .
|
@Override
public void afterNavigation(AfterNavigationEvent event) {
searchController.initialize(event.getLocation());
bindingContext.modelChanged();
}
Query parameter mapping
An implementation of SearchParameterMapper
must be created to map the search parameters to query parameters and vice versa.
public class SampleSearchParametersMapper implements SearchParameterMapper<SampleSearchParameters> {
The toSearchParameters
method creates a search parameter object based on the query parameters:
public SampleSearchParameters toSearchParameters(Map<String, List<String>> queryParams) {
var searchParams = new SampleSearchParameters();
var parameters = ParamsUtil.flatten(queryParams);
searchParams.setPartnerNumber(parameters.get(SampleSearchParameters.PROPERTY_PARTNER_NUMBER));
...
Consequently, toQueryParameters
creates query parameters from a given search parameter object:
public Map<String, List<String>> toQueryParameters(SampleSearchParameters searchParams) {
var queryParams = new LinkedHashMap<String, String>();
queryParams.put(SampleSearchParameters.PROPERTY_PARTNER_NUMBER, searchParams.getPartnerNumber());
...
In case that every parameter only has one value, ParamsUtil.flatten
can be used to use Map<String, String>
instead of Map<String, List<String>>
.
Besides that, ParamsUtil
offers some handy format
and parse
methods.
queryParams.put(SampleSearchParameters.PROPERTY_DATE_OF_BIRTH,
ParamsUtil.formatIsoDate(searchParams.getDateOfBirth()));
ParamsUtil.parseIsoDate(parameters.get(SampleSearchParameters.PROPERTY_DATE_OF_BIRTH))
.ifPresent(searchParams::setDateOfBirth);
Search within one view
The class SimpleSearchController
can be used in cases where a search on the current view is needed, e.g., within a dialog.
The UI component can be created with a SearchLayoutBuilder.
Example
var searchLayoutPmo = createSearchLayoutPmo(searchController, selection -> {
if (!validateSelection(selection).containsErrorMsg()) {
primaryAction.accept(selection);
}
});
ValidationService validationService = () -> searchLayoutPmo.getSelectedResult()
.map(SampleSearchResultRowPmo::getModelObject)
.map(this::validateSelection)
.orElse(createEmptySelectionMessage());
var searchDialog = new PmoBasedDialogFactory(validationService)
.newOkCancelDialog("Search for a business partner",
() -> searchLayoutPmo.getSelectedResult()
.map(SampleSearchResultRowPmo::getModelObject)
.ifPresent(primaryAction),
searchLayoutPmo);
return new SimpleSearchController<>(SampleSearchParameters::new,
searchService::search,
SampleSearchResult::getMessages);