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/
Advertisement
Answer
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>