0

I'm changing an application so that it stops being cached, using cache-control parameters to the requests' headers to achieve this. Unfortunately this resulted in "Confirm form resubmission" messages whenever I submit a form and press the Back button, which is a big NO...

I've been reading about Form Resubmission and Post-Redirect-Get but I'm still struggling with this.

In most navigation cases I have 2 pages:

  • page1 contains the form AND (after submitting the form) the results, it's used twice.

  • page2 shows details of the result clicked by the user.

The beans are RequestScoped and navigation from page1 to page2 is made like this:

'''

<h:dataTable value="#{page1.resultTable}">
    <h:column>
        <h:commandLink action="#{page2.getById(resultTable.id)}">
            <h:outputText value="#{resultTable.id}"/>
        </h:commandLink>
    </h:column>

   (...)

'''

After using PRG, the resultTable.id became null on page2 loading. I solved this using Flash Scope, but the issue with the page1 remains, when going back from page2.

Is there a safe and elegant way to keep page1's data after visiting page2, keeping in mind that it contains sensitive information (financial app)?

All pages are based on a template.xhtml that contains a back-button, and their respective beans are implementation classes of a templateInterface. I thought I could use an "elegant" solution by having custom code on template.xhtml's back-button that would use Flash Scope again to load the data in the previously-visited bean. However, once I click the back button, I immediately get the missing cache message.

I did manage to automatically refresh the previously visited page by including js "window.history.replaceState( null, null, window.location.href )" but all the form data is lost by doing so... Is this there a better solution?

EDIT:

page1.xhtml looks like this:

<?xml version='1.0' encoding='UTF-8' ?>
<ui:composition xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                template="/WEB-INF/templates/transaction-template.xhtml"
                xmlns:t="http://myfaces.apache.org/tomahawk"
                xmlns:c="http://java.sun.com/jsp/jstl/core"
                xmlns:components="http://java.sun.com/jsf/composite/components">

    <ui:define name="mainContent">
        <h:form id="form">
            <h:inputHidden id="_itemId" value="#{listItem.itemId}" />

            <fieldset class="fieldsetTransaction">
                <table class="formTransaction">
                    <tr>
                        <td class="labelsCol">
                            <h:outputText value="#{labels.itemId}" />
                        </td>                        
                        <td class="valuesCol">
                             <h:inputText value="#{listItem.itemId}" />
                        </td>
                    </tr>
                    <tr>
                        <td class="labelsCol">
                            <h:outputText value="#{labels.itemName}" />
                        </td>
                        <td class="valuesCol">
                             <h:inputText value="#{listItem.itemName}" />
                        </td>
                    </tr>
                    <tr class="formActions">
                        <td colspan="4">
                            <h:commandButton value="#{labels.search}"
                                                 title="#{labels.search}"
                                                 type="submit"
                                                 id="search"
                                                 action="#{listItem.list()}"
                                                 styleClass="default formButton">
                                <f:ajax execute="@form" render="resultPanel"/>
                            </h:commandButton>
                        </td>
                    </tr>
                </table>
            </fieldset>

            <h:panelGroup id="resultPanel">
            <fieldset class="fieldsetTransaction">

                <h:dataTable value="#{listItem.listReply.itemList}"
                             cellpadding="1" id="itemList_"
                             border="0" width="100%" columnClasses="c, l, c, l"
                             rowClasses="odd, even"
                             styleClass="list" var="itemList" cellspacing="1"
                             rendered="#{not empty listItem.listReply.itemList}">
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="#{labels.itemId}" />
                        </f:facet>
                        <h:commandLink action="#{editItem.getById(itemList.itemId)}"
                                       id="searchItemLink"
                                       title="#{menu['search.item']}">
                            <h:outputText value="#{itemList.itemId}" />
                        </h:commandLink>

                         <!-- new working code -->
                        <h:link action="#{editItem.getById(itemList.itemId)}"
                                       id="searchItemLink_"
                                       title="#{menu['search.item']}">
                            <h:outputText value="#{itemList.itemId}" />
                            <f:param name = "itemId" value"#{editItem.itemId}" />
                        </h:link>
                    </h:column>
                </h:dataTable>
            </fieldset>
            </h:panelGroup>

            <t:saveState value="#{listItem.listReply}" />
        </h:form>
    </ui:define>

    <ui:define name="filename">
        item\itemManagement\list_items.xhtml
    </ui:define>
</ui:composition>

Page1 bean:

@Named("listItem")
@RequestScoped
public class ListItemIdsBean extends AbstractBean {

    private BigInteger itemId;
    private String itemName;

    // dummy code, just to represent irrelevant business logic that returns a list of items
    private ExtAppListReply listReply;

    public BigInteger getItemId() {
        return itemId;
    }

    public void setItemId(BigInteger itemId) {
        this.itemId = itemId;
    }

    public String getItemName() {
        return itemName;
    }

    public void setItemName(String itemName) {
        this.itemName = itemName;
    }

    public ExtAppListReply getExtAppTransaction() {
        if( listReply == null ) {
            listReply = new ExtAppListReply();
        }
        return listReply;
    }

    public void setExtAppTransaction(ExtAppListReply trx) {
        this.trx = trx;
    }

    private void list(BigInteger itemId, String itemName) {

        ExtAppTransaction trx = ExtAppTransactionFeeder.feeder(this, itemId, itemName);
        ExtAppListReply listReply = super.executeTransaction(trx);

        if( listReply != null && !listReply.isSetRefusalCode() ) {
            setExtAppTransaction(listReply);
        } else {
            if( !(type == BACKWARD || type == FORWARD)  ) {
                setExtAppTransaction(null);
            }
        }

        // show results in the same page
        return null;

    }

}
b13
  • 1
  • 2
  • 1
    You should edit your question and try to add a [mcve] and copy&paste error messages into the question. I don't know "the missing cache message" for example. – Selaron Jan 13 '20 at 07:20
  • 2
    It all boils down to just stop using POST for page-to-page navigation. Technically speaking, this is your duplicate: https://stackoverflow.com/q/8459903 – BalusC Jan 13 '20 at 09:19
  • @Selaron this application is huge and it's hard to provide a clear example from it, but if still needed I'll get my hands into it! – b13 Jan 13 '20 at 15:37
  • @BalusC you were right, I got it working on page2 by passing a request parameter from page1, I can now go from page2 to anywhere, hit the back button and GET page2 again. But in page1 I'm still having problems because all data is lost when I submit the form: navigating with POST, page1 is refreshed and presents the results. With GET (via redirect to same page) the form data and results are gone. – b13 Jan 13 '20 at 16:19
  • Updated with example code – b13 Jan 13 '20 at 16:20
  • I screwed up while testing some changes and forgot I had previously changed the commandButton to button in page1, in the meantime I discarded all changes in this bean and that's why you still see the commandButton in there. – b13 Jan 13 '20 at 17:04
  • But with I have 2 problems: the itemId value is not being passed to the bean when I hit the button and even if it did (I tried setting the value while debugging), when I go to page2 and then come back the form is cleared and I get no results. But at least the [form resubmission message](https://archive.org/download/Confirm-Form-Resubmission/Confirm-Form-Resubmission.png) is gone. – b13 Jan 13 '20 at 17:05
  • 1
    Use ajax requests instead of full requests. Add `` to command button. – BalusC Jan 13 '20 at 18:09
  • I did it and its action is performed, however the panelGroup containing the results is not rendered. Since it already has a rendered condition (which is false during page load), I moved the rendered condition to but nothing changed... I've updated the example with this modifications. – b13 Jan 14 '20 at 16:14
  • See https://stackoverflow.com/questions/13777678/ajax-render-attribute-doesnt-work-with-rendered-false-component#13779025 – Kukeltje Jan 14 '20 at 17:12
  • @Kukeltje, I was not using that ajax tag correctly, I noticed it after seeing your link. I also added execute="@form" in order to set the itemId value, otherwise it remains null. Now I have page1 working properly (when I press Search I get the results based on the inputTexts) but when navigating to page2 and then hit Back, I get an empty form and result list. Page-to-page navigation is always made through GETs. – b13 Jan 15 '20 at 17:40
  • And after searching a little more, I guess this is as best as it gets since the beans are RequestScoped, am I wrong? I've been avoinding SessionScope because we have many pages with logs of data and I'm afraid this could be an issue later, in terms of memory usage. I thought about using a vert low session-timeout value to keep the session small but the app already has a ViewScoped bean, to keep user information (favourites, language etc) and this must remain as it is. ConversationScope maybe is an option here? – b13 Jan 15 '20 at 18:06
  • 1
    And between `@RequestScoped` and `@SessionScoped` there is `@ViewScoped` and with DeltaSpike (CDI extension) there is a `@ViewAccessScope` which is between The Session and View and easier to use as `@ConversationScope` So if you can really make a [mcve], I'm more than happy to help investigating since I'm thingking of doing this step myself too. Navigting via url params to page one is I think what you still need. – Kukeltje Jan 15 '20 at 18:31
  • See: https://stackoverflow.com/questions/38997418/navigate-to-other-view-passing-a-parameter No need for hidden fields – Kukeltje Jan 15 '20 at 18:32
  • I thought @ViewScoped wouldn't work since the view would die after navigating to another view (page1 -> page2), right? I still tried it, but I keep getting an empty form when returning to page1. I tried with PARTIAL_SAVE_SAVING = false and I have no or in my html. – b13 Jan 16 '20 at 09:49
  • Also, as a side note, when navigating to page2 the URL now shows the view param (in this case, page2.jsg?itemCode=123), considering I'm dealing with sensitive data this is not desired at all. But I think I could avoid this by storing the parameter to another object like a List and passing the list index as a parameter. – b13 Jan 16 '20 at 10:13
  • update: I finally got it, using ConversationScope and passing the conversation id from page1 to page2 as "cid" parameter, the same way itemId is being passed. Now I'll try to mask the parameters in the URL, since it contains sensitive data. – b13 Jan 17 '20 at 11:26

0 Answers0