0

I have textarea and storing in array onclick i need to show one by one from the last element and redo onclick one by one from where user clicks. i am doing a custom undo and redo functionality.

var stack =[];

jQuery('#enter-text').keypress(function() { 
console.log(jQuery('#enter-text').val());
stack.push(jQuery('#enter-text').val());

  })

 jQuery('#undo_text').click(function() {
    console.log(stack.pop());
 })

jQuery('#redo_text').click(function() {
    // how to redo onclik where user undo text
})

I have created jsfiddle

https://jsfiddle.net/k0nr53e0/4/

Parker
  • 141
  • 3
  • 20

4 Answers4

2

Make an array of old values, so something like this:

var deleted_stack = [];

// your other code

jQuery('#undo_text').click(function() {
  deleted_stack.push(stack.pop());
})

jQuery('#redo_text').click(function () {
  stack.push(deleted_stack.pop());
}

If you need to be able to do this also in the middle of the text, you should keep track of cursor position. Then your deleted stack should look more like this:

deleted_stack = [
  {
    char_idx: 2, // index of position in string where it was deleted, but
                 // then you shouldn't be deleting characters with .pop()
    char_val: 'a'
  },
  ... // other objects like first one
]

But then deleting also becomes more complex...

DekiChan
  • 375
  • 1
  • 4
  • 14
1

This is a design decision, and it depends fully on your user needs and how complex you are willing to make it.

The way it is usually done is to keep 2 lists, one for undo, and one for redo. Every time an action is undone, it is added to the redo list. Every time a new action is taken after undoing, the redo list is destroyed.

var stack = [];
var redo = [];

jQuery('#enter-text').keypress(function() { 
  console.log(jQuery('#enter-text').val());
  stack.push(jQuery('#enter-text').val());
  redo = [];  // <-----
})

jQuery('#undo_text').click(function() {
  redo.push(stack.pop());  // <-----
})

jQuery('#redo_text').click(function() {
  stack.push(redo.pop())  // <-----
})
santi-elus
  • 310
  • 2
  • 13
1

instead if keeping different stacks for the actions you've done, and undone, you can keep them in one Array, and memorize the current position:

var stack = [ jQuery('#enter-text').val() ], index = 0;
updateButtonsDisabled();

jQuery('#enter-text').keypress(function() { 
    //adding the current action
    stack[++index] = jQuery('#enter-text').val();
    
    //removing the entries after the last one you added: they belong to a different redo-stack
    stack.length = index+1;
    updateButtonsDisabled();
})

jQuery('#undo_text').click(function() {
    if(!index) return;

    jQuery('#enter-text').val(stack[--index]);
    updateButtonsDisabled();
})

jQuery('#redo_text').click(function() {
    if(index === stack.length-1) return;
    
    jQuery('#enter-text').val(stack[++index]);
    updateButtonsDisabled();
})

//just some sugar
function updateButtonsDisabled(){
    jQuery('#undo_text').toggleClass("disabled", index === 0);
    jQuery('#redo_text').toggleClass("disabled", index === stack.length-1);
}

index holds the position in stack of the currently shown value. You can undo and redo as much as you want, but as soon as you start typing, the redo-stack will be cleared.

You should consider limiting the items you want to keep in stack, or you'll allocate quite some memory. And you could change the logic for keypress to wait for a pause of like 300ms before you update the stack. That would decrease the items in your stack tremendously.

Edit: made a snippet implementing the possible changes I mentioned, like detached update, and limited stacksize. Take a look at that

//this value is kept small for testing purposes, you'd probably want to use sth. between 50 and 200
const stackSize = 10;

//left and right define the first and last "index" you can actually navigate to, a frame with maximum stackSize-1 items between them.
//These values are continually growing as you push new states to the stack, so that the index has to be clamped to the actual index in stack by %stackSize.
var stack = Array(stackSize),
  left = 0,
  right = 0,
  index = 0,
  timeout;
//push the first state to the stack, usually an empty string, but not necessarily
stack[0] = $("#enter-text").val();
updateButtons();

$("#enter-text").on("keydown keyup change", detachedUpdateText);
$("#undo").on("click", undo);
$("#redo").on("click", redo);

//detach update
function detachedUpdateText() {
  clearTimeout(timeout);
  timeout = setTimeout(updateText, 500);
}

function updateButtons() {
  //disable buttons if the index reaches the respective border of the frame
  //write the amount of steps availabe in each direction into the data-count attribute, to be processed by css
  $("#undo")
    .prop("disabled", index === left)
    .attr("data-count", index - left);

  $("#redo")
    .prop("disabled", index === right)
    .attr("data-count", right - index);

  //show status
  $("#stat").text(JSON.stringify({
    left,
    right,
    index,
    "index in stack": index % stackSize,
    stack
  }, null, 2))
}

function updateText() {
  var val = $("#enter-text").val().trimRight();
  //skip if nothing really changed
  if (val === stack[index % stackSize]) return;

  //add value
  stack[++index % stackSize] = val;

  //clean the undo-part of the stack
  while (right > index)
    stack[right-- % stackSize] = null;

  //update boundaries
  right = index;
  left = Math.max(left, right + 1 - stackSize);

  updateButtons();
}

function undo() {
  if (index > left) {
    $("#enter-text").val(stack[--index % stackSize]);
    updateButtons();
  }
}

function redo() {
  if (index < right) {
    $("#enter-text").val(stack[++index % stackSize]);
    updateButtons();
  }
}
#enter-text {
  width: 100%;
  height: 100px;
}

#undo,
#redo {
  position: relative;
  padding-right: 1em;
}

#undo:after,
#redo:after {
  content: attr(data-count);
  position: absolute;
  bottom: 0;
  right: 0;
  font-size: 0.75em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<textarea id="enter-text"></textarea>
<button id="undo">undo</button>
<button id="redo">redo</button>
<pre id="stat">

</pre>
Thomas
  • 11,958
  • 1
  • 14
  • 23
  • Thanks it works but how trigger keykpres event and append to div area – Parker Oct 06 '16 at 15:39
  • @Parker, don't trigger events, give the function a name, and simply call it by its name. And to the div, I don't know what and when you want to append to the mentioned div. All the data is in the stack-array. btw. I've added a fiddle with an extended code, take a look at it. – Thomas Oct 07 '16 at 05:16
0

You can use the keyup event and keep always the entered data on the stack array. These way you can update the value of field $('#enter-text') with the appropriate array index that will be updated in the variable index:

var $textEnter = $('#enter-text'),
    stack = [],
    index = 0;

$textEnter.on('keyup', function () {
  stack.push($textEnter.val());
  index = stack.length - 1;
});

$('#undo_text').on('click', function () {  
  if (index >= 0) {
    $textEnter.val(stack[--index]);
  }
});

$('#redo_text').on('click', function () {
  if (index < stack.length - 1) {
    $textEnter.val(stack[++index]);
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<textarea id="enter-text"></textarea>
<div id="drag-area-selected">

</div>
<button id="undo_text">
  Undo
</button>
<button id="redo_text">
  Redo
</button>
Yosvel Quintero
  • 18,669
  • 5
  • 37
  • 46