Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tree: DnD Using onDrop controlled mode results in wrong child order #11513

Open
RohdeSchwarz-SDC opened this issue Feb 23, 2024 · 14 comments · Fixed by #11515
Open

Tree: DnD Using onDrop controlled mode results in wrong child order #11513

RohdeSchwarz-SDC opened this issue Feb 23, 2024 · 14 comments · Fixed by #11515
Labels
🐞 defect Bug...Something isn't working workaround A workaround has been provided

Comments

@RohdeSchwarz-SDC
Copy link

RohdeSchwarz-SDC commented Feb 23, 2024

Describe the bug

The drag & drop feature of the tree component works fine, unless we have the following constellation:

Conditions:

  • using onDrop controlled mode instead of dropRestrict="sibling".
  • ajax event dragdrop with a listener but without update attribute and
  • the dragged item is dropped on the same level (reorder only) and
  • the dragged item is moved downwards (but not below the last item)

Scenario:
Let there be three items [A, B, C]. To reproduce the bug you must move A one step down between B and C. When the "dragdrop" listener is called, we get a wrong child order:

  • Actual [B, C, A],
  • Expected [B, A, C]

Reproducer

PrimeFaces-Reproducer-11513.zip

Expected behavior

(see above)

PrimeFaces edition

Community

PrimeFaces version

13

Theme

irrelevant

JSF implementation

Mojarra

JSF version

4.0

Java version

21

Browser(s)

irrelevant

@RohdeSchwarz-SDC RohdeSchwarz-SDC added ‼️ needs-triage Issue needs triaging 🐞 defect Bug...Something isn't working labels Feb 23, 2024
@melloware
Copy link
Member

Weird i can't reproduce:
pf-11513.zip

Run mvn clean jetty:run and navigate to localhost:8080

image

@melloware
Copy link
Member

Nevermind I see its the backend printout that is wrong.

@melloware
Copy link
Member

Got to the bottom of it. looks like this has been incorrect for a LONG time...

@melloware

This comment was marked as outdated.

@melloware melloware added the workaround A workaround has been provided label Feb 23, 2024
@RohdeSchwarz-SDC
Copy link
Author

RohdeSchwarz-SDC commented Feb 23, 2024

Excellent! This workaround fixes the symptoms 👍

@melloware
Copy link
Member

Great this will be in 13.0.7

@RohdeSchwarz-SDC
Copy link
Author

Hang on. Sorry for the premature confirmation.

We might get in trouble with another cornercase this time:

PostWorkaround-CornerCase-1

@melloware
Copy link
Member

let me look its probably the reverse problem

@melloware
Copy link
Member

hmm that is not happening for me.

[TreeBean::init......] root.children = [A, B, C]
[TreeBean::onDragDrop] root.children = [A, C, B]

@melloware
Copy link
Member

oh no i see.

@RohdeSchwarz-SDC
Copy link
Author

It is very subtle indeed. I also had to look thrice.

@melloware
Copy link
Member

melloware commented Feb 23, 2024

Try this patch. It even fixes an issue where the AJAX event should not even be sent if the node is not being moved like your scenario above.

if (PrimeFaces.widget.VerticalTree) {
    PrimeFaces.widget.VerticalTree.prototype.makeDropPoints = function(elements) {
        var $this = this,
            dragdropScope = this.cfg.dragdropScope || this.id;

        elements.droppable({
            hoverClass: 'ui-state-hover',
            accept: '.ui-treenode-content',
            tolerance: 'pointer',
            scope: dragdropScope,
            drop: function(event, ui) {
                var dragSource = PF($(ui.draggable.data('dragsourceid')).data('widget')),
                    dropSource = $this,
                    dropPoint = $(this),
                    dropNode = dropPoint.closest('li.ui-treenode-parent'),
                    dropNodeKey = $this.getRowKey(dropNode),
                    transfer = (dragSource.id !== dropSource.id),
                    draggedSourceKeys = dragSource.draggedSourceKeys,
                    isDroppedNodeCopy = ($this.cfg.dropCopyNode && $this.shiftKey),
                    draggedNodes,
                    dragNodeKey;

                if (draggedSourceKeys) {
                    draggedNodes = dragSource.findNodes(draggedSourceKeys);
                } else {
                    draggedNodes = [ui.draggable];
                }

                if ($this.cfg.controlled) {
                    $this.droppedNodeParams = [];
                }

                $this.invalidSourceKeys = [];

                for (var i = (draggedNodes.length - 1); i >= 0; i--) {
                    var draggedNode = $(draggedNodes[i]),
                        dragMode = ui.draggable.data('dragmode'),
                        dragNode = draggedNode.is('li.ui-treenode') ? draggedNode : draggedNode.closest('li.ui-treenode'),
                        dragNode = (isDroppedNodeCopy) ? dragNode.clone() : dragNode,
                        targetDragNode = $this.findTargetDragNode(dragNode, dragMode);

                    dragNodeKey = $this.getRowKey(targetDragNode);

                    if (!transfer && dropNodeKey && dropNodeKey.indexOf(dragNodeKey + '_') === 0) {
                        return;
                    }

                    if ($this.cfg.controlled) {
                        $this.droppedNodeParams.push({
                            'ui': ui,
                            'dragSource': dragSource,
                            'dragNode': dragNode,
                            'targetDragNode': targetDragNode,
                            'dropPoint': dropPoint,
                            'dropNode': dropNode,
                            'transfer': transfer
                        });
                    } else {
                        $this.onDropPoint(ui, dragSource, dragNode, targetDragNode, dropPoint, dropNode, transfer);
                    }
                }

                if (!draggedSourceKeys) {
                    draggedSourceKeys = [dragNodeKey];
                }
                draggedSourceKeys = draggedSourceKeys.filter(function(key) {
                    return $.inArray(key, $this.invalidSourceKeys) === -1;
                });

                // #11513 dndIndex is based on whether you are dragging up or down
                var dndIndex = dropPoint.prevAll('li.ui-treenode').length;

                if ($this.cfg.controlled) {
                    var isSameParent = dragNodeKey && dragNodeKey.startsWith(dropNodeKey);
                    var draggedIndex = parseInt(dragNodeKey.split('_').pop());

                    if (isNaN(draggedIndex)) {
                        draggedIndex = parseInt(dragNodeKey);
                    }

                    // #11520 if its the same parent but you are not moving the node to a new area it can be dropped
                    if (isSameParent && (draggedIndex === dndIndex || (Math.abs(draggedIndex - dndIndex) === 1 && draggedIndex + 1 === dndIndex))) {
                        draggedSourceKeys = null; // do nothing
                    } else if (draggedIndex < dndIndex) {
                        var nextDndIndex = dropPoint.nextAll('li.ui-treenode').length;
                        if (nextDndIndex > 0) {
                            dndIndex = nextDndIndex;
                        }
                    }
                }

                if (draggedSourceKeys && draggedSourceKeys.length) {
                    draggedSourceKeys = draggedSourceKeys.reverse().join(',');
                    $this.fireDragDropEvent({
                        'dragNodeKey': draggedSourceKeys,
                        'dropNodeKey': dropNodeKey,
                        'dragSource': dragSource.id,
                        'dndIndex': dndIndex,
                        'transfer': transfer,
                        'isDroppedNodeCopy': isDroppedNodeCopy
                    });
                }

                dragSource.draggedSourceKeys = null;
                $this.invalidSourceKeys = null;

                if (isDroppedNodeCopy) {
                    $this.initDraggable();
                }
            }
        });
    }
}

@melloware
Copy link
Member

pf-11513.zip

I removed onDrop and added dropRestrict="sibling" and this appears to be working as expected.

@melloware melloware removed this from the 13.0.7 milestone Feb 26, 2024
@melloware melloware reopened this Feb 26, 2024
@melloware
Copy link
Member

This issue is in controlled mode using onDrop it thinks you are in more control of how the drops are handled which is out of the normal course of action. So I think the issue really is the controlled mode really fully baked.

melloware added a commit that referenced this issue Feb 26, 2024
melloware added a commit that referenced this issue Feb 26, 2024
@melloware melloware changed the title Tree: Drag&Drop EventListener Without Update-Attribute Results in Wrong Child Order Tree: Drag&Drop Using onDrop controlled mode results in wrong child order Feb 26, 2024
@melloware melloware changed the title Tree: Drag&Drop Using onDrop controlled mode results in wrong child order Tree: DnD Using onDrop controlled mode results in wrong child order Feb 26, 2024
@melloware melloware removed their assignment Feb 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐞 defect Bug...Something isn't working workaround A workaround has been provided
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants