Question

I want to implement Undo and Redo functionality not only for client side but for server side as well. For insatnce i have a div containing image and i can rotate resize and rewrite it , All the basic operations for image generation. And all of the operations update databse and image. you can say my image is being regenerated and database is updated after every action.

Now i need to implement Undo and Redo functionality. I have done some research as well. What i need is the best approach how to implement the required task. I was thinking to maintain each action "log type thing" or handle it with database or with javascript arrays including HTML or what else??

what is the best approach to achieve my goal.

Thanks,

Was it helpful?

Solution

At a basic level, you need two things:

  • an operation stack (array) which keeps track of the operations that have been performed. When the user performs an operation, you create an object that describes the operation and add it to the array. When the user hits undo, you can remove the last item from the array.

  • each operation type needs a 'save' method and an 'undo' method. This can get tricky as some 'undo' methods are similar to their 'save' method (i.e. to undo a horizontal flip you just do another flip), whereas others do not have such symmetry (i.e. to undo a crop you'd have to store the image data as it was before the crop occurred).

If you want 'redo' functionality, then you'd need a second operation stack. Each time an operation was undone, you'd add it to the end of the redo stack. If the user hits 'Redo', then you move it back to the operation stack again.

It may help to look into the Command pattern (http://en.wikipedia.org/wiki/Command_pattern), as this is often used to implement Undo.

OTHER TIPS

My javascript undo manager uses the command pattern. Basically, for each action you also implement an undo action and a redo action. You could build the same functionality serverside.

https://github.com/ArthurClemens/Javascript-Undo-Manager

And this is a clear code example of the command pattern: https://github.com/shichuan/javascript-patterns/blob/master/design-patterns/command.html

Well I worked on this for a project I guess it's ok to show the code here, my scenario may not be exactly like urs but similar, so my case was I stored the state of an environment, i wanted to store the changes to this environment but i decided not to use store the whole array for every change that would not be smart i guess so i used something called JSON-Patch its a cool protocol to monitor the changes in a json array and i can store those patches because they are smaller than my massive array.

const jiff = require('jiff')

/**
 * @description function chacks the difference between two states and returns the differece to be stored
 * @param oldState @type Array
 * @param newState @type Array
 */

///note when changing from a to b your looking for what would make b a not a b
  const createAnOpp = (newState, oldState) => {
  // console.log("\n newState",newState)
  // console.log("\n oldState",oldState)
  // console.log("\n new opp", jiff.diff(  JSON.parse(JSON.stringify(newState)),JSON.parse(JSON.stringify(oldState))))
  return jiff.diff(  JSON.parse(JSON.stringify(newState)),JSON.parse(JSON.stringify(oldState)) )
};


/**
 * @description takes an operation  and applies the patch operation to data passed on by reference
 * @param opperation @type reference
 * @param data @type reference
 */
  const perfomOpp =(opperation,data )=>{
   return jiff.patch(opperation, data);
}

/**
 * @description applies the do redo or undo feature based on the command sent to it
 * @param code @type number 1 = redo 0 = undo
 * @param data @type Array
 * @param opperation
 * @returns an object which is the state and the redo opp { newOpp,latestState}
 */

  const performCall = (code,data,operation)=>{
  switch(code){
    case(0): ////undo
    {
      //patches on the list are stored to undo(go back to previous state)
      const latestState = perfomOpp(operation,data)
      //  console.log("\n latestState",latestState)
      //  console.log("\n oldState",data)

      return {
      latestState ,
       newOpp:createAnOpp(latestState,data)
      }

      break
    }


    case(1): ////redo
     {
      //patches on the list are stored to undo(go back to previous state)
       const latestState = perfomOpp(operation,data)
            // console.log('\n neww opp stage 1==>',createAnOpp(data,latestState))

      return {
      latestState ,
       newOpp:createAnOpp(latestState,data)
      }

      break



    }
  }

}


///init state
var a = [
    { name: 'a' },
    { name: 'b' },
    { name: 'c' },
]

console.log("\n current Array ", a)

var opps = []
var pointerToOps = 0

var b = a.slice();
b.push({ name: 'd' });
// console.log("\n b==>",b)
console.log("\n current Array ", b)


// Generate diff (ie JSON Patch) from a to b
var patch = createAnOpp(b, a);

opps.push(patch)//store the diff when its been changed
pointerToOps = opps.length - 1 
// console.log("\n opps1==>",JSON.stringify(opps))

//update the pointer

var c = b.slice();

c.push({ name: 'e' });
console.log("\n current Array ", c)

// console.log("\n c==>",c)

// Generate diff (ie JSON Patch) from a to b
var patch = createAnOpp(c, b);
opps.push(patch)//store the diff when its been changed
pointerToOps = opps.length - 1 
console.log("\n opp ", opps)


//update the pointer

//now ive applied change and what not time to undo

const newData = performCall(0,c,opps[pointerToOps])
// //now i have to go take a step back with the pointer 
opps[pointerToOps] = newData.newOpp//replacing undo opp with redo opp
pointerToOps = --pointerToOps 
// console.log("\n opps3==>",JSON.stringify(opps))
console.log("\n current Array ", newData.latestState)



const newData2 = performCall(0,newData.latestState,opps[pointerToOps])
//now i have to go take a step back with the pointer 
console.log("\n current Array ", newData2.latestState)
opps[pointerToOps] = newData2.newOpp//replacing undo opp with redo opp
pointerToOps = --pointerToOps 


pointerToOps = ++pointerToOps
const newData3 = performCall(1,newData2.latestState,opps[pointerToOps])
//now i have to go take a step back with the pointer 
opps[pointerToOps] = newData3.newOpp//replacing undo opp with redo opp

console.log("\n current Array ", newData3.latestState)

pointerToOps = ++pointerToOps
const newData4 = performCall(1,newData3.latestState,opps[pointerToOps])
//now i have to go take a step back with the pointer 
opps[pointerToOps] = newData4.newOpp//replacing undo opp with redo opp
console.log("\n current Array ", newData4.latestState)



console.log("\n opp ", opps)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top