CellTable с пользовательским заголовком, содержащим SearchBox и Focus Problem
Я пытаюсь реализовать CellTable с пользовательским заголовком столбца, который отображает SearchBox (простое текстовое поле) ниже обычного текста столбца.
SearchBox должен позволять пользователю фильтровать CellTable. Это должно выглядеть примерно так:
|Header 1|Header 2 |
|SEARCHBOX|SEARCHBOX|
-------------------------------------------------------
| ROW 1
------------------------------------------------------
| ROW 2
Как только пользователь вводит символ в SearchBox, запускается RangeChangeEvent, что приводит к запросам сервера, и CellTable обновляется новым отфильтрованным списком.
В основном все работает отлично. Однако, как только CellTable обновляется, SearchBox теряет фокус, и пользователь должен снова щелкнуть мышью в SearchBox, чтобы ввести нового персонажа.
Вероятно, это связано с тем, что метод рендеринга пользовательского заголовка и его ячейки вызывается после обновления CellTable.
Есть ли способ установить фокус обратно на SearchBox? Я пытался установить tabindex=0, но это не помогло.
Пользовательский класс заголовка
public static class SearchHeader extends Header<SearchTerm> {
@Override
public void render(Context context, SafeHtmlBuilder sb) {
super.render(context, sb);
}
private SearchTerm searchTerm;
public SearchHeader(SearchTerm searchTerm,ValueUpdater<SearchTerm> valueUpdater) {
super(new SearchCell());
setUpdater(valueUpdater);
this.searchTerm = searchTerm;
}
@Override
public SearchTerm getValue() {
return searchTerm;
}
}
Ячейка пользовательского поиска (используется в пользовательском заголовке)
Логический флаг isChanged устанавливается в значение true, когда пользователь вводит что-то в SearchBox, и возвращается в значение false, если элемент SearchBox теряет фокус. Я добавил этот флаг, чтобы различать, какой SearchBox получает фокус (в случае, если я использую несколько SearchBox)
public static class SearchCell extends AbstractCell<SearchTerm> {
interface Template extends SafeHtmlTemplates {
@Template("<div style=\"\">{0}</div>")
SafeHtml header(String columnName);
@Template("<div style=\"\"><input type=\"text\" value=\"{0}\"/></div>")
SafeHtml input(String value);
}
private static Template template;
private boolean isChanged = false;
public SearchCell() {
super("keydown","keyup","change","blur");
if (template == null) {
template = GWT.create(Template.class);
}
}
@Override
public void render(com.google.gwt.cell.client.Cell.Context context,
SearchTerm value, SafeHtmlBuilder sb) {
sb.append(template.header(value.getCriteria().toString()));
sb.append(template.input(value.getValue()));
}
@Override
public void onBrowserEvent(Context context,Element parent, SearchTerm value,NativeEvent event,ValueUpdater<SearchTerm> valueUpdater) {
if (value == null)
return;
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if ("keyup".equals(event.getType()))
{
isChanged = true;
InputElement elem = getInputElement(parent);
value.setValue(elem.getValue());
if (valueUpdater != null)
valueUpdater.update(value);
}
else if ("blur".equals(event.getType())) {
isChanged =false;
}
}
protected InputElement getInputElement(Element parent) {
Element elem = parent.getElementsByTagName("input").getItem(0);
assert(elem.getClass() == InputElement.class);
return elem.cast();
}
}
Код инициализации для CellTable
NameColumn - это реализация абстрактного класса Column с соответствующими типами. Он использует TextCell внутри.
ValueUpdater<SearchTerm> searchUpdater = new ValueUpdater<SearchTerm>() {
@Override
public void update(AccessionCellTableColumns.SearchTerm value) {
// fires a server request to return the new filtered list
RangeChangeEvent.fire(table, new Range(table.getPageStart(), table.getPageSize()));
}
};
table.addColumn(new NameColumn(searchTerm),new SearchHeader(searchTerm,searchUpdater));
1 ответ
Тощий
К сожалению, поддержка GWT для пользовательских заголовков столбцов, по меньшей мере, немного странна. Если бы кто-то имел удовольствие работать с классами AbstractCell, вы бы знали, что я имею в виду. Кроме того, правильным способом реализации составных (вложенных виджетов) в ячейку заголовка столбца является перебор, так как я не смог заставить его работать должным образом и не нашел работоспособных примеров работы CompositeCell.
Если ваша сетка данных реализует своего рода ColumnSortHandler (LOL thats phunny), ваши вложенные объекты пользовательского интерфейса, которые могут иметь события клавиш или мыши, будут вызывать сортировку столбцов. ПОТЕРПЕТЬ ПОРАЖЕНИЕ. Опять же, я не смог найти способ перегрузки событий сортировки столбцов, чтобы исключить срабатывание триггеров при взаимодействии с вложенными компонентами / виджетами заголовка столбца. Не говоря уже о том, что вам нужно абстрактно определять вложенные компоненты, записывая встроенный HTML-код в интерфейс Template, который создает вашу ячейку. Не совсем элегантный выбор, поскольку он заставляет разработчиков писать собственный код JavaScript для создания и управления обработчиками, связанными с вложенными компонентами / виджетами в заголовке столбца.
Эта "правильная" методика реализации также не решает проблему фокуса, к которой относится этот вопрос, и не является почти отличным решением для сложных сетей данных, которым требуются наборы данных AsyncProvider (или ListProvider) с фильтрацией ячеек столбцов или пользовательской визуализацией. Производительность этого тоже meh >_> Далеко от правильного решения IMO
Шутки в сторону???
Чтобы реализовать функциональную фильтрацию ячеек столбцов, вы должны решить эту проблему с помощью более традиционного динамического подхода javascript/css, существовавшего до GWT и сумасшедших библиотек JQuery. Мое функциональное решение - гибрид "правильного" пути с каким-то хитрым CSS.
код псевдо выглядит следующим образом:
- убедитесь, что ваша сетка обернута LayoutPanel
- убедитесь, что столбцы вашей сетки управляются коллекцией / списком
- создать пользовательский заголовок столбца, чтобы создать область для вашей фильтрации
- создайте фильтрующий контейнер для размещения ваших текстовых полей
- макет вашей сетки контейнеров детей (сетка, фильтр, пейджер)
- использовать методы CSS для позиционирования фильтров в заголовках столбцов
- добавить обработчики событий в фильтры
- добавить таймер для обработки задержек на входе фильтра
- функция обновления огневой сетки для обновления данных, асинхронного или локального списка
хм, надеюсь, я еще не потерял тебя, так как есть много, чтобы сделать эту работу
Шаг 1: Настройка класса сетки для расширения LayoutPanel
Во-первых, вы должны убедиться, что ваш класс, который создает вашу сетку, может поддерживать и иметь правильный размер в вашем приложении. Для этого убедитесь, что ваш класс сетки расширяет LayoutPanel.
public abstract class PagingFilterDataGrid<T> extends LayoutPanel {
public PagingFilterDataGrid() {
//ctor initializers
initDataGrid();
initColumns();
updateColumns();
initPager();
setupDataGrid();
}
}
Шаг 2. Создание управляемых столбцов
Этот шаг также довольно прост. Вместо этого непосредственно добавьте новые столбцы в таблицу данных, сохраните их в список, а затем программно добавьте их в свою таблицу с помощью оператора foreach.
ColumnModel (вы должны быть в состоянии создать число или дату, или любой другой тип столбца, который вы хотите. Для простоты я обычно работаю со строковыми данными в веб-приложениях, если только мне явно не нужна специальная арифметика или функция даты)
public abstract class GridStringColumn<M> extends Column<VwGovernorRule, String> {
private String text_;
private String tooltip_;
private boolean defaultShown_ = true;
private boolean hidden_ = false;
public GridStringColumn(String fieldName, String text, String tooltip, boolean defaultShown, boolean sortable, boolean hidden) {
super(new TextCell());
setDataStoreName(fieldName);
this.text_ = text;
this.tooltip_ = tooltip;
this.defaultShown_ = defaultShown;
setSortable(sortable);
this.hidden_ = hidden;
}
}
создайте список в своем классе данных для хранения ваших столбцов в
public abstract class PagingFilterDataGrid<T> extends LayoutPanel {
private List<GridStringColumn<T>> columns_ = new ArrayList<GridStringColumn<T>>();
}
чтобы создать столбцы, создайте метод initColumn, который вызывается в конструкторе сетки данных. Обычно я расширяю базовый класс сетки данных, чтобы я мог поместить в него свои конкретные инициализаторы сетки. Это добавит столбец к вашему хранилищу столбцов. MyPOJODataModel - это ваша структура данных, в которой вы храните записи для сетки данных, обычно это POJO вашего спящего режима или что-то из вашего бэкэнда.
@Override
public void initColumns() {
getColumns().add(new GridStringColumn<MyPOJODataModel>("columnName", "dataStoreFieldName", "column tooltip / description information about this column", true, true, false) {
@Override
public String getValue(MyPOJODataModelobject) {
return object.getFieldValue();
}
});
}
Создайте некоторый код для обновления ваших столбцов в вашей сетке, убедитесь, что вы вызываете этот метод после вызова метода initColumns. метод initFilters мы скоро получим. Но если вам нужно знать сейчас, это метод, который устанавливает ваши фильтры на основе того, какие столбцы у вас есть в вашей коллекции. Вы также можете вызывать эту функцию всякий раз, когда хотите показать / скрыть столбцы или изменить порядок столбцов в сетке. я знаю, ты любишь это!
@SuppressWarnings("unchecked")
public void updateColumns() {
if (dataGrid_.getColumnCount() > 0) {
clearColumns();
}
for (GridStringColumn<T> column : getColumns()) {
if (!column.isHidden()) {
dataGrid_.addColumn((Column<T, ?>) column, new ColumnHeader(column.getText(), column.getDataStoreName()));
}
}
initFilters();
}
Шаг 3. Создайте пользовательский заголовок столбца
Теперь мы приступаем к интересным вещам, теперь у нас есть решетка и столбцы, готовые для фильтрации. Эта часть похожа на пример кода, который задает этот вопрос, но немного отличается. Здесь мы создаем новый пользовательский AbstractCell, который задает HTML-шаблон для GWT, который будет отображаться во время выполнения. Затем мы внедряем этот новый шаблон ячейки в наш пользовательский класс заголовка и передаем его в метод addColumn(), который использует данные gwt для создания нового столбца в вашей сетке данных.
Ваша пользовательская ячейка:
final public class ColumnHeaderFilterCell extends AbstractCell<String> {
interface Templates extends SafeHtmlTemplates {
@SafeHtmlTemplates.Template("<div class=\"headerText\">{0}</div>")
SafeHtml text(String value);
@SafeHtmlTemplates.Template("<div class=\"headerFilter\"><input type=\"text\" value=\"\"/></div>")
SafeHtml filter();
}
private static Templates templates = GWT.create(Templates.class);
@Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
if (value == null) {
return;
}
SafeHtml renderedText = templates.text(value);
sb.append(renderedText);
SafeHtml renderedFilter = templates.filter();
sb.append(renderedFilter);
}
}
Если вы не научились ненавидеть то, как вы создаете пользовательские ячейки, вы скоро почувствуете это, когда закончите реализацию этого. Далее нам нужен заголовок, чтобы вставить эту ячейку в
заголовок столбца:
public static class ColumnHeader extends Header<String> {
private String name_;
public ColumnHeader(String name, String field) {
super(new ColumnHeaderFilterCell());
this.name_ = name;
setHeaderStyleNames("columnHeader " + field);
}
@Override
public String getValue() {
return name_;
}
}
как вы можете видеть, это довольно простой и простой класс. Честно говоря, это больше похоже на оболочку, поэтому GWT подумал о том, чтобы объединить их в определенную ячейку заголовка столбца, а не вводить общую ячейку, мне не понятно. Может быть, не супер фантазия, но я уверен, что было бы намного легче работать с
если вы посмотрите выше на свой метод updateColumns(), вы увидите, что он создает новый экземпляр этого класса columnheader при добавлении столбца. Также убедитесь, что вы достаточно точны в том, что вы делаете статическим и окончательным, чтобы не тратить свою память при создании очень больших наборов данных... IE 1000 строк в 20 столбцах - это 20000 вызовов или экземпляров шаблона или членов, которые вы сохранили. Таким образом, если один элемент в вашей ячейке или заголовке имеет 100 байтов, которые превращаются в 2 МБ или ресурсы или больше только для CTOR. Опять же, это не так важно, как отображение пользовательских ячеек данных, но это также важно и для ваших заголовков!!!
Теперь не забудьте добавить свой CSS
.gridData table {
overflow: hidden;
white-space: nowrap;
table-layout: fixed;
border-spacing: 0px;
}
.gridData table td {
border: none;
border-right: 1px solid #DBDBDB;
border-bottom: 1px solid #DBDBDB;
padding: 2px 9px
}
.gridContainer .filterContainer {
position: relative;
z-index: 1000;
top: 28px;
}
.gridContainer .filterContainer td {
padding: 0 13px 0 5px;
width: auto;
text-align: center;
}
.gridContainer .filterContainer .filterInput {
width: 100%;
}
.gridData table .columnHeader {
white-space: normal;
vertical-align: bottom;
text-align: center;
background-color: #EEEEEE;
border-right: 1px solid #D4D4D4;
}
.gridData table .columnHeader div img {
position: relative;
top: -18px;
}
.gridData table .columnHeader .headerText {
font-size: 90%;
line-height: 92%;
}
.gridData table .columnHeader .headerFilter {
visibility: hidden;
height: 32px;
}
теперь это CSS для всего того, что ты собираешься добавить. Я слишком ленив, чтобы отделить это, плюс я думаю, ты можешь понять это. gridContainer - это панель макета, которая оборачивает вашу сетку данных, а gridData - это ваша фактическая сетка данных.
Теперь при компиляции вы должны увидеть пробел под текстом заголовка столбца. Здесь вы разместите свои фильтры в css
Шаг 4: Создайте свой фильтр-контейнер
Теперь нам нужно что-то, чтобы поместить наши входные данные фильтра. К этому контейнеру также применен css, который переместит его в пространство, которое мы только что создали в заголовках. Да, верно, фильтры, которые находятся в заголовке, фактически и технически не находятся в заголовке. Это единственный способ избежать проблемы события сортировки и потерять фокус
private HorizontalPanel filterContainer_ = new HorizontalPanel();
и ваш фильтр инициализации
public void initFilters() {
filterContainer_.setStylePrimaryName("filterContainer");
for (GridStringColumn<T> column : getColumns()) {
if (!column.isHidden()) {
Filter filterInput = new Filter(column);
filters_.add(filterInput);
filterContainer_.add(filterInput);
filterContainer_.setCellWidth(filterInput, "auto");
}
}
}
Вы можете видеть, что он требует вашей коллекции столбцов для правильного создания входов фильтра, которые входят в ваш контейнер. Кроме того, класс фильтра также передается в столбец, чтобы связать столбец с конкретным фильтром. Это позволяет вам получить доступ к полям и тому подобное
public class Filter extends TextBox {
final private GridStringColumn<T> boundColumn_;
public Filter(GridStringColumn<T> column) {
super();
boundColumn_ = column;
addStyleName("filterInput " + boundColumn_.getDataStoreName());
addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
filterTimer.cancel();
filterTimer.schedule(FILTER_DELAY);
}
});
}
public GridStringColumn<T> getBoundColumn() {
return boundColumn_;
}
}
Шаг 5: добавьте свои компоненты на панель LayoutPanel
теперь, когда вы запускаете свою сетку, чтобы добавить пейджер и сетку в контейнер макетов, мы не учитываем вертикальную высоту, которую фильтр обычно должен занимать. Так как он установлен в относительное положение с z-индексом, тогда как сетка и столбцы будут отображаться в заголовке. МАГИЯ!!!
public void setupDataGrid() {
add(pagerContainer_);
setWidgetTopHeight(pagerContainer_, 0, Unit.PX, PAGER_HEIGHT, Unit.PX);
add(filterContainer_);
setWidgetTopHeight(filterContainer_, PAGER_HEIGHT + FILTER_HEIGHT, Unit.PX, FILTER_HEIGHT, Unit.PX);
add(dataGrid_);
setWidgetTopHeight(dataGrid_, PAGER_HEIGHT, Unit.PX, ScreenManager.getScreenHeight() - PAGER_HEIGHT - BORDER_HEIGHT, Unit.PX);
pager_.setVisible(true);
dataGrid_.setVisible(true);
}
а теперь для некоторых констант
final private static int PAGER_HEIGHT = 32;
final private static int FILTER_HEIGHT = 32;
final private static int BORDER_HEIGHT = 2;
Высота границы соответствует конкретному стилю CSS, который может иметь ваше приложение, технически это пробковое пространство, чтобы убедиться, что все плотно прилегает.
Шаг 6: Используйте CSS Magic
конкретный CSS, который помещает фильтры на ваши столбцы сверху, это
.gridContainer .filterContainer {
position: relative;
z-index: 1000;
top: 28px;
}
который переместит контейнер над столбцами и разместит там слой над вашими заголовками
Далее нам нужно убедиться, что ячейки в filterContainer совпадают с ячейками в нашей сетке данных.
.gridContainer .filterContainer td {
padding: 0 13px 0 5px;
width: auto;
text-align: center;
}
Затем убедитесь, что наши входные данные масштабируются в соответствии с размером ячейки контейнера, в которой они живут.
.gridContainer .filterContainer .filterInput {
width: 100%;
}
наконец, мы хотим переместить наш индикатор сортировки изображения вверх, чтобы текстовые поля фильтра ввода не скрывали их
.gridData таблица.columnHeader div img {позиция: относительная; верх: -18px; }
теперь, когда вы компилируете, вы должны увидеть фильтры над заголовками столбцов. вам может понадобиться настроить CSS, чтобы они точно выстроились в линию. Это также предполагает, что у вас не установлены специальные ширины столбцов. если вы это сделаете, вам нужно будет создать некоторые дополнительные функции, чтобы вручную устанавливать размеры ячеек и стили ширины для синхронизации со столбцами. Я пропустил это для santity.
* сейчас время для перерыва, ваш почти там! ^ _ __ _ __ _ _ ^ *
Шаг 7 и 8: добавление обработчиков событий
Это легкая часть. если вы посмотрите на класс фильтра сверху, обратите внимание на тело этого метода
addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
filterTimer.cancel();
filterTimer.schedule(FILTER_DELAY);
}
});
создать свой таймер фильтра
private FilterTimer filterTimer = new FilterTimer();
убедитесь, что вы указали поле в корне тела класса, а не в строке.
private class FilterTimer extends Timer {
@Override
public void run() {
updateDataList();
}
}
необходим таймер, чтобы событие не срабатывало каждый раз, когда пользователь вводит значение. Многие люди добавили onblur или другие глупые обработчики, но это бессмысленно. Пользователь может вводить данные только в одно поле за раз, так что это немой момент. просто используйте обработчик onKeyUp..
Шаг 9: Обновите свою сетку
теперь, когда мы вызываем updateDataList (который также должен вызываться из вашего события onRangeChanged (для сортировки и загрузки данных), мы хотим выполнить итерацию, хотя наша коллекция Filters для наших примененных фильтров введена пользователем. Лично я сохраняю все параметры запроса в HashMap для легкого доступа и обновления. Затем просто передайте всю карту в мой механизм запросов, который выполняет ваши RPC или RequestFactory вещи
public void updateDataList() {
initParameters();
// required parameters controlled by datagrid
parameters_.put("limit", limit_ + "");
parameters_.put("offset", offset_ + "");
// sort parameters
if (sortField_.equals("") || sortOrder_.equals("")) {
parameters_.remove("sortField");
parameters_.remove("sortDir");
} else {
parameters_.put("sortField", sortField_);
parameters_.put("sortDir", sortOrder_);
}
// filter parameters
for (Filter filter : filters_) {
if (!filter.getValue().equals("")) {
CGlobal.LOG.info("filter: " + filter.getBoundColumn().getDataStoreName() + "=" + filter.getValue());
parameters_.put(filter.getBoundColumn().getDataStoreName(), filter.getValue());
}
}
RequestServiceAsync requestService = (RequestServiceAsync) GWT.create(RequestService.class);
requestService.getRequest("RPC", getParameters(), new ProviderAsyncCallback());
}
Вы можете увидеть, как и почему нам нужно привязать фильтр к столбцу, поэтому, когда мы выполняем итерацию по фильтрам, мы можем получить имя сохраненного поля. обычно я просто передаю имя поля и значение фильтра в качестве параметра запроса, а не передаю все фильтры как один параметр запроса фильтра. Это намного более расширяемо, и редко встречаются случаи, когда ваши столбцы БД должны == зарезервировать слова для параметров запроса, таких как sortDir или sortField выше.
* Готово < _ __ _ _> *
Что ж, я надеюсь, что это поможет всем вам с некоторыми продвинутыми вещами GWT DataGrid. Я знаю, что это было трудно создать самому себе, так что, надеюсь, это сэкономит вам кучу времени в будущем. Удачи!
В Blogger этот код следует
<b:if cond='data:blog.pageType == "static_page"'></b:if>
см. больше в Контенте, который вы хотите показать на статических страницах[1]