Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to rollback nodes that couldn't be moved in jstree

I'm trying to figure out how to rollback only a folder node that wasn't successfully moved. The code below is an example of what I'm trying to do. The problem comes when you have selected a couple of folders and moved them into another folder. If one of the directories fails to be moved I want to be able to roll it back to it's original parent. Unfortunately $.jstree.rollback(data.rlbk); rollsback all of the folders that were selected to their previous locations.

$("#tree").jstree({...}).bind("move_node.jstree", function (e, data) {
    // process all selected nodes directory
    data.rslt.o.each(function (i) {
         // Send request.
         var move = $.parseJSON($.ajax({
             url: "./jstree.php",
             type: 'post',
             async: false,
             data: {
                 operation:  "move_dir",
                 ....
             }
         }).responseText);
         // When everything's ok, the reponseText will be {success: true}
         // In all other cases it won't exist at all.
         if(move.success == undefined){
             // Here I want to rollback the CURRENT failed node.
             // $.jstree.rollback(data.rlbk); will rollback all 
             // of the directories that have been moved.
         }
    }
});

Is there a way for this to be done?

like image 202
tftd Avatar asked Feb 02 '12 23:02

tftd


2 Answers

I've looked at using jstree before, but haven't used it in my code. As a result, the code may not be correct, but the concepts should be.

Based on your code, it appears that you're performing the move operation on the server side and you want the tree to be updated to reflect the results.

Based on the jsTree documentation, it looks as though you cannot commit node updates and roll back to the last commit.

Instead of rolling back only the changes that you don't want, you can roll back the tree (all changes) and perform the moves afterward.

In order to better understand the code below, you may want to read it (or create a copy) without the lines where "wasTriggeredByCode" is set or referenced in the condition for an "if" statement.

$("#tree").jstree({...}).bind("move_node.jstree", function (e, data) {

    var jsTree = $(this);
    var successes = [];

    // Becomes true when function was triggered by code that updates jsTree to
    //  reflect nodes that were successfully moved on the server
    var wasTriggeredByCode = false;

    // process all selected nodes directory
    data.rslt.o.each(function (i) {

         // I'm not certain that this is how the node is referenced
         var node = $(this);

         wasTriggeredByCode = (wasTriggeredByCode || node.data('redoing'));

         // Don't perform server changes when event was triggered from code
         if (wasTriggeredByCode) {
             return;
         }

         // Send request.
         var move = $.parseJSON($.ajax({
             url: "./jstree.php",
             type: 'post',
             async: false,
             data: {
                 operation:  "move_dir",
                 ....
             }
         }).responseText);

         if(move.success){
             successes.push(node);
         }
    });

    // Don't continue when event was triggered from code
    if (wasTriggeredByCode) {
         return;
    }

    // Roll back the tree here
    jsTree.rollback(data.rlbk);

    // Move the nodes
    for (var i=0; i < successes.length; i++) {
        var node = successes[i];

        // According to the documentation this will trigger the move event,
        //  which will result in infinite recursion. To avoid this you'll need
        //  to set a flag or indicate that you're redoing the move.
        node.data('redoing', true);

        jsTree.move_node(node, ...);

        // Remove the flag so that additional moves aren't ignored
        node.removeData('redoing');
    }
});
like image 55
Dave F Avatar answered Oct 19 '22 22:10

Dave F


I thought about having something like "onbeforenodemove" event in jstree, something like this:

$("#tree").jstree({...}).bind("before_move_node.jstree", function (e, data) {...}

So I looked inside jstree.js file (version jsTree 3.1.1) and searched for declaration of original "move_node.jstree" handler. It found it declared starting line 3689:

move_node: function (obj, par, pos, callback, is_loaded, skip_redraw, origin) {...}

This function contains the following line at the end of its body:

this.trigger('move_node', { "node" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_pos, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });

The above line actually calls your callback declared using .bind("move_node.jstree"). So at the beginning of this function body, I added this:

var before_data = { "node": obj, "parent": new_par.id, "position": pos, "old_parent": old_par, "old_position": old_pos, 'is_multi': (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign': (!old_ins || !old_ins._id), 'old_instance': old_ins, 'new_instance': this, cancelled: false };
this.trigger('before_move_node', before_data);
if (before_data.cancelled) {
    return false;
}

Mind "cancelled": false at the end of before_data assigned value.

Also mind inserting the above after new_par, etc. values are assigned.

Code (jsTree instantiation) on my page looks now like this:

$('#tree')
    .jstree({
        core: {...},
        plugins: [...]
    })
    .bind('before_move_node.jstree', function (e, data) {
        if (...) {
            data.cancelled = true;
        }
    })

data object passed to 'before_move_node.jstree' contains the same values that you receive in standard 'move_node.jstree' data argument so you have everything to decide whether you want to cancel the move or let it go. If you decide to cancel, just set the additional 'cancelled' property to true. The entire move will then not happen.

like image 23
Martin Avatar answered Oct 19 '22 21:10

Martin