GWT:向CellTable添加过滤

时间:2011-03-04 01:48:48

标签: java gwt

我的任务是实施排序和安排。过滤GWT CellTable中显示的数据。 值得庆幸的是,GWT已经支持排序,但看起来我必须将我自己的过滤支持黑客攻击。

更准确地说,我想要支持的内容类似于Excel提供的过滤功能,您可以单击列标题中的下拉菜单(例如)单击复选框以允许您根据筛选列的值筛选行。一张图片胜过千言万语:

excel column filtering

我的问题:有关如何在GWT 2.2中实现此功能的任何建议?它甚至可能吗?

我正在考虑的一个选项是将自定义Header对象传递给CellTable.addColumn()。如果可能的话,我会将一个ClickHandler添加到Header,然后打开一个Popup,显示一个用于过滤的小部件。不确定如何实现这一点而不会对排序行为产生负面影响。

欢迎任何建议。

修改

感谢下面的John,我有以下FilterableHeader课程,它允许我至少在标题中添加一个图标。目前还不确定如何在该图标上获取ClickHandler,因为图像是通过HTML插入的,而不是使用GWT小部件。

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()));
    }
}

3 个答案:

答案 0 :(得分:2)

自定义标头是GWT 2.1用于排序的标头。 2.1 bikeshed包含使用自定义标头的示例,并使用一个进行排序,直到Mvp4g移动到2.2。要启用过滤,只需添加一个带有自己的点击处理程序的图像,你应该做得很好 - 点击它时不会触发排序行为,只有标题的其余部分会出现。

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

对于实际过滤,如果您正在使用数据库模型from the examples(ListDataProvider的包装类),那么我认为您只需要保留两个列表 - 已分配给它的已过滤列表ListDataProvider,以及它所基于的未过滤列表。

希望有所帮助!


在您的新示例代码中,您可能想要尝试使用其中包含ClickableTextCell的CompositeCell以及用于过滤部分的ActionCell - 如果您可以将图像粘贴到ClickableTextCell中,那么应该能够在ActionCell中加上你想要的鼠标行为。

答案 1 :(得分:1)

我使用鼠标单击位置将自定义点击事件添加到列标题。换句话说,您可以对其进行设置,以便在用户点击图像所在的“常规区域”时,您可以显示过滤屏幕。

这是一个例子,我忽略了我添加的文本字段的点击事件:

        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 {

//如果用户点击其他任何地方,则排序                     trySort(父);                 }

并且,因为单元格分别侦听'keyup'事件,当用户点击enter(当单元格被聚焦时)执行过滤器。

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

答案 2 :(得分:1)

我开发业务应用程序,其中典型的数据库查询可能返回数百或数千行。用户发现类似excel的过滤器和列排序非常有用。

因此,我实现了一个扩展ListDataProvider的类,以便与支持客户端类似excel的列过滤和排序的CellTable一起使用。在所有其他方面,它的行为很像ListDataProvider。

它取决于实现以下ColumnAccessor接口,为CellTable中的每个列提供符号名称,提供对列级数据的排序和过滤访问,用于排序的列的比较器以及显示标签标题。以下是ColumnAccessor类。它假设您有某种数据传输对象&lt; T&gt;对行进行建模。

/**
 * 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();

}

以下是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();
            }
        }
    }
}

我希望这对某人有帮助。