The problem
You have two pages: a
main form and a
sub-form. Users start with the main form, and are taken to the sub-form to enter the value of a field. For instance, consider a registration form. On the main form users enter their personal information, such as first name and last name. But for a specific field, say the country, users are taken to a sub-page, which we'll also call
search page. Once they selected a country on the search page, they are taken back to the main form.
(Obviously, this is a vastly simplified use case as a drop-down allowing a country to be selected in the main form would be sufficient. You will only want to use a sub-form or search form in cases that are more complicated.)
From a technical perspective, this poses two problems:
- How to make sure that the data entered so far in the main form is somehow "kept around" while users navigate to the search form, so when they come back to the main form, the information they entered previously is restored.
- How to make the search page as independent as possible from the main page, so it can potentially be reused from different parts of the application.
The solution
Using the session is standard remedy to this type of problem: before going to the search page, save the data the user has entered so far in the session, and restore it from the session when the user comes back from the search page. This solution works but it has one big drawback: it breaks if users open two browser windows pointing to the same form, enter different data in each instance of the main form, and from there go to the search page in each window. If you are not careful, the "second" browser window that goes to the search page will overwrite what was there in the session, leading to unexpected results when the user comes back to the main form from the "first" browser window. You might thing that this scenario is fairly unlikely to happen in your particular application. But why take the risk, if a solution that doesn't pose this problem isn't any more complicated?
Before you proceed, you might want to watch the video on this page showing how the two pages interact, and open the full source for the two pages which is linked below under "get the source".
What you will do here is to POST the instance data of the main form to the search form. The trigger runs the go-to-search submission, defined as:
<xforms:submission id="go-to-search" ref="instance('registration')"
method="post" replace="all" resource="navigation-search"/>
The search page gets the XML posted by the main page, and can store it in an instance with:
<xforms:instance id="input" src="input:instance"/>
The search page doesn't need to know anything about that XML. But it needs to return it to the main page, in addition to the country selected by the user. The XML returned by the search page is stored in another instance:
<xforms:instance id="country">
<response>
<country/>
<!-- We'll insert here the instance we got in input -->
</response>
</xforms:instance>
In that
country instance, where you see that comment, the search page needs to insert the XML it gets in input. You do this with:
<xforms:insert ev:event="xforms-model-construct-done"
nodeset="instance('country')/*" origin="instance('input')"/>
The search page populates the content of the
<country> element, and returns that instance to the main page. Now the main page can also receive some XML POSTed, so it also defines an instance:
<xforms:instance id="input" src="input:instance"/>
Unlike the search page, which always receives XML from the main page, the main page receives XML only if the user is coming back from the search page. When that happens, you want to replace the current instance with the element inside the response, and populate the country element in that instance with the content of the country element inside the response:
<xforms:action ev:event="xforms-model-construct-done" if="exists(instance('input')/registration)">
<xforms:insert nodeset="instance('registration')" origin="instance('input')/registration"/>
<xforms:setvalue ref="instance('registration')/country" value="instance('input')/country"/>
</xforms:action>
With this, we came full circle: you're back on the main form, you restored the data that was passed to the search form, and you now know what country was selected by the user on the search form.
Get the source
A word of warning about pop-ups
A disclaimer: we don't like pop-ups.
And some of your users will feel the same way. Maybe more importantly, a lot of them will use a browser which blocks pop-ups (all major browsers now do by default, even Internet Explorer since Windows XP SP2 released in 2004). This means that they won't see your pop-up window until they authorize your side to display pop-ups. Some of them won't know how to do that, and once the finally manage to authorize pop-ups they might get confused, now having to deal with multiple browser windows.
Instead of using pop-ups, if the sub-form fits in a "smaller window", we recommend you use the
xxforms:dialog. This will also simplify your code, as the dialog lives in the same form and has access to the same instances as the other controls you have on your form.
Variation: using a pop-up
In the above code, the search page replaces the main page. If instead you want the search page to open in a new browser window, change the submission on the main page to:
<xforms:submission id="go-to-search"
ref="instance('registration')"
method="post" replace="all"
resource="navigation-search"
xxforms:target="_blank"
xxforms:show-progress="false"/>
The
xxforms:target="_blank" makes sure the search form is opened in a new window. The
xxforms:show-progress="false" is here so the "loading indicator" doesn't stay on the page after the new form is opened.
In the search form, you need to post back to the window that opened the search page. For this, you need to refer that page by name. To avoid any possible name conflict, and to allow users to follow parallel flows in multiple windows (opening two main pages at the same time, and from there opening two search pages), use a random name. Add a element
opener-name to the ui instance:
<xforms:instance id="ui">
<ui>
<select-trigger/>
<opener-name/>
</ui>
</xforms:instance>
Compute the random name, store it in the instance, and assign it to the opener window:
<xforms:action ev:event="xforms-model-construct-done">
<xforms:setvalue ref="instance('ui')/opener-name"
value="digest(string(random(true)), 'MD5', 'hex')"/>
<xxforms:script>opener.name = ORBEON.xforms.Document.getValue("opener-name");</xxforms:script>
</xforms:action>
In the trigger that runs the submission, close the current window with
window.close():
<xforms:trigger ref="instance('ui')/select-trigger" class="select-button">
<xforms:label>Select</xforms:label>
<xforms:action ev:event="DOMActivate">
<xxforms:script>window.close();</xxforms:script>
<xforms:send submission="go-to-main"/>
</xforms:action>
</xforms:trigger>
Finally, reference the opener name you stored earlier in the
xxforms:target attribute (the curly braces denote an AVT) to post back to the window that opened this form:
<xforms:submission id="go-to-main" ref="instance('country')" method="post" replace="all"
resource="navigation-main" xxforms:target="{instance('ui')/opener-name}"/>