LazyTurtle
LazyTurtle

Reputation: 131

JavaFX TableView filter using TextField

I want to code a method where I pass a textfield (filterField), data from a tableview (data) and tableview (table) in order to filter all the table data, checking each time a key is released in the textfield, if the string typed is contained in any cell of the table.

This method is to be generic and used for different type of tables with different column headers.

To do so I´m using the following code:

public void addTextFilter(ObservableList<List<Object>> allData, 
                           JFXTextField filterField, TableView<List<Object>> table) {

    FilteredList<List<Object>> filteredData  = new FilteredList<>(allData, p -> true);
    filterField.setOnKeyReleased(e -> 
    {
         filteredData.setPredicate(p  -> 
         {
             if (filterField.getText() == null || filterField.getText().isEmpty()){
                 return true;
             }else {
                 return p.contains(filterField.getText()); 
             }
         });


    });

    SortedList<List<Object>> sortedData = new SortedList<>(filteredData);
    sortedData.comparatorProperty().bind(table.comparatorProperty());
    table.setItems(sortedData);
}

This code only returns the values when the string in the textfield matches exactly the value of any cell in the table.

I would need it to return any cell of the table that contains the string typed in it while typing, even if the cell contains more characters. Something similar to this: JavaFX 8 TableView Sorting and Filtering

I would also need it to return values not depending on lowercase or uppercase values.

Upvotes: 1

Views: 5134

Answers (3)

Brock Winfrey
Brock Winfrey

Reputation: 21

I was pretty proud of this, and I think that this is the first time I have answered a question on StackOverflow.

This code will allow you to search for partial matches. So if you type the letter d it will eliminate any row that doesn't have the letter d.

This code will ALSO allow you to search for multiple words in each row of the table (separated by a space). So if the word "dog" and "woof" are both on the line, then you would type:

dog woof

And it will show only those rows that have BOTH of those words. You can use as many search words as you want.

public static void addTextFilterB(ObservableList<List<Object>> allData,
                                  TextField filterField, TableView<List<Object>> table) {

    FilteredList<List<Object>> filteredData  = new FilteredList<>(allData, p -> true);
    filterField.setOnKeyReleased(e ->
    {
        filteredData.setPredicate(p  ->
        {
            if (filterField.getText() == null || filterField.getText().isEmpty()){
                return true;
            }else {
                String pToString = p.toString().toLowerCase().replace(", "," ");
                String textIwantB = filterField.getText();
                String[] parts = textIwantB.toLowerCase().split(" ");

                if(p.contains(textIwantB)){
                    System.out.println("p.: " + p);

                }

                int counter = 0;
                for (int i = 0; i < parts.length; i ++) {
                    if (parts[i] != null)
                        if(!(pToString.contains(parts[i]))){
                            System.out.println("this one is eliminated: " + pToString);
                            return false;
                        }
                        counter++;
                }

                System.out.println("counter: " + counter);




                return pToString.contains(parts[0]);
            }
        });


    });

    SortedList<List<Object>> sortedData = new SortedList<>(filteredData);
    sortedData.comparatorProperty().bind(table.comparatorProperty());
    table.setItems(sortedData);
}

Upvotes: 0

fabian
fabian

Reputation: 82461

Rather than using a KeyEvent, you should listen to changes in the text property.

TableColumns can be used to retrieve the values for the cells which can be used in the predicate.

public static <T> void addTextFilter(ObservableList<T> allData,
        JFXTextField filterField, TableView<T> table) {

    final List<TableColumn<T, ?>> columns = table.getColumns();

    FilteredList<T> filteredData = new FilteredList<>(allData);
    filteredData.predicateProperty().bind(Bindings.createObjectBinding(() -> {
        String text = filterField.getText();

        if (text == null || text.isEmpty()) {
            return null;
        }
        final String filterText = text.toLowerCase();

        return o -> {
            for (TableColumn<T, ?> col : columns) {
                ObservableValue<?> observable = col.getCellObservableValue(o);
                if (observable != null) {
                    Object value = observable.getValue();
                    if (value != null && value.toString().toLowerCase().equals(filterText)) {
                        return true;
                    }
                }
            }
            return false;
        };
    }, filterField.textProperty()));

    SortedList<T> sortedData = new SortedList<>(filteredData);
    sortedData.comparatorProperty().bind(table.comparatorProperty());
    table.setItems(sortedData);
}

If your columns contain the values of the list, you could simplify the code a bit though:

public static void addTextFilter(ObservableList<List<Object>> allData,
        JFXTextField filterField, TableView<List<Object>> table) {

    final List<TableColumn<List<Object>, ?>> columns = table.getColumns();

    FilteredList<List<Object>> filteredData = new FilteredList<>(allData);
    filteredData.predicateProperty().bind(Bindings.createObjectBinding(() -> {
        String text = filterField.getText();

        if (text == null || text.isEmpty()) {
            return null;
        }
        final String filterText = text.toLowerCase();

        return o -> {
            for (Object value : columns) {
                if (value != null && value.toString().toLowerCase().equals(filterText)) {
                    return true;
                }
            }
            return false;
        };
    }, filterField.textProperty()));

    SortedList<List<Object>> sortedData = new SortedList<>(filteredData);
    sortedData.comparatorProperty().bind(table.comparatorProperty());
    table.setItems(sortedData);
}

Note that none of the above code snippets are notified of changes done to items.

Upvotes: 3

pdem
pdem

Reputation: 4067

The tutorial is good and uses the observable value of the TextField: the textProperty instead of the Texfield itself. Just adapt it to your case.

It will give you less coupling, and the right usage of the observable, you won't rely on graphical event (what if the user click and paste from the mouse instead of keyboard?)-> more robust

public void addTextFilter(ObservableList<List<Object>> allData, 
                       StringProperty textProperty, TableView<List<Object>> table)     {


  FilteredList<List<Object>> filteredData  = new FilteredList<>(allData, p -> true);
  // Use textProperty
  textProperty.addListener((observable, oldValue, newValue) -> {
        filteredData.setPredicate(person -> {
            // If filter text is empty, display all persons.
            if (newValue == null || newValue.isEmpty()) {
                return true;
            }

            // Compare first name and last name of every person with filter text.
            String lowerCaseFilter = newValue.toLowerCase();

            if (person.getFirstName().toLowerCase().contains(lowerCaseFilter)) {
                return true; // Filter matches first name.
            } else if     (person.getLastName().toLowerCase().contains(lowerCaseFilter)) {
                return true; // Filter matches last name.
            }
            return false; // Does not match.
        });
    });

  SortedList<List<Object>> sortedData = new SortedList<>(filteredData);
  sortedData.comparatorProperty().bind(table.comparatorProperty());
  table.setItems(sortedData);
}

Upvotes: 0

Related Questions