Filtering a TreeTable

I have a TreeTable class that I need to be able to filter via the contents of a TextField. (I can worry about styling said TextField later so it has a magnifying glass icon and an “X” button for quickling clearing it.)

I’ve attempted to follow along with the Vaadin Book example, modifying it slightly so it should work better in my use case. For example, I keep them all in one object, since the methods “changeDayTo” and “filterTable” seem like they should be internal to this TreeTable. (Keeping in mind that whole ‘objects should only contain properties and methods that are specific to themselves’ idea.)

So here’s my semi-custom TreeTable class:

[code]
/**
*
*/
package info.chrismcgee.sky.scheduling.ui.components;

import java.sql.SQLException;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.vaadin.data.Container.Filterable;
import com.vaadin.data.util.filter.SimpleStringFilter;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Notification;
import com.vaadin.ui.TreeTable;
import com.vaadin.ui.Notification.Type;

import info.chrismcgee.sky.scheduling.beans.Job;
import info.chrismcgee.sky.scheduling.beans.OrderDetail;
import info.chrismcgee.sky.tables.JobManager;

/**

  • @author Marketing

*/
public class ProductionTreeTable extends TreeTable {

/**
 * Serialization!
 */
private static final long serialVersionUID = -8456241351283529486L;

private final static Logger logger = Logger.getLogger(ProductionTreeTable.class.getName()); // Logging!

private SimpleStringFilter filter = null;
private Filterable f = null;


/**
 * Constructor
 */
public ProductionTreeTable() {
    super();
    
    // Set the columns.
    addContainerProperty("Name / Product", HorizontalLayout.class, "");
    addContainerProperty("Job # / Detail", String.class, "");
    addContainerProperty("Print Type", String.class, "");
    addContainerProperty("Colors", Long.class, 0L);
    addContainerProperty("Quantity", Long.class, 0L);
    addContainerProperty("Total", Long.class, 0L);

}


HorizontalLayout createNode (String name, boolean isChecked) {
    HorizontalLayout layout = new HorizontalLayout();
    
    layout.addComponent(new CheckBox(null, isChecked));
    layout.addComponent(new Label(name));
    
    return layout;
}

public void changeDayTo (final Date date) {
    // Get a list of jobs on a particular date.

    filter = null; // Reset the filter.
    List<Job> jobsList = null;
    try {
        jobsList = JobManager.getJobsByDate(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
    } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    
    if (jobsList != null && jobsList.size() > 0) {
        
        removeAllItems(); // Clear out the TreeTable first.
        
        for (final Job j : jobsList) {
            // Go through each item in the Jobs list and add it
            // (along with all of its OrderDetail items) to the TreeTable.
            final Object jobId = addItem(new Object[] {createNode(j.getCustomerName(), j.getJobCompleted() != null), j.getJobId(),
                    "", null, null, null}, null);
            for (final OrderDetail od : j.getOrderDetailList()) {
                final Object odId = addItem(new Object[] {createNode(od.getProductId(), od.getItemCompleted() != null), od.getProductDetail(),
                        od.getPrintType().getValue(), od.getNumColors(), od.getQuantity(), (od.getNumColors() * od.getQuantity())}, null);
                boolean parentSet = setParent(odId, jobId);
                setChildrenAllowed(odId, false);
                setCollapsed(odId, false);
            }
            setCollapsed(jobId, false);
        }
        filterTable("");
    }
}

public void filterTable (String searchText) {
    // TODO Auto-generated method stub
    f = (Filterable) getContainerDataSource();
    
    // Remove old filter
    if (filter != null) {
        f.removeContainerFilter(filter);
    }
    
    // Set new filter for the "Name / Product" column
    filter = new SimpleStringFilter("Name / Product", searchText, true, false);
    f.addContainerFilter(filter);
    
    Notification.show("Results narrowed.", Type.TRAY_NOTIFICATION);
}

}
[/code]Unfortunately, anything typed in the text field (which gets passed to the filterTable() method) causes the TreeTable to be completely emptied. I’m sure it has something to do with the Filterable property and/or the SimpleStringFilter object I’m trying to use here, but I don’t know what it is. I’m completely new to filtering. All I do know is that, once the TreeTable has been populated with data in the changeDayTo() method, it is using a HierarchicalContainer, which does implement the Container.Filterable interface, from what I understand. Now I just need to know how to take advantage of this.

Please help?

Hi,

Could it be related to some parent-filter behavior ?
https://vaadin.com/api/7.6.0/com/vaadin/data/util/HierarchicalContainer.html#setIncludeParentsWhenFiltering(boolean)

Anyway, not answering the question, but you can also take a look at one of the best add-on : https://vaadin.com/directory#!addon/filteringtable, that will do most of the job for you :wink:

Regards
Sebastien

Hi, Sebastien.

I suppose it’s possible that might have something to do with the problem, but here’s something else interesting that may be relevant: In my ProductionTreeTable class, whenever I attempt to do a getContainerDataSource(), I am not given the option to .setIncludeParentsWhenFiltering on it. Eclipse does not give me that option, meaning that getContainerDataSource() doesn’t actually return a HierarchicalContainer (nor something that extends it), despite the fact that the tooltip I get on getContainerDataSource() specifically says that it returns “Hierarchical.” Now I’m even more confused.

Also, I looked at that add-on, but I’m not so sure that I really want to try it. I have enough trouble just getting Vaadin’s stock stuff to work, even with all of the documentation surrounding them; I’d be afraid that I might be out of my league attempting to use a community-made add-on with very little documentation (which is probably geared for someone who knows much more about Java and Vaadin than I).

That looked a bit strange… I assume HorizontalLayout.class should be String.class?
I see this is the column you filter on, so it might be relevant.

I believe you’ve hit upon it, Guttorm. Actually, I do want that column to be a HorizontalLayout, since–depending on the user’s access level–there may be either just a Label in that column or both a Checkbox and a Label.

The trick seems to be in getting at that Label part. I’m guessing the SimpleStringFilter assumes that the column must be of the String.class, so perhaps I have to use a not-so-simple filter? Either way, how would I direct the filter to dig into that HorizontalLayout to get at the Label inside it?

Okay, so I had a bit of inspiration this afternoon. Since I couldn’t figure out any way of changing the fact that
SimpleStringFilter
only works on a
String
, I decided to work around this limitation. Reading the
API doc for SimpleStringFilter
showed me that “values of other types are converted using toString().” How could I get the
HorizontalLayout
to return a String?

I created a new class that extends
HorizontalLayout
and overrides
.toString()
to return the
Label
's value!

package info.chrismcgee.sky.scheduling.ui.components;

import com.vaadin.ui.CheckBox;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;

public class TreeNode extends HorizontalLayout {
    


    /**
     * Serialization!
     */
    private static final long serialVersionUID = 232800657274822961L;
    
    private CheckBox checkbox;
    private Label label;
    
    
    /**
     * @param checkbox
     * @param label
     */
    public TreeNode(String name, boolean isChecked) {
        super();
        
        checkbox = new CheckBox(null, isChecked);
        label = new Label(name);
        
        addComponent(checkbox);
        addComponent(label);
    }
    
    
    @Override
    public String toString() {
        // TODO Auto-generated method stub
        return label.getValue();
    }

}

Seems so simple, now. Of course, my code for it is probably über-sloppy, but at least it works now!

Now I just need to figure out how to have it filter more properly. i.e., to include the parent/child node when filtering. Also to have it
not
show the checkbox if the logged-in user doesn’t have sufficient privileges.