Link to home
Start Free TrialLog in
Avatar of msalamon
msalamon

asked on

Undo and Only Allow Deletes in Textarea

Delete Only

I need to create a text area that will provide very limited editing capabilities to our users.  The textarea will come prefilled with text.  I want to give users the ability to ONLY delete text from the textarea, not to add any text.  I have generally figured out how to do this, by disabling all keys except delete/backspace and the arrow keys.  One thing I have not been able to turn off, however, it the right-mouse-click paste feature, which will paste text, notwithstanding the disabling of other keys.  I want the paste disabled JUST for the textarea (there will be other stuff on the page).

Undo

Related to this, I want to give users the ability to undo (redo is not important) any deletions they have made, such as by including an undo button under the textarea.  (there can be an unlimited number of undos).  However, I have not been able to figure out how to do this.
Avatar of Witchazel
Witchazel

<textarea
  onbeforepaste=event.returnValue=false
  onpaste=event.returnValue=false
>HIHI</textarea>

no more pasten.  killing the keys is probable your best bet too.

-w
undoes

<script>
var versions = new Array();
</script>

<textarea id=youcannottypetome
  onbeforepaste=event.returnValue=false
  onpaste=event.returnValue=false
  onkeydown=(hoping you killed all keys on keydown)
  onkeyup=versions[versions.length]=this.value
  oncut=versions[versions.length]=this.value
>HIHI</textarea>
<button onclick="youcannottypetome.value=versions[versions.length-1];versions.length=versions.length-1"
>HIHI</button>
Avatar of msalamon

ASKER

Thanks.  The paste restriction worked great.  However, I have a few concerns about the undo.

First, the restriction on editing that I need, that you can only delete, does not seem to work if you fully disable onkeydown, which you recommended.  I am not an expert on javascript in any way, but I was applying the same function:

function disableNonDelete()
{

      e = event;



      if(e.keyCode == 8 || e.keyCode == 46 || (e.keyCode >= 37 && e.keyCode <=40))

            return true;

      else

            return false;

}


on each of the following (although all may not be necessary):

onKeyDown="return disableNonDelete();"
onKeyPress="return disableNonDelete();"
onKeyUp="return disableNonDelete();"
onChange="return disableNonDelete();"

How can I both preserve the ability to delete with the delete/backspace key and include ALL deletes (using either delete, backspace, control-x or right-mouse-cut) in the undo functionality.

Second, my disable delete function does not seem to be able to allow control-x for deleting.  Since this is a key combination, as soon as the control key is hit, the function won't wait for the -x to be hit as well.  Do you know how I can get control-x to work too (along with delete, backspace, and the arrows)?

Third, if you hit undo and there is nothing in the array, then the textarea gets the text "undefined". How do I disable the undo button when the array.length = 0?

Fourth, the Array holds the entire contents of the text area, which in some cases may be 20+ pages.  If we store a copy of the text every time a single letter is deleted, the array is going to get VERY full  What I was hoping (but couldn't figure out how), was to use the document.getSection() methods of javascript to work.  I was hoping that each time you either selected and then deleted text, or just deleted text with a key stroke, that I could store the starting point of where the delete happened and the text deleted, and put that into the undo array.  Then, each time you hit undo, it would take the last deletion and insert the text at the point where it was deleted, updating the text in the textarea.  this would greatly reduce the text stored in the array.  A solution like that would be great!

oopsie wrong event for the control+X and mouse cut ; this will be better (oncut should be onbeforecut :)


<script>
var versions = new Array();

function undo() {
     if(version.length>0) {
          youcannottypetome.value=versions[versions.length-1];
          versions.length=versions.length-1;
     }
}

function keydow() {
    switch(event.keyCode) {
        case 88:  case 67:    // x and c (not sure if you want c because that is copy not really delete)
            if(event.ctrlKey) return;       // if control x or control c go about yer business         break;
        case 8:  case 46:
            return;             // if del or backspace  go about your business
        break;
    }
    event.returnValue = false;       // everything else is canceled
}
</script>

<textarea id=youcannottypetome
  onbeforepaste=event.returnValue=false
  onpaste=event.returnValue=false
  onkeydown=keydown()
  onkeyup=versions[versions.length]=this.value
  onbeforecut=versions[versions.length]=this.value
>HIHI</textarea>

<button onclick="youcannottypetome.value onclick=undo()>HIHI</button>

note if you allow them to use copy/control c you need to add
  onbeforecopy=versions[versions.length]=this.value


-w


for the selection area the hardest part you are going to have is finding where you are in the textarea, i think ns6 has a cursorlocation call but ie doesnt.

here is the psudo of what you need to do

on before delete key or backspace key

var selection = document.selection.createRange().duplicate();  // get range, this will be blank (del not done)
selection.moveStart("character",-1);   // get the char you are going to delete
var selection  = selection.text;    // there got the deleted stuff
var whereat = document.selection.createRange().duplicate();
whereat .moveStart("character",-1);   // move to where this would go back
whereat .moveEnd("character",-1);   // start and end now both at position
whereat .moveStart("textedit",-1);   // move start to begining of textarea (sometimes this goes to begin of doc)
var whereat = whereat.text.length;

it will take lots of testing and be browser dependant, lots of weird things like to happen with the selections too, like textedit-1 takes you to the begining of the document so you need to embed a hidden span with funny chars so you can split the string to find the real value and such

oh, another sneekie way you could to it is
onbeforecut  get selection
onkeydown  get the char to the left of selection

then

insert some unique chars, split the whole textarea by the chars, count the chars in the first arry of the split, then remove chars :)

-w


Using a combination of the ideas you gave me and some ideas I read elsewhere to figure out the index position of the cursor in a textarea, I managed to succeed about 85%.  The code below mostly works (although in some cases I don't know why it works), but it does NOT work appropriately when you try to delete (with the "delete" key) a single character.  It works fine for these situations where you try to delete text:  backspace a single character or backspace/delete/ctrl-x/right-mouse-cut selected text.  I trying playing around wth lots of combinations of changes, but none of them worked.  (btw, I noticed that mozilla has a built in undo feature, but not IE.)

The code below basically takes the selected text, replaces it with a ~ (which should not be in the text in the first place), gets all of the text in the textarea (which now has the ~), finds the index of the ~, then reverts the text back the way it was, allowing any of the delete types to go forward.  What appears to happen with the "delete" key where you are deleting a single character is that moveEnd() position of the textrange is incremented by 1 (to capture the character to be deleted, for undo purposes), the char to be deleted gets replaced by ~ (and is never changed back), and then the delete goes forward to the character to the right of the one you wanted deleted.  For example, if you had:
     ABC
and tried to delete the A, you would end up with:
     ~C
where the A was replaced by the ~ and the B deleted.  I put a comment in that shows a line of code that will not delete the B, but still leaves the ~ in place.

Here is the relevant code.

<script language="javascript">
var versions = new Array();
function disableNonDelete()
{
      var e = event;
      if(e.keyCode == 8 || e.keyCode == 46 || (e.keyCode == 88 && e.ctrlKey))
      {
            var undoObject = new Object();
            undoObject.text = "";
            undoObject.start = 0;
            var tr = document.selection.createRange();
            var trCopy = document.selection.createRange().duplicate();

            if(tr.text == "")
            {
                  if(e.keyCode == 8)
                  {
                        tr.moveStart("character",-1);
                        if(tr.text == "")
                              return true;
                        undoObject.text = tr.text;
                  }
                  else if(e.keyCode == 46)
                  {
                        tr.moveEnd("character",1);
                        if(tr.text == "")
                              return true;
                        undoObject.text = tr.text;
//tr.moveEnd("character",-1);
                  }
                        else
                              return false;
            }
            else
                  undoObject.text = tr.text;

            tr.text = "~";
            undoObject.start = document.mda.delete_only.value.indexOf("~");
            versions[versions.length]=undoObject;
            tr = trCopy;
            document.mda.undo_button.disabled=false;
            return true;
      }
      else if (e.keyCode >= 37 && e.keyCode <=40)
            return true;
      else
            return false;
}

// the function for handling the right mouse click-cut is similar to above and works fine

function undo()
{
      if(versions.length > 0)
      {
            var undoObject = versions[versions.length-1];
            versions.length=versions.length-1;

            var text = document.mda.delete_only.value;
            var before = text.substring(0,undoObject.start);
            var after = text.substring(undoObject.start, text.length);
            document.mda.delete_only.value = before + undoObject.text + after;
      }
}

</script>

<form name="mda">
<textarea name="delete_only" cols="50" rows="10" wrap="soft"
onKeyDown="return disableNonDelete();"
onbeforepaste="event.returnValue=false"
onpaste="event.returnValue=false"
oncut="return onCut();"
>
WHATEVER TEXT YOU WANT HERE
</textarea>

<p>
<button name="undo_button" onclick="if(versions.length > 0) {undo(); if(versions.length == 0) {this.disabled=true;}}" disabled>Undo Last Change</button>

...
</form>

No comment has been added lately, so it's time to clean up this TA.
I will leave the following recommendation for this question in the Cleanup topic area:

PAQ with points refunded

Please leave any comments here within the next four days.
PLEASE DO NOT ACCEPT THIS COMMENT AS AN ANSWER!

jAy
EE Cleanup Volunteer
ASKER CERTIFIED SOLUTION
Avatar of SpazMODic
SpazMODic

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial