views:

1021

answers:

6

I have a form with an array of text fields. The user (through javascript) can add an arbitrary number of text fields to the form. After submitting the form and pressing the back button the form displays only with the fields that were on the original form when it was first rendered (any added text fields are lost). What is the best way to allow the back button to render the form in the state when the user submitted it? Any ideas are welcome, some things I've tried are:

  • Put the form data in a cookie (this doesn't work great for a couple reasons but the biggest killer for me is that cookies are limited to 4K in size)
  • Put the form data in a session
  • Submit the form via AJAX and then manage the history

Thanks for the help. I've posted a test form on my website at http://fishtale.org/formtest/f1.php. Also here is a simple form exhibiting the behavior I mentioned:

<form action="f2.php" method="post">
<input type="text" name="text[]" id="text1"/>
<input type="submit" name="saveaction" value="submit form" />
</form>

<a href="f2.php" id="add_element">Add Form Element</a>

<script type="text/javascript" 
     src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js" ></script>

<script type="text/javascript" >
    $('#add_element').click(function (event) {
     event.preventDefault();
     $('#text1').after('<input type="text" name="text[]" />');
    });
</script>

This is similar to a question I posted a while ago, Best Way For Back Button To Leave Form Data, however, this form's elements are modified by the user.

A: 

You could make your own back button at the top of the web page and make it bigger and prettier than the standard web browser back button.

Under the hood your code could know what the previous state was and revert to it or if there was no previous state you can maybe call the browser's back function?

Kurt W. Leucht
Thanks for the idea, I actually already do that, however, some users still click on the other back button (including myself sometimes).
Brian Fisher
+7  A: 

I can't find a prewritten library for this, but I'm sure its been solved before. If I had to it myself I would take this approach:

  1. Use the command pattern so that each method which modifies the page's UI by adding controls also invokes an AJAX method to push the method invoked (textual Javascript representation) onto a queue stored in the server's session.

  2. After body onLoad completes, use an AJAX method to query the server's session for a command queue for the page the user is on. If one is retrieved, just eval each member of the queue to rebuild the page's UI in the same order the user did it.

Keep in mind with this approach you are recording not just additions of controls, but removals as well. You will require separate state holders for user input controls, like text boxes (you will also likely need server-side session with AJAX method access).

cfeduke
Sounds interesting, I will give it a try. You mention "separate state holders for user input controls" what do you mean by this? Thanks.
Brian Fisher
If a button click adds a text box ('text2') your method will push a 'addTextBox("text2");' JavaScript command to your AJAX service. That's all fine and good. The problem is the user is going to type text in said text box. You'll need these text boxes (or other controls) to relay said data to the server so when you recreate the UI, you also recreate the values the user input into the controls. You can use the command pattern to solve this as well by pushing JavaScript functions to set values onto the command queue.
cfeduke
There *has* to be a library out there for doing just this, unfortunately googling for "record and replay Javascript" doesn't point me right to it.
cfeduke
MS has a thing called "history points" for their AJAX and ASP.NET stuff which utilizes the querystring for state storage (ug) but I don't see a general purpose library for doing this.
cfeduke
Here's an interesting alternative approach where you insert URLs into the browser's history: http://www.oracle.com/technology/pub/articles/dev2arch/2006/01/ajax-back-button.html also look at the references on page 3 for more ideas.
cfeduke
+4  A: 

How about creating an <input type="hidden"> (with no name or outside the form so it's not submitted) in which you store an encoded list of extra fields to add and their values? While the browser won't remember newly-added fields on ‘back’, it will remember the values of hidden fields that were in the form from the start.

Here's an example that saves the extra fields on document unload and retrieves them on ready:

<input type="hidden" id="remembertexts" />

<form action="http://www.google.com/" method="get">
    <div id="texts">
        <input type="text" name="text[]" value="" />
    </div>
    <div>
        <input type="submit" />
        <input type="button" id="addtext" value="+" />
    </div>
</form>

<script type="text/javascript">

    // Add new field on button press
    //
    $('#addtext').click(function() {
        addInput('');
    });

    function addInput(text) {
        $('#texts input').eq(0).clone().val(text).appendTo('#texts');
    };

    // Store dynamic values in hidden field on leaving
    //
    $(window).bind('beforeunload', function() {
        var vals= [];
        $('#texts input').each(function() {
            vals.push(encodeURIComponent(this.value));
        });
        $('#remembertexts').val(vals.join(';'));
    });

    // Retrieve dynamic values on returning to page
    //
    $(function() {
        var extratexts= $('#remembertexts').val().split(';').slice(1);
        $.each(extratexts, function() {
            addInput(decodeURIComponent(this));
        });
    });
</script>

Notes:

  • You can use form.onsubmit instead of window.onbeforeunload if you only need it to remember values over a submission. onunload doesn't work as some browsers will already have stored the old form values before that event occurs.

  • In Firefox the position of the hidden input is important. For some reason, if you put it below the dynamically-added fields, Firefox gets confused about which input it is and fails to remember the value.

  • This example doesn't work in Opera. It can be made to work in Opera, but it's a pain. Opera's calling of load and unload events is inconsistent so you have to use onsubmit instead, or setting the hidden field on a polling interval, or something. Worse, when Opera remembers previous form-field values, it actually doesn't fill them in until after onload has fired! This already causes many, many form-scripting problems. You can work around that by putting a small timeout in your onload to wait until the form values have gone in if you need Opera compatibility.

bobince
A: 

Block the use of the back button. When the back button is pressed, rerender the previous page for the user with the new fields included, either visibly if that makes sense, or hidden. That way the user is able to use the back button normally and you have full control over the appearance of the ''previous'' page.

In your specific use case, you just need to render the page with all the fields visible and filled in with the values that were submitted.

This is a good pattern to follow for any wizard type of process where you provide a sequence of forms for the user to fill in and they may choose to go back to a previous form.

To make it perfectly clear, I am suggesting that you use this advice on capturing the onUnload event to trigger form submission (so that you get the entered values) and to rerender the previous page that "back" would have displayed (without the values). The only alternative is to use Ajax to send the entered values every time that the user leaves a field, and then have every page check with the server via AJAX to retrieve additional values to display.

Here are some additional pages that discuss taking control over the function of the back button and using the unload event to persist forms data:

Michael Dillon
The question here is.. how to rerender the previous page with user submitted values. And don't forget that the user may alter the values and submit again. They may even delete some values.
Nirmal
Strange comment. The application on the server has the user submitted values and should know how to rerender the previous page (which it also rendered) using the additional values submitted by the user. Or deleted by the user. The point is that after clicking submit, the state exists in the server, and if it wants to maintain that state on the page that the user views, it can render the page with that state included so that the state is now on the client.
Michael Dillon
+3  A: 

In good browsers you can have it working perfectly simply by not breaking it.

Firefox 1.5 uses in-memory caching for entire Web pages, including their JavaScript states, for a single browser session. Going backward and forward between visited pages requires no page loading and the JavaScript states are preserved. source

This is supported in Opera and WebKit too. However DOM cache is only possible in you stick to the rules:

  1. Don't use onunload, onbeforeunload.
  2. Don't use Cache-control: no-store or must-revalidate.
    In PHP you must change session.cache_limiter from patently_ridiculous (I think they spell it nocache) to none.

    session_cache_limiter('none');
    
  3. Unfortunately HTTPS is also out.

If you don't force browsers to reload the page, they won't. They'll keep the DOM and its values unchanged, exactly as RFC 2616 suggests.


However, if you're looking for place to stash the data, there's incredibly clever hack – window.name can store megabytes of data. It's not sent to server, and it isn't shared between windows.

There are also Flash cookies and HTML 5 localStorage is implemented in IE8 and Safari 4.

porneL
In my example the fields added dynamically by javascript to not get recreated when I press the back button in FF3.5, Chrome or IE 8. I believe my http headers are set correctly. I also tried using session_cache_limiter('none'), but no change. Is there something I'm missing?
Brian Fisher
jQuery uses onunload to plug IE memory leaks, and onunload breaks caching: http://groups.google.com/group/jquery-dev/browse_thread/thread/67abd50cd3ef0fc8
porneL
A: 

Step 2: The script processing the form puts the values entered into an array and stores that array into a session variable (or text / db / whatever you find appropriate).

Step 1: The script which outputs the form adds a javascript (which in turn fills in the form) if that session variable is found (and it also clears the session variable).

Anax