Issue
I'm wondering what is the best implementation of applying multiple filters for an ObservableList object.
I have the following objects:
ComboBox<String> cbYear
TextField tfSearch
ObservableList<OrderItem> data.
I need to filter the data list based on the value of cbYear and the value of tfSearch.
Here is what i have done to achieve the requiement, but i wonder if there is easier way...
private void initializeFilterTable() {
FilteredList<OrderItem> dataFiltered = new FilteredList<>(data, p -> true);
tfSearch.textProperty().addListener((obs, oldVal, newVal) -> {
String year = cbYear.getSelectionModel().getSelectedItem();
Predicate<OrderItem> filter = filters(newVal, year);
dataFiltered.setPredicate(filter);
});
cbYear.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> {
Predicate<OrderItem> filter = filters(tfSearch.getText(), newVal);
dataFiltered.setPredicate(filter);
});
SortedList<OrderItem> dataSorted = new SortedList<>(dataFiltered);
dataSorted.comparatorProperty().bind(table.comparatorProperty());
table.setItems(dataSorted);
}
private Predicate<OrderItem> filters(String search, String year) {
String lower = search.trim().toLowerCase();
Predicate<OrderItem> searchFilter = order -> {
if (lower == null || lower.isEmpty())
return true;
if (order.getCustomerName().toLowerCase().contains(lower))
return true;
else if (order.getOrderNumber().toLowerCase().contains(lower))
return true;
else if (order.getPDA_TypeStr().toLowerCase().contains(lower))
return true;
else if (order.getCount4UStr().toLowerCase().contains(lower))
return true;
else if (order.getCity().toLowerCase().contains(lower))
return true;
else if (order.getContactName().toLowerCase().contains(lower))
return true;
else if (order.getSoftware().toLowerCase().contains(lower))
return true;
else if (order.getPDA_Type() == 2 && "אנדרואיד".contains(lower))
return true;
else if (order.getStatusStr().toLowerCase().contains(lower))
return true;
return false;
};
Predicate<OrderItem> yearFilter = order -> {
if (year.equals("הכל"))
return true;
else if (year.equals(String.valueOf(order.getYear())))
return true;
return false;
};
return searchFilter.and(yearFilter);
}
Solution
You can filter a filtered list. This keeps the two pieces of logic separate.
FilteredList<OrderItem> filteredByText = new FilteredList<>(data, p -> true);
FilteredList<OrderItem> filteredByYear = new FilteredList<>(filteredByText, p -> true);
tfSearch.textProperty().addListener((obs, oldVal, newVal) -> filteredByText.setPredicate(p -> {
String lower = newVal.trim().toLowerCase();
if (lower == null || lower.isEmpty())
return true;
if (order.getCustomerName().toLowerCase().contains(lower))
return true;
else if (order.getOrderNumber().toLowerCase().contains(lower))
return true;
else if (order.getPDA_TypeStr().toLowerCase().contains(lower))
return true;
else if (order.getCount4UStr().toLowerCase().contains(lower))
return true;
else if (order.getCity().toLowerCase().contains(lower))
return true;
else if (order.getContactName().toLowerCase().contains(lower))
return true;
else if (order.getSoftware().toLowerCase().contains(lower))
return true;
else if (order.getPDA_Type() == 2 && "אנדרואיד".contains(lower))
return true;
else if (order.getStatusStr().toLowerCase().contains(lower))
return true;
return false;
}));
cbYear.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, year) -> filteredByYear.setPredicate(p -> {
if (year.equals("הכל"))
return true;
else if (year.equals(String.valueOf(order.getYear())))
return true;
return false;
}));
SortedList<OrderItem> dataSorted = new SortedList<>(filteredByYear);
dataSorted.comparatorProperty().bind(table.comparatorProperty());
table.setItems(dataSorted);
Answered By - James_D
Answer Checked By - Timothy Miller (JavaFixing Admin)