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.