antonu17
antonu17

Reputation: 573

Reordering p:dataTable rows containing inputs

There is p:dataTable with p:inputText in a column:

<h:form id="form">
    <p:dataTable id="dataTable" value="#{rowReorder.dataList}" 
                 var="row" draggableRows="true" rowKey="#{row.id}">
        <p:ajax event="rowReorder" listener="#{rowReorder.reorder}" update="dataTable"/>

        <p:column>
            <f:facet name="header">
                <p:commandButton value="Add" actionListener="#{rowReorder.addData}" 
                                 update="dataTable" process="dataTable"/>
            </f:facet>
            <p:outputLabel value="#{row.id}"/>
        </p:column>
        <p:column>
            <p:inputText value="#{row.name}"/>
        </p:column>
    </p:dataTable>
</h:form>

Backing bean:

import org.omnifaces.cdi.ViewScoped;
import org.primefaces.event.ReorderEvent;
import javax.inject.Named;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;

@Named("rowReorder")
@ViewScoped
public class RowReorder implements Serializable {

    private List<Data> dataList = new LinkedList<>();

    public void addData() {
        Data data = new Data();
        data.setId(dataList.size() + 1);
        data.setName("Data " + data.getId());
        dataList.add(data);
    }

    public void reorder(ReorderEvent event) {

    }

    /**
     * Getters, Setters
     */

    public List<Data> getDataList() {
        return dataList;
    }
}

Data class:

public class Data implements Serializable {

    private Integer id;
    private String name;

    /**
     * Getters, Setters
     */

}

Sample datatable before reordering:

--------------
|id |  name  |
--------------
| 1 | Data 1 |
| 2 | Data 2 |
| 3 | Data 3 |
| 4 | Data 4 |
--------------

and after reordering (moving 1-st row to 3-rd):

--------------
|id |  name  |
--------------
| 2 | Data 1 |
| 3 | Data 2 |
| 1 | Data 3 |
| 4 | Data 4 |
--------------

I understand that it is happening 'cause of setting data from p:inputText's at UPDATE_MODEL phase. I tried to prevent processing of input fields by specifying process="@none" in p:ajax component, but it doesn't work. Have any idea how to make draggableRows and p:inputText friends?

Upvotes: 7

Views: 6384

Answers (5)

jansohn
jansohn

Reputation: 2326

The issue has been finally fixed with PrimeFaces 14.0.12+ (https://github.com/primefaces/primefaces/issues/13310).

For the (optional) reorder ajax event this is enough:

<p:ajax
  event="rowReorder"
  listener="#{testView.onRowReorder}"
  update="@this" />

No more workarounds needed for now...

Upvotes: 0

Robert
Robert

Reputation: 260

I've got the same problem using Primefaces 12.

Saddly the solutions above doesn't work for me, so i've to find another option:

Primefaces 12 offers the attribute draggableRowsFunction on the p:datatable element. If set, the default behavior described by antonu17 in his second solution is disabled. (see org.primefaces.component.datatable.feature.DraggableRowsFeature.decode(...))

This means the submit is processed normaly, including the correct model update in UPDATE_MODEL_VALUES-Phase. After that you can use the ajax event "rowReorder" with a listener method to get the ReorderEvent-Object with from/to index and shift the order in the list yourself.

That worked for me.

(Primefaces 7 worked fine with the first solution of antonu17)

Upvotes: 2

Jasper de Vries
Jasper de Vries

Reputation: 20253

Other simple solution is to disable the inputs on the start of rowReorder:

<p:ajax event="rowReorder"
        onstart="$(':input', PrimeFaces.escapeClientId('#{component.clientId}')).prop('disabled',true)"
        update="@this"/>

Note that #{component.clientId} will return the client ID of the data table.

To avoid loosing data, you can Ajaxify the inputs:

<p:column headerText="#{msg.value}">
  <p:inputText value="#{item.value}">
    <p:ajax/>
  </p:inputText>
</p:column>

Upvotes: 0

Marcelo Rebou&#231;as
Marcelo Rebou&#231;as

Reputation: 699

The secret is the attribute rowStatePreserved of your datatable, add it:

rowStatePreserved="true"

 <p:dataTable id="dataTable" value="#{rowReorder.dataList}" 
              var="row" draggableRows="true" rowKey="#{row.id}" 
              rowStatePreserved="true">

And keep this code as like:

<p:ajax event="rowReorder" listener="#{rowReorder.reorder}" update="dataTable" process="@this"/>

In my case i was used a combobox into datatable column, and after i added this atrribute the value do not change more than one row to another when i used draggableRows function.

I wait help you.

Upvotes: 3

antonu17
antonu17

Reputation: 573

First solution

I found a solution! It does not processing inputs (and actually does not submit it at all) with attributes process="@none" partialSubmit="true"

So complete p:ajax component looks like <p:ajax event="rowReorder" listener="#{rowReorder.reorder}" update="dataTable" process="@none" partialSubmit="true"/>


Second solution (if submitted data is needed)

Theory:

Lets check out what is happening on dragging row? We have ajax request forcing process="form:dataTable". On APPLY_REQUEST_VALUES phase DataTableRenderer tries to invoke decode of DraggableRowsFeature which, in turn, rotating list elements (list that specified as dataTable's value attribute). So on UPDATE_MODEL_VALUES phase we have a rotated list, and input components, which wants to submit their values to name fields of Data objects. But request parameters still contains old row indexes in input ids: they are form:dataTable:1:name = Data 2, form:dataTable:2:name = Data 3, form:dataTable:0:name = Data 1 (i added 3 rows, and moved first row to last). So here we getting what we got. In this way if we need data to be submitted on right places we have to prevent our list rotating before UPDATE_MODEL_VALUES is done, and perform this rotation later on INVOKE_APPLICATION phase, and render dataTable on that ajax request:

In DraggableRowsFeature.decode() we can see that Collections.rotate() is calling only when value is instance of List.

    if (value instanceof List) {
        List list = (List) value;

        if(toIndex >= fromIndex) {
            Collections.rotate(list.subList(fromIndex, toIndex + 1), -1);
        }
        else {
            Collections.rotate(list.subList(toIndex, fromIndex + 1), 1);
        }            
    } 
    else {
        LOGGER.info("Row reordering is only available for list backed datatables, use rowReorder ajax behavior with listener for manual handling of model update.");
    }     

Also there is DraggableRowsFeature.shouldDecode() method.

public boolean shouldDecode(FacesContext context, DataTable table) {
    return context.getExternalContext().getRequestParameterMap().containsKey(table.getClientId(context) + "_rowreorder");
}

So here we have 2 possibilities to prevent datasource rotating:

  1. Don't use List as dataTable value
  2. Create own org.primefaces.component.datatable.feature.DraggableRowsFeature returning false in shouldDecode() method.

Practice:

I modified bean file like this:

@Named("rowReorder")
@ViewScoped
public class RowReorder implements Serializable {

    private static final Logger log = LoggerFactory.getLogger(RowReorder.class);
    private Set<Data> dataList = new LinkedHashSet<>();


    public void addData() {
        Data data = new Data();
        data.setId(dataList.size() + 1);
        data.setName("Data " + data.getId());
        dataList.add(data);
        log.warn("{} {}", Integer.toHexString(data.hashCode()), data.getId());
    }

    public void removeData(Data data) {
        dataList.remove(data);
    }

    public void reorder(ReorderEvent event) {
        List<Data> list = new LinkedList<>(dataList);

        int fromIndex = event.getFromIndex();
        int toIndex = event.getToIndex();
        if(toIndex >= fromIndex) {
            Collections.rotate(list.subList(fromIndex, toIndex + 1), -1);
        }
        else {
            Collections.rotate(list.subList(toIndex, fromIndex + 1), 1);
        }
        dataList.clear();
        dataList.addAll(list);
    }

    /**
     * Getters, Setters
     */

    public Set<Data> getDataList() {
        return dataList;
    }
}

And now it firstly submitting values to model and rotating list on INVOKE_APPLICATION phase.

Upvotes: 13

Related Questions