简体   繁体   English

GWT:向CellTable添加过滤

[英]GWT: add filtering to CellTable

I've been tasked with implementing sorting & filtering of data displayed in a GWT CellTable . 我的任务是对GWT CellTable中显示的数据进行排序和过滤。 Thankfully GWT already supports sorting, but it looks like I'll have to hack together my own filtering support. 值得庆幸的是,GWT已经支持排序,但是看起来我将不得不结合自己的过滤支持。

To be more precise, what I'm trying to support is similar to the filtering offered by Excel, whereby you can click on a drop-down menu in the column headers and (for example) click checkboxes that will allow you to filter the rows based on the values for the filtered column(s). 更准确地说,我要支持的内容类似于Excel提供的过滤,在该过滤中,您可以单击列标题中的下拉菜单,然后单击(例如)复选框以过滤行。基于已过滤列的值。 A picture is worth a thousand words: 一张图片胜过千言万语:

Excel列过滤

My question : any suggestions on how to go about implementing this in GWT 2.2? 我的问题 :关于如何在GWT 2.2中实现此建议? Is it even possible? 可能吗?

One option I'm thinking about is to pass in a custom Header object to CellTable.addColumn(). 我正在考虑的一种选择是将自定义Header对象传递给CellTable.addColumn()。 If it's possible, I'll add a ClickHandler to the Header, and then open a Popup that displays a widget for filtering. 如果可能,我将ClickHandler添加到页眉中,然后打开一个弹出窗口,其中显示用于过滤的小部件。 Not sure how to implement this without negatively affecting the sorting behavior. 不知道如何在不负面影响排序行为的情况下实现这一点。

Any suggestions gladly welcome. 任何建议都欢迎。

Edit : 编辑

Thanks to John below, I've got the following FilterableHeader class that allows me to at least put an icon into the header. 感谢下面的John,我得到了以下FilterableHeader类,该类使我至少可以在标头中放置一个图标。 Unsure how to get a ClickHandler on that icon just yet, as the image is inserted via HTML rather than using GWT widgets. 由于图像是通过HTML而不是使用GWT小部件插入的,因此尚不确定如何在该图标上获取ClickHandler。

public class FilterableHeader extends Header<String>
{
    /**
     * Image resources.
     */
    public static interface Resources extends ClientBundle
    {
        ImageResource downArrow();
        ImageResource upArrow();
    }

    private static final Resources RESOURCES = GWT.create(Resources.class);
    private static final int IMAGE_WIDTH = 16;
    private static final String DOWN_ARROW = makeImage(RESOURCES.downArrow());
    private static final String UP_ARROW = makeImage(RESOURCES.upArrow());

    private static String makeImage(ImageResource resource)
    {
        AbstractImagePrototype proto = AbstractImagePrototype.create(resource);
        return proto.getHTML().replace("style='", "style='position:absolute;right:0px;top:0px;");
    }

    private String text;

    public FilterableHeader(String text)
    {
        super(new ClickableTextCell());
        this.text = text;
    }

    @Override
    public String getValue()
    {
        return text;
    }

    @Override
    public void render(Cell.Context context, SafeHtmlBuilder safe)
    {
        int imageWidth = IMAGE_WIDTH;

        StringBuilder sb = new StringBuilder();
        sb.append("<div style='position:relative;cursor:hand;cursor:pointer;");
        sb.append("padding-right:");
        sb.append(imageWidth);
        sb.append("px;'>");
        sb.append(UP_ARROW);
        sb.append("<div>");
        sb.append(text);
        sb.append("</div></div>");

        safe.append(SafeHtmlUtils.fromSafeConstant(sb.toString()));
    }
}

I develop business applications where a typical database query might return hundreds or thousands of rows. 我开发了业务应用程序,其中典型的数据库查询可能返回数百或数千行。 Users find the excel-like filters and column sorts to be very helpful. 用户发现类似excel的过滤器和列排序非常有帮助。

Hence I have implemented a class that extends ListDataProvider for use with a CellTable that supports client-side excel-like column filtering and sorting. 因此,我实现了一个扩展ListDataProvider的类,以便与支持客户端类似于excel的列过滤和排序的CellTable一起使用。 In all other respects it behaves much like a ListDataProvider. 在所有其他方面,它的行为都非常类似于ListDataProvider。

It depends on implementing a the following ColumnAccessor interface to provide a symbolic name for for each column in the CellTable, to provide access to column-level data for sorting and filtering, a Comparator for the column for sorting, and display label for the header. 它依赖于实现以下ColumnAccessor接口,以便为CellTable中的每个列提供符号名称,提供对列级数据的访问以进行排序和过滤,为列进行比较的Comparator以及为标题显示标签。 Following is the ColumnAccessor class. 以下是ColumnAccessor类。 It assumes that you have some sort of Data Transfer Object <T> that models the rows. 假定您具有某种对行进行建模的数据传输对象<T>。

/**
 * Interface to provide access to a specific
 * column within a data row.
 * @param <T> Object that contains the column
 *  values in a cell table row. Typically a Data Transfer Object.
 */
public interface ColumnAccessor<T> {
    /**
     * Filter display value for blank/null column values
     */
    public final String FILTER_SELECTOR_BLANK = "{Blank}";
    /**

     * Returns A row-unique symbolic name for the column. This name is
     * used as a Map key to access the ColumnAccessor instance by
     * name for filtering and sorting.
     * @return
     */
    public String getColumnName();

    /**
     * Returns text label to appear as column header in CellTable.
     * @return
     */
    public String getLabel();

    /**
     * Returns value of the column as a String
     * @param t Object that models the column values in a
     *          cell table row (Typically a Data Transfer Object)
     * @return
     */
    public String getValue(T t);

    /**
     * Returns Comparator for sorting data rows and for sorting
     * discrete values that appear in a filter's select/option list.
     * While the getValue() method always returns a String,
     * these comparators should sort the column's values in
     * consideration for the data type (for example, dates sorted
     * as dates, numbers sorted as numbers, strings sorted as strings).
     * @return
     */
    public Comparator comparator();

}

Following is the FilterSortDataProvider class: 以下是FilterSortDataProvider类:

import com.google.gwt.cell.client.SelectionCell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.SelectElement;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.cellview.client.Header;
import com.google.gwt.view.client.ListDataProvider;

import java.util.*;

/**
 * Class that extends a ListDataProvider but adds "Excel-Like" column filters and also
 * includes click on column heading sorts.
 * @param <T> Object that contains the column values in a cell table row. Typically a Data Transfer Object.
 */
public class FilterSortDataProvider<T> extends ListDataProvider {
    private List<T> rows;
    private List<T> filteredSortedRows;
    public Map<String, DataColumn> dataColumnMap = new HashMap<String, DataColumn>();
    private String lastSortColumn = "*";
    private int lastSortDirection = 0;

    /**
     * Constructs the DataProvider and columns
     * @param rows Collection of objects that contain column data for cell table rows, typically
     *             Data Transfer Objects.
     * @param columnAccessors List of ColumnAccessor instances for each column that will appear in
     *                        the cell table. Each accessor will render a sortable, filterable column header
     *                        and provides access to column-level data.
     */
    public FilterSortDataProvider(Collection<T> rows, List<ColumnAccessor> columnAccessors) {
        this.rows = new ArrayList<T>(rows);
        this.filteredSortedRows = new ArrayList<T>();
        Iterator<ColumnAccessor> columnAccessorIterator = columnAccessors.iterator();
        while (columnAccessorIterator.hasNext()) new DataColumn(columnAccessorIterator.next());
        // Initialize filters
        filter();
    }

    /**
     * Returns defensive copy of the current collection of filtered/sorted data rows
     * @return
     */
    public List<T> getFilteredSortedRows() {
        return new ArrayList(filteredSortedRows);
    }

    /**
     * Returns a CellTable Header for the named column for use when setting up the CellTable (ie:
     * used as Header value in cellTable.addColumn(TextColumn, Header) call. The header includes
     * the columnAccessor.getLabel() value as a click-to-sort header label, and a drop-down filter
     * where the options include all available values.
     * @param columnName Same value as returned by this columns ColumnAccessor.getColumnName()
     * @return
     */
    public Header getColumnHeader(final String columnName) {
        DataColumn column = dataColumnMap.get(columnName);
        return (column != null ? new FilteredCellTableHeader(column) : null);
    }

    /**
     * Called when user clicks on column header label. Repeated clicks on the same column header will
     * reverse the sort direction. Can also be called prior to display of CellTable to establish an initial
     * sort order.
     * @param sortColumnName
     */
    public void sort(String sortColumnName) {
        if (!sortColumnName.equals("*")) {
            DataColumn column = dataColumnMap.get(sortColumnName);
            if (column != null) {
                // Sort ascending
                Collections.sort(this.filteredSortedRows, column);
                // Re-Sort of same column
                if (sortColumnName.equals(lastSortColumn)) {
                    lastSortDirection *= -1;
                }
                else {
                    lastSortDirection = 1;
                    lastSortColumn = sortColumnName;
                }
                if (lastSortDirection == -1) Collections.reverse(filteredSortedRows);
            }
        }
        this.setList(filteredSortedRows);
    }

    /**
     * Optional call to pre-set filter before initial display of CellTable
     * @param columnName
     * @param value
     */
    public void filter(String columnName, String value) {
        DataColumn column = dataColumnMap.get(columnName);
        if (column != null) column.filter(value);
    }

    /**
     * Filters the rows based on all of the filters, and re-builds the filter drop-down
     * options.
     */
    private void filter() {
        // Build collection of rows that pass all filters
        filteredSortedRows = new ArrayList<T>();
        Iterator<T> rowIterator = this.rows.iterator();
        while (rowIterator.hasNext()) {
            T row = rowIterator.next();
            if (rowPassesFilter(row, null)) filteredSortedRows.add(row);
        }

        // Build filter select/option list for each column based on rows
        // that pass all filters EXCEPT for the column in question.
        Iterator<DataColumn> columnIterator = dataColumnMap.values().iterator();
        while (columnIterator.hasNext()) {
            DataColumn column = columnIterator.next();
            Set<String> optionsSet = new HashSet<String>();
            rowIterator = this.rows.iterator();
            while (rowIterator.hasNext()) {
                T row = rowIterator.next();
                if (rowPassesFilter(row, column)) {
                    optionsSet.add(column.filterOptionValue(row));
                }
            }
            // Sort the options using the ColumnAccessor's comparator
            List<String> optionsList = new ArrayList<String>(optionsSet);
            Collections.sort(optionsList, column.comparator());
            // Make blank option (if any) the last entry in the option list
            if (optionsList.contains(ColumnAccessor.FILTER_SELECTOR_BLANK)) {
                optionsList.remove(ColumnAccessor.FILTER_SELECTOR_BLANK);
                optionsList.add(ColumnAccessor.FILTER_SELECTOR_BLANK);
            }
            // Add the wild-card "All" as the first entry in the option list
            optionsList.add(0, "*");
            // Set the new list of options in the column
            column.filterOptions = optionsList;
        }
        // Re-sort the data with consideration for the current sort column and direction
        lastSortDirection *= -1;
        sort(lastSortColumn);
    }

    /**
     * Returns true if the specified row passes all column filters.
     * @param row Data row to test
     * @param columnToIgnore When specified, this column is assumed to allow the row
     *                       to pass the filter. This is used when building the list
     *                       of filter select/option values.
     * @return
     */
    private boolean rowPassesFilter(T row, DataColumn columnToIgnore) {
        Iterator<DataColumn> columnIterator = dataColumnMap.values().iterator();
        boolean passes = true;
        while (columnIterator.hasNext() && passes) {
            DataColumn column = columnIterator.next();
            if (column != columnToIgnore) {
                passes = column.rowPassesFilter(row);
            }
        }
        return passes;
    }

    /**
     * Inner class that models a CellTable column, its ColumnAccessor, current filter value,
     * and current filter option values.
     */
    public class DataColumn implements Comparator<T> {
        private String filterValue = "*";
        private List<String> filterOptions = new ArrayList<String>();
        private ColumnAccessor columnAccessor;

        /**
         * Constructs a filterable, sortable column
         * @param columnAccessor
         */
        public DataColumn(final ColumnAccessor columnAccessor) {
            this.columnAccessor = columnAccessor;
            FilterSortDataProvider.this.dataColumnMap.put(columnAccessor.getColumnName(), this);
        }

        /**
         * Returns symbolic name of column
         * @return
         */
        public String getName() {
            return this.columnAccessor.getColumnName();
        }

        /**
         * Returns display label for column header
         * @return
         */
        public String getLabel() {
            return columnAccessor.getLabel();
        }

        /**
         * Returns value of column
         * @param row
         * @return
         */
        public String getValue(T row) {
            return columnAccessor.getValue(row);
        }

        /**
         * Returns comparator define in ColumnAccessor for use when sorting
         * data rows and for sorting filter options.
         * @return
         */
        public Comparator comparator() {
            return columnAccessor.comparator();
        }

        /**
         * Called when user changes the value of a column filter
         * @param filterValue
         */
        public void filter(String filterValue) {
            if (this.filterOptions.contains(filterValue)) {
                this.filterValue = filterValue;
                FilterSortDataProvider.this.filter();
            }
        }

        /**
         * Called when user clicks on column label to sort rows
         */
        public void sort() {
            FilterSortDataProvider.this.sort(this.columnAccessor.getColumnName());
        }

        /**
         * Used to sort data rows. Uses comparator specified in ColumnAccessor.
         * @param row1
         * @param row2
         * @return
         */
        public int compare(T row1, T row2) {
            return comparator().compare(getValue(row1), getValue(row2));
        }

        /**
         * Returns true if specified row passes this column's filter
         * @param row
         * @return
         */
        public boolean rowPassesFilter(T row) {
            return filterValue.equals("*") || filterValue.equals(filterOptionValue(row));
        }

        /**
         * Returns value to appear in filter options list. Null or "blank" values appear in options
         * list as {Blank}.
         * @param row
         * @return
         */
        private String filterOptionValue(T row) {
            String value = getValue(row);
            return (value == null || value.trim().length() == 0 ? ColumnAccessor.FILTER_SELECTOR_BLANK : value);
        }

        /**
         * Renders Html Select/Options tag for column filter
         * @return
         */
        public String toHtmlSelect() {
            StringBuffer sb = new StringBuffer();
            sb.append("<select size='1' style='width: 100%;'>");
            Iterator<String> opts = filterOptions.iterator();
            while (opts.hasNext()) {
                String escapedOption = SafeHtmlUtils.htmlEscape(opts.next());
                sb.append("\t<option value='" + escapedOption);
                sb.append((escapedOption.equals(filterValue) ? "' SELECTED>" : "'>"));
                sb.append(escapedOption + "</option>\n");
            }
            sb.append("</select>\n");
            return sb.toString();
        }
    }

    /**
     * Inner class Header wrapper for FilteredSortedCellTableHeaderCell
     */
    public class FilteredCellTableHeader extends Header {
        public FilteredCellTableHeader(DataColumn column) {
            super(new FilteredSortedCellTableHeaderCell(column));
        }
        public Object getValue() {
            return null;
        }
    }

    /**
     * CellTable SelectionCell that includes filter and sort controls, renders controls, and
     * handles onBrowserEvent()
     */
    private class FilteredSortedCellTableHeaderCell extends SelectionCell {
        private DataColumn column;
        public FilteredSortedCellTableHeaderCell(final DataColumn column) {
            super(new ArrayList<String>());
            this.column = column;
        }

        /**
         * Renders Html Submit button as sort control, and Html Select/Option tag for filter.
         * @param context
         * @param value
         * @param sb
         */
        @Override
        public void render(Context context, String value, SafeHtmlBuilder sb) {
            String sortButton = "<input type='submit' value='" + SafeHtmlUtils.htmlEscape(column.getLabel()) +
                    "' style='text-align: center; width: 100%; background: none; border: none; font-weight: bold;'>";
            sb.appendHtmlConstant(sortButton);
            sb.appendHtmlConstant("<br>");
            sb.appendHtmlConstant(column.toHtmlSelect());
        }

        /**
         * Detects filter and sort user interaction events
         * @param context
         * @param parent
         * @param value
         * @param event
         * @param valueUpdater
         */
        @Override
        public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater) {
            super.onBrowserEvent(context, parent, value, event, valueUpdater);
            String type = event.getType();
            Element element = event.getEventTarget().cast();
            String tagName = element.getTagName();
            // Filter selection changed
            if ("change".equals(type) && tagName.equals("SELECT")) {
                // Set filter value and call filter routine
                SelectElement se = (SelectElement)element;
                String filterValue = se.getOptions().getItem(se.getSelectedIndex()).getValue();
                column.filter(filterValue);
            }
            // Click on sort button
            else if (type.equals("focus") && tagName.equals("INPUT")) {
                column.sort();
            }
        }
    }
}

I hope that this might be of help to someone. 我希望这可能对某人有所帮助。

Custom headers are what is used with GWT 2.1 to do sorting. 自定义标头是用于GWT 2.1进行排序的内容。 The 2.1 bikeshed has examples that use custom headers and am using one for sorting until Mvp4g moves to 2.2. 2.1自行车棚的示例使用自定义标头,并且正在使用一个标头进行排序,直到Mvp4g移至2.2。 To enable the filtering, just add an image with its own click handler and you should be good - it won't trigger sort behavior when it's clicked on, just the rest of the header will. 要启用过滤,只需添加一个带有其自己的单击处理程序的图像,您就应该会很好-单击它不会触发排序行为,只有标题的其余部分会触发。

table.addColumn(new MyColumn(new MyCell()), new MyFilterHeader());

For the actual filtering, if you're using the database model from the examples (the wrapper class for the ListDataProvider), then I'd think you'd just keep two lists - the filtered list that's assigned to the ListDataProvider, and the unfiltered list it's based on. 对于实际的过滤,如果您使用示例中的数据库模型(ListDataProvider的包装器类),那么我认为您将只保留两个列表-分配给ListDataProvider的过滤列表和未过滤的列表列出其依据。

Hope that helps! 希望有帮助!


In your new sample code, you might want to try a CompositeCell with the ClickableTextCell inside it, along with an ActionCell for the filtering part - if you can stick an image into the ClickableTextCell, you should be able to in the ActionCell, plus it'll have the mouseup behavior you want. 在新的示例代码中,您可能希望尝试使用其中具有ClickableTextCell的CompositeCell以及用于过滤部分的ActionCell-如果您可以将图像粘贴到ClickableTextCell中,则应该可以在ActionCell中加上它,将具有您想要的mouseup行为。

I used the mouse-click position to add custom click events to column headers. 我使用鼠标单击位置将自定义单击事件添加到列标题。 In other words, you can set it up so that if the user clicks in the 'general area' where the image is supposed to be, you can show a filtering screen. 换句话说,您可以对其进行设置,以便如果用户单击应该在其中放置图像的“一般区域”,则可以显示一个过滤屏幕。

Here's an example where I have it ignoring click events for a text field I added: 这是一个示例,其中我忽略了添加的文本字段的点击事件:

        if(col.isFilterable()){
            if (event.getClientY() > (getInputElement(parent).getAbsoluteTop() - 2) && event.getClientY() < (getInputElement(parent).getAbsoluteBottom() + 2)) {
                //ignore on click in area of the text field
                event.preventDefault();
            } else {

//sort if user clicks anywhere else trySort(parent); //如果用户单击其他任何地方,则进行排序trySort(parent); } }

And the because cell listens for 'keyup' events separately, the filter is executed when the user hits enter (while the cell is focused). 由于因为单元格分别侦听“ keyup”事件,所以当用户按下Enter键(单元格处于焦点状态)时,将执行过滤器。

    if(event.getKeyCode()==13){
        event.preventDefault();
        handleSetFilterValue(parent);
        tryFilter();
    } 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM