1

I want to allow users to drag and drop items in a table to easily reorganize. I am running into two problems. The first is that, when I add an ondragstart value to my tag, I get a slightly delayed flicker roughly one second after I start dragging. If there is a known way to fix this, that would be appreciated. The main question though, is how to assign variables from the ondragstart event.

Because it's kind of confusing, my code contains 4 lists. Two of them are basically functioning in place of a map where one is a list of keys (note headers) and the other is a list of values (the actual notes). I then have a third list that counts from 0 for each item in the first 'key' list (and then use this in the first repeat to figure out how many times to create a separate table header for each section of notes). My fourth list runs side-by-side with the notes list and contains the number of times to indent each note.

Here is my current relevant Controller code:

//this is being called from my ondragstart event, but seems to be what is not working
public void MoveVars() {
    draggedNoteSection = Integer.valueOf(selectedNoteIndex);
    draggedNote = Integer.valueOf(selectedNoteRowIndex);
}

//This is being called from the ondrop event and seems to be working more reliably
public void MoveNote() {
    if (draggedNote > Integer.valueOf(selectedNoteRowIndex)){
        lstNotes[Integer.valueOf(selectedNoteIndex)].parsedIndents.add(Integer.valueOf(selectedNoteRowIndex), lstNotes[draggedNoteSection].parsedIndents[draggedNote]);
        lstNotes[Integer.valueOf(selectedNoteIndex)].parsedNotes.add(Integer.valueOf(selectedNoteRowIndex), lstNotes[draggedNoteSection].parsedNotes[draggedNote]);
        draggedNote++;
        lstNotes[draggedNoteSection].parsedIndents.remove(draggedNote);
        lstNotes[draggedNoteSection].parsedNotes.remove(draggedNote);
    } else {
        lstNotes[Integer.valueOf(selectedNoteIndex)].parsedIndents.add(Integer.valueOf(selectedNoteRowIndex)+1, lstNotes[draggedNoteSection].parsedIndents[draggedNote]);
        lstNotes[Integer.valueOf(selectedNoteIndex)].parsedNotes.add(Integer.valueOf(selectedNoteRowIndex)+1, lstNotes[draggedNoteSection].parsedNotes[draggedNote]);
        lstNotes[draggedNoteSection].parsedIndents.remove(draggedNote);
        lstNotes[draggedNoteSection].parsedNotes.remove(draggedNote);
    }

    lstNotes[Integer.valueOf(selectedNoteIndex)].parsedNotesCount.clear();
    for (Integer i = 0; i < lstNotes[Integer.valueOf(selectedNoteIndex)].parsedNotes.size(); i++){
        lstNotes[Integer.valueOf(selectedNoteIndex)].parsedNotesCount.add(i);
    }

}

From Visualforce (deprecated extra columns):

//In theory, the selectedNoteIndex is the 'key' value and the selectedNoteRowIndex is the note
<apex:actionFunction name="DraggedVars" action="{!MoveVars}" >
        <apex:param name="rowToBeMoved" value="" assignTo="{!selectedNoteRowIndex}"></apex:param>
        <apex:param name="noteToBeMoved" value="" assignTo="{!selectedNoteIndex}"></apex:param>
    </apex:actionFunction>
    <apex:actionFunction name="MoveRows" action="{!MoveNote}" rerender="NoteRepeater" onComplete="onReRender();">
        <apex:param name="rowToBeMovedTo" value="" assignTo="{!selectedNoteRowIndex}"></apex:param>
        <apex:param name="noteToBeMovedTo" value="" assignTo="{!selectedNoteIndex}"></apex:param>
    </apex:actionFunction>
    <apex:repeat value="{!lstCount}" var="key3">
        <apex:repeat value="{!lstNotes[key3]}" var="value3">
            <table>
                <tr><th></th></tr>
                <apex:repeat value="{!value3.parsedNotesCount}" var="c1">
                    <tr draggable="True" ondragstart="DraggedVars('{!c1}', '{!key3}');" ondrop="MoveRows('{!c1}', '{!key3}');" ondragover="allowDrop(event)"><td></td></tr>

Javascript:

function allowDrop(ev) {
    ev.preventDefault();
}

Third question actually (I haven't looked for this answer yet, so feel free to ignore): is there a way to enforce the ondragstart function happening prior to the ondrop function if they are done in very quick succession? The ondragstart seems to take a second to register so I'm wondering if it sometimes comes in later.

To reiterate, right now the MoveVars from the ondragstart seem to be the issue in that they are not being correctly set to the variables of the row that is being dragged. Let me know if you think I'm missing some section of code that might shed some light on this. Thanks in advance for any help!

1

Don't bother updating the view state on drag start. It simply causes heartache (or heartburn, whichever).

Instead, simply store the information from the start event through to the end. I actually wrote some code in haxe that was compiled to JavaScript for use in a project, which I'll include here:

package fear.brian.dnd.ui;

import fear.brian.dnd.api.VisualforceController;
import fear.brian.dnd.data.*;
import fear.brian.dnd.events.VisualforceRemotingEvent;
import fear.brian.dnd.responses.ReorderResponse;
import js.Browser;
import js.html.*;

class UiDragDropWrapper
{
    public function new(target:DivElement)
    {
        target.addEventListener("dragstart", dragStart);
        target.addEventListener("dragover", dragAccept);
        target.addEventListener("dragenter", dragAccept);
        target.addEventListener("drop", drop);
    }

    public function dragAccept(event:DragEvent):Bool
    {
        event.preventDefault();
        event.dataTransfer.dropEffect = "move";
        return false;
    }

    public function dragStart(event:DragEvent):Bool
    {
        var target:Element = cast(event.target, Element);
        event.dataTransfer.setData("Text", target.dataset.recordId);
        event.dataTransfer.effectAllowed = "move";
        if (event.dataTransfer.setDragImage != null) {
            event.dataTransfer.setDragImage(target.querySelector(".thumbnail"), 0, 0);
        };
        return false;
    }

    public function drop(event:DragEvent):Null<Bool>
    {
        if (event.dataTransfer == null || event.dataTransfer.files == null || event.dataTransfer.files.length == 0) {
            var target:Element = cast(event.target, Element);
            VisualforceController.reorganize([SystemData.parentId, event.dataTransfer.getData("Text"), target.dataset.recordId], handleReorderResponse);
            event.preventDefault();
            event.stopPropagation();
            return false;
        };
        return null;
    }

    public function handleReorderResponse(data:ReorderResponse, event:VisualforceRemotingEvent):Void
    {
        Main.reorderableList.redraw(data.values);
    }
}

There's a lot more "uninteresting" code from a drag and drop point of view, but here's the gist:

In UiDragDropWrapper.new, we're assigning dragstart, dragover, dragenter, and dragdrop events.

In the dragover and dragenter events, we "accept" the drop.

In the dragstart event, we track the recordId of the source element, set the effect to "move", and set a drag image for everything but IE.

In the drop event, we call the VisualforceController asynchronously, and we'll be returned into handleReorderResponse, where we finally choose to redraw the screen (note: you can skip this step if you bind to an actionFunction with reRender instead, but I chose not to do this).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.