Adding global undo and redo functionality to web-pages – implement rich javascript functionality in any JSF, JSP, PHP, etc. page

0

Most Desktop applications sport features such as CTRL-Z and CTRL-Y that allow us to undo (and undo the undo, aka redo) changes in files, documents and spreadsheets. Browsers such as Firefox have similar features for webpages (HTML Forms), but only on the field level. If you enter values in an HTML based webform, such as the one shown here, there is undo and redo support for every individual field, going back all the way to the original value of when the page first loaded by multiple ctrl+y keypresses.

 

After having created a pretty complex, wannabe-rich user interface for massive data manipulation, I was wondering whether perhaps a global undo and redo option, one that goes across all fields and does not require navigation to individual fields, would be convenient. I have created a little bit of JavaScript – that is by no means finished. But the general idea is there, and I am wondering it makes sense at all. So if you have an opinion on this functionality, please share it with me.

What I have created is the following functionality:....

  • changes in the fields in the first two and the last column are recorded
  • whenever the user press CTRL+Shift+Z, the generic Undo is applied: without the user having to navigate back to any of the fields he or she made changes to, the old values are restored. Every addition CTRL+Shift+Z takes back one step further in time. Every undone field is highlighted, so it is clear to the user where changes have been reverted.
  • after one or more Undo actions, the user can press CTRL+Shift+Y to redo an action (or undo the undo).

Let’s make a few changes in the table from above:

Now we realize that from a certain point in time we have been applying erroneous changes. We cannot quite remember which fields we have changed. And some changes were correct, so reloading the page and reapplying them all is somewhat silly as well. So now we use Global Undo and press Ctrl+Shift+Z, to rewind our changes.

 

Well, that won’t do so let’s revert some more changes, again with a few Ctrl+Shift+Z actions:

There we are. With Ctrl+Shift+Y we can redo some of these changes if we feel like it. We could even present the user with a popup of all changes made in the current page. And the only changes required are the registration on the fields we want to have undo-enabled of two event listeners. The most straightforward way to do this, is like this:

<tr:inputText value="#{customer.firstName}" onfocus="registerForUndo(this);" onblur="deregisterForUndo(this);" />
<tr:inputText value="#{customer.lastName}"  onfocus="registerForUndo(this);" onblur="deregisterForUndo(this);" />
<tr:inputText value="#{customer.country}"  onfocus="registerForUndo(this);" onblur="deregisterForUndo(this);" /> 

However, we can do it even simpler, by having a JavaScript function called from the onLoad for example find all visible, enabled INPUT elements in the page and attaching these event listeners to all of them.

On the table element (could also be the form or any other container element) we attach a KeyUp event listener to listen for Ctrl+Shift with Y or Z actions:

 <tr:table id="customers" emptyText="No items were found" onkeyup="keyup(this,event);"
            value="#{CustomerManager.customers}" var="customer">
            

Now all we need is to attach a JavaScript library to our page with the functions called in these event listeners. The draft code looks like this:

<p>var currentregisterindex=0;<br />var undoregister = new Array();<br />var undomode = false;<br /><br />function registerForUndo(el) {<br />  if (!undomode &amp;amp;&amp;amp; el.type==&quot;INPUT&quot;) {<br />    index = el.id;<br />    changeRecord = {'id':index, 'oldvalue':el.value};<br />    undoregister.push(changeRecord);<br />  }<br />}<br /><br />function deregisterForUndo(el) {<br />  if (!undomode &amp;amp;&amp;amp; el.type==&quot;INPUT&quot;) {<br />    if (el.id == undoregister[undoregister.length-1].id) {<br />      if (undoregister[undoregister.length-1].oldvalue== el.value) {   <br />        // not changed so loose the registration<br />        undoregister.pop();<br />      }<br />      else {<br />        undoregister[undoregister.length-1].newvalue= el.value;<br />        currentregisterindex=undoregister.length-1;<br />      }<br />    } <br />    else // new element, that is already deregistered; this really should not happen<br />      {}<br />  }<br />}<br /><br />    function keyup(inputField, e) <br />    { <br />      var x='';<br />      if (document.all){<br />        var evnt = window.event;<br />        x=evnt.keyCode;<br />      }//if <br />      else {<br />        x=e.keyCode;<br />      }//else<br />      if (e.shiftKey &amp;amp;&amp;amp; e.ctrlKey &amp;amp;&amp;amp; x==90) // CTRL Z<br />      {<br />        if (currentregisterindex&gt;=0) {<br />          changeRecord=undoregister[currentregisterindex];<br />          el = document.getElementById(changeRecord.id);<br />          undomode=true;<br />          el.value = changeRecord.oldvalue;<br />          el.select();<br />          undomode=false;<br />          if (currentregisterindex&gt;0)<br />            currentregisterindex--;<br />        }<br />      }<br />      if (e.shiftKey &amp;amp;&amp;amp; e.ctrlKey &amp;amp;&amp;amp; x==89) // CTRL Y<br />      {<br />        if (currentregisterindex &amp;lt; undoregister.length-1) {<br />          changeRecord=undoregister[++currentregisterindex];<br />          el = document.getElementById(changeRecord.id);<br />          undomode=true;<br />          el.value = changeRecord.newvalue;<br />          el.select();<br />          undomode=false;<br />       }<br />      }<br />     }<br /><br /><br />&nbsp;</p>
Share.

About Author

Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director for Fusion Middleware. Consultant, trainer and instructor on diverse areas including Oracle Database (SQL & PLSQL), Service Oriented Architecture, BPM, ADF, Java in various shapes and forms and many other things. Author of the Oracle Press book: Oracle SOA Suite 11g Handbook. Frequent presenter on conferences such as JavaOne, Oracle OpenWorld, ODTUG Kaleidoscope, Devoxx and OBUG. Presenter for Oracle University Celebrity specials.

Comments are closed.