CellTable с пользовательским заголовком, содержащим SearchBox и Focus Problem - PullRequest
11 голосов
/ 21 июня 2011

Я пытаюсь реализовать CellTable с пользовательским заголовком столбца, который отображает SearchBox (простое текстовое поле) ниже обычного текста столбца.
SearchBox должен позволять пользователю фильтровать CellTable.Это должно выглядеть примерно так:

  |Header  1|Header 2 |
  |SEARCHBOX|SEARCHBOX|
  -------------------------------------------------------
  |    ROW 1 
  ------------------------------------------------------
  |    ROW 2 

Как только пользователь вводит символ в поле поиска, запускается 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));

Ответы [ 2 ]

22 голосов
/ 15 августа 2012

Тощий

К сожалению, поддержка GWT для пользовательских заголовков столбцов, по меньшей мере, немного странна. Если бы кто-то имел удовольствие работать с классами AbstractCell, вы бы знали, что я имею в виду. Кроме того, правильным способом реализации составных (вложенных виджетов) в ячейку заголовка столбца является перебор, так как я не смог заставить его работать должным образом и не нашел работоспособных примеров работы CompositeCell.

Если ваша сетка данных реализует своего рода ColumnSortHandler (LOL thats phunny), ваши вложенные объекты пользовательского интерфейса, которые могут иметь события клавиш или мыши, будут вызывать сортировку столбцов. ПОТЕРПЕТЬ ПОРАЖЕНИЕ. Опять же, я не смог найти способ перегрузить события columnsort, чтобы исключить срабатывание триггеров при взаимодействии с вложенными компонентами / виджетами заголовка столбца. Не говоря уже о том, что вам нужно абстрактно определять вложенные компоненты, записывая встроенный HTML-код в интерфейс Template, который создает вашу ячейку. Не совсем элегантный выбор, поскольку он заставляет разработчиков писать собственный код JavaScript для создания и управления обработчиками, связанными с вложенными компонентами / виджетами в заголовке столбца.

Эта «правильная» методика реализации также не решает проблему фокуса, к которой относится этот вопрос, и не является почти отличным решением для сложных сетей данных, которым требуются наборы данных AsyncProvider (или ListProvider) с фильтрацией ячеек столбцов или пользовательской визуализацией. Производительность этого тоже meh> _> далека от правильного решения IMO

Серьезно ???

Чтобы реализовать функциональную фильтрацию ячеек столбцов, вы должны решить эту проблему с помощью более традиционного динамического подхода javascript / css, существовавшего до GWT и сумасшедших библиотек JQuery. Мое функциональное решение - гибрид "правильного" пути с каким-то хитрым CSS.

код псевдо выглядит следующим образом:

  1. убедитесь, что ваша сетка обернута LayoutPanel
  2. убедитесь, что столбцы вашей сетки управляются коллекцией / списком
  3. создать пользовательский заголовок столбца, чтобы создать область для фильтрации
  4. создайте контейнер фильтрации, чтобы поместить текстовые поля в
  5. макет вашей сетки контейнеров дочерних элементов (сетка, фильтр, пейджер)
  6. использовать методы css для размещения фильтров в заголовках столбцов
  7. добавить обработчики событий в фильтры
  8. добавить таймер для обработки задержек на входе фильтра
  9. функция обновления сетевой сетки для обновления данных, асинхронного или локального списка

Вот так, надеюсь, я еще не потерял тебя, так как есть много, чтобы сделать эту работу


Шаг 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 table .columnHeader div img {position :lative;верх: -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 (для сортировки изагрузка данных) мы хотим выполнить итерацию, хотя наша коллекция фильтров для примененных нами фильтров была введена пользователем. Лично я сохраняю все параметры запроса в хэш-карте для быстрого доступа и обновления. Затем просто передаю всю карту в механизм запросов, которыйделает ваши 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 выше.

* Готово <<em> _ __ _ _> *


хорошо, я надеюсь, что это поможет всем вам с некоторыми продвинутыми вещами GWT DataGrid.Я знаю, что это было трудно создать самому себе, так что, надеюсь, это сэкономит вам кучу времени в будущем.Гудлак!

0 голосов
/ 23 мая 2019

в Blogger этот код следует

<b:if  cond='data:blog.pageType == "static_page"'></b:if>

подробнее на Контент, который вы хотите показывать на статических страницах [1]

...