简体   繁体   中英

Undo & Redo function not working as expect

I'm trying to create an undo/redo function for jQueryUI .draggable at the moment I have a function but doesn't work as expect, here is my funcion:

 $(document).ready(function() { $('.editable').draggable({ stop: stopHandlerDrag, start: startHandlerDrag }); }); var historyApp = { stackStyle: [], stackId: [], counter: -1, add: function(style, id) { ++this.counter; this.stackStyle[this.counter] = style; this.stackId[this.counter] = id; this.doSomethingWith(style, id); // delete anything forward of the counter this.stackStyle.splice(this.counter + 1); }, undo: function() { --this.counter; this.doSomethingWith(this.stackStyle[this.counter], this.stackId[this.counter]); }, redo: function() { ++this.counter; this.doSomethingWith(this.stackStyle[this.counter], this.stackId[this.counter]); }, doSomethingWith: function(style, id) { //Check if make buttons undo/redo disabled or enabled if (this.counter <= -1) { $('#undo').addClass('disabled'); $('#redo').removeClass('disabled'); return; } else { $('#undo').removeClass('disabled'); } if (this.counter == this.stackStyle.length) { $('#redo').addClass('disabled'); $('#undo').removeClass('disabled'); return; } else { $('#redo').removeClass('disabled'); } console.log(style + ' - ' + id); //Apply history style $('#' + id).attr('style', style); console.log(this.counter + ' - ' + this.stackStyle.length); } }; //Stop Handler Drag function stopHandlerDrag(event, ui) { console.log('stop drag'); var style = $(ui.helper).attr('style'); var id = $(ui.helper).attr('id'); historyApp.add(style, id); } //Star Handler Drag function startHandlerDrag(event, ui) { console.log('start drag'); var style = $(ui.helper).attr('style'); var id = $(ui.helper).attr('id'); historyApp.add(style, id); //Dettach all events $('#' + id).draggable("option", "revert", false); //reassign stop events $('#' + id).draggable({ stop: stopHandlerDrag, start: '' }); } //Click Events For Redo and Undo $(document).on('click', '#redo', function() { historyApp.redo(); }); $(document).on('click', '#undo', function() { historyApp.undo(); }); 
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js"></script> <span id="undo" class="btn btn-sm btn-success disable">Undo</span> <span id="redo" class="btn btn-sm btn-danger disable ">Redo</span> <p id="P1" class="editable" style="left: auto; top:auto;">Drag #1</<p> <p id="P2" class="editable" style="left: auto; top:auto;">Drag #2</<p> <p id="P3" class="editable" style="left: auto; top:auto;">Drag #3</<p> 

And here is a Working code demo

Now, the problem is that for example if you run my code and drag the elements in the following order (Drag #1, Drag #2, Drag #3), and then you click on undo sometimes you will have to click twice, and then if you drag in that order and the click on undo to restore all the elements as at te beginning and then drag elements #1 and #2 and then click on undo you will notice that only the #1 element is going to comeback to his possition, so my question is what I'm doing wrong and how can I solve this? I think that maybe my counter is wrong or something is wrong with my var historyApp

You probably need to append a logic to check if the style to undo or redo differs from the prev/next state:

        add: function (style, id) {
            var preIndex = this.counter;
            if (this.counter == this.stackStyle.length - 1)
            {
                ++this.counter;
            }
            else
            {
                this.counter = this.stackStyle.length;
            }
            this.stackStyle[this.counter] = style;
            this.stackId[this.counter] = id;
            this.doSomethingWith(style, id);
        },
        undo: function () {
            --this.counter;
            while ($('#' + this.stackId[this.counter]).attr('style') == this.stackStyle[this.counter])
            {
                --this.counter;
            }
            this.doSomethingWith(this.stackStyle[this.counter], this.stackId[this.counter]);
        },
        redo: function () {
            ++this.counter;
            while ($('#' + this.stackId[this.counter]).attr('style') == this.stackStyle[this.counter]) {
                ++this.counter;
            }
            this.doSomethingWith(this.stackStyle[this.counter], this.stackId[this.counter]);

        },

There's something wrong with your logic. Here's a StateMaker I made myself. Don't copy it letter for letter, but it shows you the logic.

function StateMaker(initialState){
  var o = initialState;
  if(o){
    this.initialState = o; this.states = [o];
  }
  else{
    this.states = [];
  }
  this.savedStates = []; this.canUndo = this.canRedo = false; this.undoneStates = [];
  this.addState = function(state){
    this.states.push(state); this.undoneStates = []; this.canUndo = true; this.canRedo = false;
    return this;
  }
  this.undo = function(){
    var sl = this.states.length;
    if(this.initialState){
      if(sl > 1){
        this.undoneStates.push(this.states.pop()); this.canRedo = true;
        if(this.states.length < 2){
          this.canUndo = false;
        }
      }
      else{
        this.canUndo = false;
      }
    }
    else if(sl > 0){
      this.undoneStates.push(this.states.pop()); this.canRedo = true;
    }
    else{
      this.canUndo = false;
    }
    return this;
  }
  this.redo = function(){
    if(this.undoneStates.length > 0){
      this.states.push(this.undoneStates.pop()); this.canUndo = true;
      if(this.undoneStates.length < 1){
        this.canRedo = false;
      }
    }
    else{
      this.canRedo = false;
    }
    return this;
  }
  this.save = function(){
    this.savedStates = this.states.slice();
    return this;
  }
  this.isSavedState = function(){
    var st = this.states, l = st.length; sv = this.savedStates;
    if(l !== sv.length){
      return false;
    }
    function rec(o, c){
      var x, z;
      if(typeof o !== typeof c){
        return false;
      }
      else if(o instanceof Array){
        for(var n=0,q=o.length; n<q; n++){
          x = o[n]; z = c[n];
          if(x && typeof x === 'object'){
            return rec(x, z);
          }
          else if(o !== c){
            return false;
          }
        }
      }
      else if(o && typeof o === 'object'){
        for(var n in o){
          x = o[n]; z = c[n];
          if(x && typeof x === 'object'){
            return rec(x, z);
          }
          else if(o !== c){
            return false;
          }
        }
      }
      else if(o !== c){
        return false;
      }
      return true;
    }
    for(var i=0; i<l; i++){
      if(!rec(st[i], sv[i])){
        return false;
      }
    }
    return true;
  }
}

Implementation with jQuery UI would look something like:

var dragging;
function DragMaster(yourDraggable){
  var d = yourDraggable, p = d.offset(), sm = new StateMaker({left:p.left, top:p.top})), states = sm.states, t = this;
  function ls(){
    if(states.length){
      return states.slice(-1)[0];
    }
    return false;
  }
  this.update = function(){
    var cp = d.offset();
    sm.addState({left:cp.left, top:cp.top});
    return this;
  }
  this.undo = function(){
    sm.undo(); d.offset(ls());
    return this;
  }
  this.redo = function(){
    sm.redo(); d.offset(ls());
    return this;
  }
  this.save = function(){
    sm.save();
    return this;
  }
  this.getStates = function(){
    return states;
  }
  this.getSavedStates = function(){
    return sm.savedStates;
  }
  this.init = function(){
    return {
      start: function(){
        dragging = d;
      },
      stop: function(){
        t.update();
      }
    }
  }
}
var yd = $('#yourDraggable'), dm = new DragMaster(yd);
yd.draggable(dm.init());
$('#undo').click(function(){
  if(dragging === yd)dm.undo();
});
$('#redo').click(function(){
  if(dragging === yd)dm.redo();
});

Now you can create new DragMaster() s and send .init() into draggable.

Note:

If you want to see if the current state is the saved state to change the color of your save button then StateMakerInstance.isSavedState() is what you can use.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM