p: выборки dataTable теряются после разбиения на страницы LazyDataModel

Моя проблема в том, что после того, как я выбрал несколько элементов на 1-й странице, если я разбил страницу на другую страницу и вернулся, мои начальные выборы не отображаются. Я пытался реализовать SelectableDataModel а также с использованием rowKey атрибут, но проблема сохраняется.

Это мой тестовый боб:

@ManagedBean
@ViewScoped
public class MrBean {
    private List<Item> chosenItems;
    private LazyDataModel lazyModel;

    @PostConstruct
    public void prepareTest() {
        this.lazyModel = new LazyItemDataModel();
    }

    public void countItems() {
        System.out.println("TEST 3: chosenItems's size: " + chosenItems.size());
    }

    private class LazyItemDataModel extends LazyDataModel<Item> implements SelectableDataModel<Item> {
        @Override
        public Item getRowData(String rowKey) {
            System.out.println("TEST 1: getRowData");
            Iterator<Item> iter = ((List<Item>) this.getWrappedData()).iterator();
            while (iter.hasNext()) {
                Item item = iter.next();
                if (item.getId().equals(rowKey)) {
                    return item;
                }
            }

            return null;
        }

        @Override
        public Object getRowKey(Item item) {
            return item.getId();
        }

        @Override
        public List<Item> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map filters) {
            System.out.println("TEST 2: load");
            // Code to retrieve items from database
        }
    }

    // Getters and Setters
}

Это моя тестовая страница:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui">
    <h:head>
        <title>Test page</title>
    </h:head>
    <h:body>
        <h:form>
            <p:dataTable id="itemTable" var="item" value="#{mrBean.items}" rows="5" 
                         paginator="true" selection="#{mrBean.chosenItems}" lazy="true" >

                <p:ajax event="rowSelectCheckbox" listener="mrBean.countItems" />                    

                <p:column selectionMode="multiple" />

                <p:column headerText="ID">
                    <h:outputText value="#{item.id}" /> 
                </p:column>

                <p:column headerText="Name">
                    <h:outputText value="#{item.name}" /> 
                </p:column>

            </p:dataTable>
        </h:form>
    </h:body>
</html>

Буду очень признателен, если вы покажете мне, что я здесь не так сделал.

ОБНОВЛЕНИЕ: после того, как я добавил больше System.out.println("TEST") В приведенном выше коде я заметил следующие вещи:

  1. На консоли каждый раз, когда я делаю пагинацию, TEST 1: getRowData всегда печатается раньше TEST 2: load, Как следствие, я считаю, что метод #LazyDataModel.getWrappedData() может вернуть данные со старой страницы. Сначала я думал, что целью этого метода является получение выбранных строк для выделения на таблице. Однако, если этот метод вызывается раньше loadнет ли способа сделать работу правильно?
  2. После того, как я выбрал первые 2 пункта на первой странице, на консоли я увидел TEST 3: chosenItems's size: 2, Если я перейду на 2-ю страницу, а затем вернусь на 1-ю страницу, выборы будут потеряны, как уже упоминалось. Однако, если я продолжил выбирать другой элемент, на консоли я увидел TEST 3: chosenItems's size: 3, Очевидно, что chosenItems В списке все еще хранятся мои старые выборки, но они не отображаются на столе.

5 ответов

Это потому что когда SelectionFeature расшифрован новый список создан.

И если table.getRowData(rowKeys[i]) (связано с вашим LazyDataModel реализация) возвращает null, ваши старые варианты выбора на предыдущей странице пропали. можете попытаться решить эту проблему, изменив вашу реализацию LazyDataModel. Я не пробовал этого, но взглянул на это и это

У меня была та же проблема, и я думаю, что это решение проще, если у вас есть много разных таблиц, реализующих LazyDataModel.

Это то, что я сделал: сначала проверьте, не ленивый ли он, затем добавьте выбранные строки в selectionList.

Для простых лиц 4.0

1) Переопределить DataTableRenderer

В лицах-config.xml

<render-kit>
   <renderer>
       <component-family>org.primefaces.component</component-family>
       <renderer-type>org.primefaces.component.DataTableRenderer</renderer-type>
       <renderer-class>com.package.LazyDataTableRenderer</renderer-class>
   </renderer>
</render-kit>

А также

public class LazyDataTableRenderer extends DataTableRenderer {
static Map<DataTableFeatureKey,DataTableFeature> FEATURES;

    static {
        FEATURES = new HashMap<DataTableFeatureKey,DataTableFeature>();
        FEATURES.put(DataTableFeatureKey.DRAGGABLE_COLUMNS, new DraggableColumnsFeature());
        FEATURES.put(DataTableFeatureKey.FILTER, new FilterFeature());
        FEATURES.put(DataTableFeatureKey.PAGE, new PageFeature());
        FEATURES.put(DataTableFeatureKey.SORT, new SortFeature());
        FEATURES.put(DataTableFeatureKey.RESIZABLE_COLUMNS, new ResizableColumnsFeature());
        FEATURES.put(DataTableFeatureKey.SELECT, new LazySelectionFeature());
        FEATURES.put(DataTableFeatureKey.ROW_EDIT, new RowEditFeature());
        FEATURES.put(DataTableFeatureKey.CELL_EDIT, new CellEditFeature());
        FEATURES.put(DataTableFeatureKey.ROW_EXPAND, new RowExpandFeature());
        FEATURES.put(DataTableFeatureKey.SCROLL, new ScrollFeature());
    }

    @Override
    public void decode(FacesContext context, UIComponent component) {
        DataTable table = (DataTable) component;

        for(Iterator<DataTableFeature> it = FEATURES.values().iterator(); it.hasNext();) {
            DataTableFeature feature = it.next();

            if(feature.shouldDecode(context, table)) {
                feature.decode(context, table);
            }
        }

        decodeBehaviors(context, component);        
    }
}

2) Переопределить декодирование SelectionFeature

Обновлено: отредактировано для отмены выбора

public class LazySelectionFeature extends org.primefaces.component.datatable.feature.SelectionFeature{

    @Override
    public void decode(FacesContext context, DataTable table) {
        String clientId = table.getClientId(context);
        Map<String,String> params = context.getExternalContext().getRequestParameterMap();

        String selection = params.get(clientId + "_selection");

        if(table.isSingleSelectionMode())
            decodeSingleSelection(table, selection);
        else
            decodeMultipleSelection(context, table, selection);
    }

    void decodeSingleSelection(DataTable table, String selection) {
            if(ComponentUtils.isValueBlank(selection))
                table.setSelection(null);
            else
                table.setSelection(table.getRowData(selection));
        }

    void decodeMultipleSelection(FacesContext context, DataTable table, String selection) {
        Class<?> clazz = table.getValueExpression("selection").getType(context.getELContext());
        boolean isArray = clazz.isArray();

        if(!isArray && !List.class.isAssignableFrom(clazz)) {
            throw new FacesException("Multiple selection reference must be an Array or a List for datatable " + table.getClientId());
        }

        if(ComponentUtils.isValueBlank(selection)) {
            if(isArray) {
                table.setSelection(Array.newInstance(clazz.getComponentType(), 0));
            }
            else {
                table.setSelection(new ArrayList<Object>());
            }
        }
        else {
            String[] rowKeys = selection.split(",");
            List<Object> selectionList = new ArrayList<Object>();

            boolean lazy=table.isLazy();
            if (lazy) {

            List<String> currentRowKeys = new ArrayList<String>(Arrays.asList(rowKeys));
            if (table.getSelection() != null) {
                List<Object> alreadySelected = (List<Object>) table.getSelection();

                for (Object object : alreadySelected) {//For deselecting
                    Object rowKeyFromModel = table.getRowKeyFromModel(object);
                    if (currentRowKeys.contains(rowKeyFromModel)) {
                        selectionList.add(object);
                        currentRowKeys.remove(rowKeyFromModel);
                    } 
                }                    
            }
            for (String key : currentRowKeys) {//For selecting  
                Object rowData = table.getRowData(key);
                if (rowData != null && !selectionList.contains(rowData)) {
                       selectionList.add(rowData);
                }
            }

        }else{

                for(int i = 0; i < rowKeys.length; i++) {
                    Object rowData = table.getRowData(rowKeys[i]);

                    if(rowData != null)
                        selectionList.add(rowData);
                }

            }

            if(isArray) {
                Object selectionArray = Array.newInstance(clazz.getComponentType(), selectionList.size());
                table.setSelection(selectionList.toArray((Object[]) selectionArray));
            }
            else {
                table.setSelection(selectionList);
            }
        }
    }
}

Может быть не лучшим решением, но должно работать, дайте мне знать, если есть лучший способ. Надеюсь, это кому-нибудь поможет.

В webPage просто добавьте событие, когда страница переключается:

<p:ajax event="page" listener="#{listingBean.updateSelected()}" />

В листинге Bean просто сохраните выбранное:

private List<Entity> selectedInstances;
private List<Entity> selectedInstancesSaved;

public List<Entity> getSelectedInstances()
{
    return selectedInstancesSaved;
}

public void setSelectedInstances(List<Entity> selectedInstances)
{
    this.selectedInstances = selectedInstances;
}

public void updateSelected()
{
    if (selectedInstances != null && !selectedInstances.isEmpty()) {
        for (Entity inst : lazyModel.getDatasource()) {
            if (selectedInstances.contains(inst)) {
                selectedInstancesSaved.add( inst);
            } else {
                selectedInstancesSaved.remove( inst);
            }
        }
    }
}

Хотя решение Bruno работает для сохранения выбора между нумерацией страниц, оно не учитывает сохранение выбора на отдельной странице (то есть, когда никогда не меняются страницы).

Эту проблему можно решить проще, используя события ajax rowSelectCheckbox и rowUnselectCheckbox, в дополнение к наличию отдельного "сохраненного" списка строк.

JSF:

 <p:dataTable selection="#{myBean.selectedRows}" ... >
   <p:ajax event="rowSelectCheckbox" process="@this" listener="#{myBean.onSelectRow}" />
   <p:ajax event="rowUnselectCheckbox" process="@this" listener="#{myBean.onUnselectRow}" />
   <p:column selectionMode="multiple" ... />
    ...
 </p:dataTable>

Бэк бин:

    private List<MyRowClass> selectedRows;
    private List<MyRowClass> selectedRowsSaved;

    ...

    public void onSelectRow(SelectEvent event){
        MyRowClass row = (MyRowClass) event.getObject();
        selectedRowsSaved.add(row);
    }

    public void onUnselectRow(UnselectEvent event){
        MyRowClass row = (MyRowClass) event.getObject();
        selectedRowsSaved.remove(row);
    }

    public List<MyRowClass> getSelectedRows(){
        return selectedRowsSaved;
    }

    public void setSelectedRows(List<MyRowClass> selectedRows){
        this.selectedRows = selectedRows;
    }

Таким образом, список сохраненных строк всегда обновляется, не требуя ajax-события "page".

Просто реализуйте свойство, привязанное к свойству выбора объекта DataTable (selection="#{pageBackingForm.selectedEntityList}") вот так и будет работать

    private Map<Integer, List<Entity>> selectedEntityListMap = new Hashtable<>();

    public List<Entity> getSelectedEntityList() {
        return selectedEntityListMap.get(getCurrentEntitySelectionPage());
    }

    public void setSelectedEntityList(List<Entity> selectedEntityList) {
        if (selectedEntityList == null) {
            selectedEntityListMap.remove(getCurrentEntitySelectionPage());
            return;
        }

        selectedEntityListMap.put(getCurrentEntitySelectionPage(), selectedEntityList);
    }

    public Integer getCurrentEntitySelectionPage() {
        DataTable dataTable = (DataTable) FacesContext.getCurrentInstance().getViewRoot().findComponent("formId:dataTableId");
        return dataTable.getPage();
    }

У меня была такая же проблема с моей таблицей данных. Хотя мой случай немного отличается, потому что я использую вместо этого selectBooleanCheckbox. Я нашел простое решение, которое работает для меня. Меня поразило, когда вы сказали, что "старые выборки не отображаются в таблице".

  • привязать флажок к событию поддержки a4j:

код:

<h:selectBooleanCheckbox value="#{batch.toPortfolio}">
    <a4j:support event="onchange" />
</h:selectBooleanCheckbox>
Другие вопросы по тегам