primefaces: Datatable: Reorder with RowExpansion doesn't work for expanded rows

Describe the defect I have a p:dataTable with draggableRows=“true”, a p:rowToggler column and a p:rowExpansion. When I expand one of the columns and then drag it to the last position, only the table row (without the expansion) gets dropped. The expanded row stays at the top. Also, the rowReorder event doesn’t get fired.

Works: 1 2

Doesn’t work: 3 4

Reproducer I have attached a reproducer: primefaces-test-masterReproducer.zip

Environment:

  • PF Version: master-SNAPSHOT (9.0), 8.0
  • JSF + version: Mojarra 2.2.20
  • Affected browsers: Edge Version 87.0.664.47 (Offizielles Build) (64-Bit), Firefox 83.0 (64-bit), Chrome 87.0.4280.66 (Official Build) (64-bit)
  • Application Server: Jetty 9.4.35.v20201120

To Reproduce Steps to reproduce the behavior:

  1. Expand the row with the index 0
  2. Drag this row to the last position

Expected behavior

  • The row and its expansion are moved to the last position of the datatable
  • A growl message “Row Moved From:0, To:2” should appear

Example XHTML

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:p="http://primefaces.org/ui"
      xmlns:h="http://java.sun.com/jsf/html">

    <h:head>
        <title>PrimeFaces Test</title>
        <h:outputScript name="test.js" />
    </h:head>
    <h:body>

        <h1>#{testView.testString}</h1>
        <h:form id="frmTest">
            <p:growl id="msgs" showDetail="true" skipDetailIfEqualsSummary="true" />
     			 
			<p:dataTable var="str" value="#{dtReorderView.strings}" draggableRows="true">
				<p:ajax event="rowReorder" listener="#{dtReorderView.onRowReorder}" update=":frmTest:msgs" />
				<f:facet name="header">
					Draggable Rows
				</f:facet>
				
				<p:column style="width:16px">
					<p:rowToggler />
				</p:column>
		
				<p:column headerText="Index">
					<h:outputText value="#{dtReorderView.strings.indexOf(str)}" />
				</p:column>
				
				<p:column headerText="String">
					<h:outputText value="#{str}" />
				</p:column>
		 
				<p:column headerText="Hash">
					<h:outputText value="#{car.hashCode()}" />
				</p:column>
				
				<p:rowExpansion>
					<p:panelGrid columns="2" columnClasses="label,value" style="width:300px">
		 
						<h:outputText value="String:" />
						<h:outputText value="#{str}" />
		 
						<h:outputText value="Hashcode" />
						<h:outputText value="#{str.hashCode()}" />
					</p:panelGrid>
				</p:rowExpansion>

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

    </h:body>
</html>

Example Bean

/*
 * Copyright 2009-2014 PrimeTek.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.primefaces.test;

import org.primefaces.event.ReorderEvent;

import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Named;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Named("dtReorderView")
@RequestScoped
public class ReorderView implements Serializable {
    
    private List<String> strings;
    
  
    @PostConstruct
    public void init() {
		strings = new ArrayList<>();
        strings.add("Primefaces rocks");
		strings.add("Primefaces really rocks");
		strings.add("!!!");
    }

    public List<String> getStrings() {
        return strings;
    }
    
    public void onRowReorder(ReorderEvent event) {
        FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Row Moved", "From: " + event.getFromIndex() + ", To:" + event.getToIndex());
        FacesContext.getCurrentInstance().addMessage(null, msg);
    }
}

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 21 (21 by maintainers)

Commits related to this issue

Most upvoted comments

OK can you try this version I made it a little more jQuery friendly.

update: function(event, ui) {
                var fromIndex = ui.item.data('ri'),
                fromNode = ui.item;
                itemIndex = ui.item.index(),
                toIndex = $this.paginator ? $this.paginator.getFirst() + itemIndex : itemIndex;
                isDirectionUp = fromIndex >= toIndex;

                // #5296 must not count header group rows
                // #6557 must not count expanded rows
                if (isDirectionUp) {
                    for (i = 0; i <= toIndex; i++) {
                        fromNode = fromNode.next('tr');
                        if (fromNode.hasClass('ui-rowgroup-header') || fromNode.hasClass('ui-expanded-row-content')){
                            toIndex--;
                        }
                    }
                } else {
                    fromNode.prevAll('tr').each(function() {
                        var node = $(this);
                        if (node.hasClass('ui-rowgroup-header') || node.hasClass('ui-expanded-row-content')){
                            toIndex--;
                        }
                    });
                }
                toIndex = Math.max(toIndex, 0);
                console.log("FromIndex = " + fromIndex + " ToIndex = " + toIndex);

Nice work let me see if I can Jquery this a little bit.

ahh good point. let me keep poking.

Awesome let me investigate and thanks for testing so far!

Thanks, melloware! I can test your changes on monday at the earlist, but I will let you know how it worked for our application.

Well I fixed the JS stacktrace error. But this problem is hard becuase the row expanders create their own <tr/> rows and those are not brought along in the draggable. So the row expanders are detached from their parent row.

Yep that is the hard part. I am still working on some kind of way to make it work.

There are definitely multiple issues here. But one of the issues is because this introduces new rows when you have it expanded the indexes are off. That can be fixed with this in makeRowsDraggable

            update: function(event, ui) {
                var fromIndex = ui.item.data('ri'),
                itemIndex = ui.item.index();
                
                // #6557 must not count expanded rows
                if ($this.cfg.rowExpandMode) {
                   var expandedRows = $this.tbody.children('.ui-expanded-row-content').length;
                   itemIndex = itemIndex - expandedRows;
                }
                
                var toIndex = $this.paginator ? $this.paginator.getFirst() + itemIndex: itemIndex;

That fixes the issue of which row was dragged properly and makes the AJAX fire but that doesn’t fix the real problem of closing the original row’s expander and having the dragged row show as un-expanded.