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

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

Это мой тест bean:

@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 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") к указанному выше коду, я заметил следующие вещи:

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

Ответ 1

Это происходит потому, что при декодировании SelectionFeature создается новый список.

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

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

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

Для праймеров 4.0

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

В faces-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) Override SelectionFeature decode

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

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

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

Ответ 2

Просто реализуйте свойство, привязанное к свойству выделения 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();
    }

Ответ 3

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

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

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

private List<GInstance> selectedInstances;
private List<GInstance> selectedInstancesSaved;

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

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

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

Ответ 4

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

  • установите флажок с a4j: событие поддержки

код:

<h:selectBooleanCheckbox value="#{batch.toPortfolio}">
    <a4j:support event="onchange" />
</h:selectBooleanCheckbox>