[英]GWT: add filtering to CellTable
我的任務是對GWT CellTable中顯示的數據進行排序和過濾。 值得慶幸的是,GWT已經支持排序,但是看起來我將不得不結合自己的過濾支持。
更准確地說,我要支持的內容類似於Excel提供的過濾,在該過濾中,您可以單擊列標題中的下拉菜單,然后單擊(例如)復選框以過濾行。基於已過濾列的值。 一張圖片勝過千言萬語:
我的問題 :關於如何在GWT 2.2中實現此建議? 可能嗎?
我正在考慮的一種選擇是將自定義Header對象傳遞給CellTable.addColumn()。 如果可能,我將ClickHandler添加到頁眉中,然后打開一個彈出窗口,其中顯示用於過濾的小部件。 不知道如何在不負面影響排序行為的情況下實現這一點。
任何建議都歡迎。
編輯 :
感謝下面的John,我得到了以下FilterableHeader
類,該類使我至少可以在標頭中放置一個圖標。 由於圖像是通過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()));
}
}
我開發了業務應用程序,其中典型的數據庫查詢可能返回數百或數千行。 用戶發現類似excel的過濾器和列排序非常有幫助。
因此,我實現了一個擴展ListDataProvider的類,以便與支持客戶端類似於excel的列過濾和排序的CellTable一起使用。 在所有其他方面,它的行為都非常類似於ListDataProvider。
它依賴於實現以下ColumnAccessor接口,以便為CellTable中的每個列提供符號名稱,提供對列級數據的訪問以進行排序和過濾,為列進行比較的Comparator以及為標題顯示標簽。 以下是ColumnAccessor類。 假定您具有某種對行進行建模的數據傳輸對象<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();
}
以下是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();
}
}
}
}
我希望這可能對某人有所幫助。
自定義標頭是用於GWT 2.1進行排序的內容。 2.1自行車棚的示例使用自定義標頭,並且正在使用一個標頭進行排序,直到Mvp4g移至2.2。 要啟用過濾,只需添加一個帶有其自己的單擊處理程序的圖像,您就應該會很好-單擊它不會觸發排序行為,只有標題的其余部分會觸發。
table.addColumn(new MyColumn(new MyCell()), new MyFilterHeader());
對於實際的過濾,如果您使用示例中的數據庫模型(ListDataProvider的包裝器類),那么我認為您將只保留兩個列表-分配給ListDataProvider的過濾列表和未過濾的列表列出其依據。
希望有幫助!
在新的示例代碼中,您可能希望嘗試使用其中具有ClickableTextCell的CompositeCell以及用於過濾部分的ActionCell-如果您可以將圖像粘貼到ClickableTextCell中,則應該可以在ActionCell中加上它,將具有您想要的mouseup行為。
我使用鼠標單擊位置將自定義單擊事件添加到列標題。 換句話說,您可以對其進行設置,以便如果用戶單擊應該在其中放置圖像的“一般區域”,則可以顯示一個過濾屏幕。
這是一個示例,其中我忽略了添加的文本字段的點擊事件:
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(parent); }
由於因為單元格分別偵聽“ keyup”事件,所以當用戶按下Enter鍵(單元格處於焦點狀態)時,將執行過濾器。
if(event.getKeyCode()==13){
event.preventDefault();
handleSetFilterValue(parent);
tryFilter();
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.