Refresh parent window from child window – conditional reloading with javascript and AJAX

If you work with popups, there will be situations where you need the parent window to update based on some changes/submitted values in the popup window. I will show a solution that is done for myfaces/jsf, and it’s based on the ‘onload’ event of the popup window. I also use ajax (a4j implementation) to update the parent window to avoid a submit and full screen refresh there.

The basic conceptual thoughts about this are quite easy: When you submit a form – or do other actions in your popup that sends data to the server, you need to update the parent window. It could be that you in the popup for example added an item to a table that are displayed in your parent window. So, when you click your button or link in the popup that fires of an action or submit of some sort, you also trigger a javascript on the onclick event that retrieves a hidden button in your parent window and cliks it. This would cause a submit /refresh on you parent window, and the new data that you added from the popup is diplsayed.

Popup.jsp: The popup form with submit button and onclick event:


<h:form id="myPopupForm">
                <t:commandButton id="mySubmit" action="#{myHandler.myAction}" onclick="updateParentWindow();" value="#{messages.myMessage}" >
                </t:commandButton>
</h:form>

Parent.jsp: The parent window hidden button that submits the form or in other ways (onclick event) refreshes that page

<h:form id="updateParentForm" >
        <t:commandButton style="display: none; visibility: hidden;" id="updateParentButton" action="#{myHandler.myAction}"></t:commandButton>
 </h:form>

The script that finds the parent button and clicks it

function updateParentWindow()
{
    var elementToGet = "updateParentForm"+ ":" + "updateParentButton";

    var form = document.forms['updateParentForm'];

    var button = form.elements[elementToGet];

    button.click();
}

But then, if we do it like this – what are the guarantee that the action we fired off in the popup is finished before the parent view is refreshed? None. If we do a submit in the popup, that page will reload. During reloading there is an onload-event that is called, and the popup is not reloaded until the action we fired off has finished. In other words, putting the function inside the onload-event is safe, it will never refresh the parent window before the submit you did is completed. So, let’s move the call of the updateParentWindow()-function from the button to the onload-event of the popup-page:Popup.jsp

<f:verbatim>
    <body onload="updateParentWindow();">
</f:verbatim>

If you now try your application you should se that if you submit the popup, as soon as it has reloaded, your parent window will refresh as well. Nice! This is one big step in the right direction. But there are still some issues to deal with. If I open my popup-window for the first time, I don’t want my parent window to refresh, cause I haven’t done any changes to the data in my popup yet. Having the updateParentWindow() call in the onload event as it is in the example abowe would cause the parent window to be refreshed also the first time the window opens.We want to avoid this, therefore we add some knowledge that the onload event should only trigger the function if the popup page was reloaded by some clicks/submit in the popup-page itself. What we do is to add a boolean value in javascript somewhere inside the body (not the head) of the parent page. It defaults to false, but the links/buttons in the popup will call a small function that set’s this property to true. Then we do a conditional test in the onload event, and only continue if this value is true.The javascript boolean inside the body of the parent page:Parent.jsp

<f:verbatim>
    <script type="text/javascript">
        var fromPopup = false;
    </script>
</f:verbatim>

Our links and buttons in the popup will on the onclick-event set this property to true:Popup.jsp


<h:commandLink onclick="window.opener.fromPopup = true;" action="#{myHandler.myAction}">
        <h:outputText value="myAction"></h:outputText>
</h:commandLink>

And then the conditional reloading would be like this:


<f:verbatim>
    <body onload="if(window.opener.fromPopup)updateParentWindow(); window.opener.fromLink = false;">
</f:verbatim>

It is important to notice that we have to reset the boolean window.opener.fromLink whenever the popup reloads, but we need to do it AFTER the test.

If you are pleased by this solution, no further steps are necessary! But in my application I display a lot of data at the same time, and what I updated in my popup is only a small part of the view in the parent window. Therefore I only wanted to refresh that part of the page, not do a full refresh/reload of my parent window.To do this I use the a4j:commandButton, which has ajax-capabilities and gives me the possibility of rerendering only a part of the page. If you remember we had a hidden button in our parent window that when clicked was responsible for refreshing that page. We modify it a bit, and create an a4j:commandButton instead, and add the reRender property to update the view:Parent.jsp

<a4j:form id="updateParentForm" >
		<a4j:commandButton style="display: none;" id="updateParentButton"  reRender="myComponentOutsideAForm" ></a4j:commandButton>
</a4j:form>

Notice that the area/component I decide to rerender is given by it’s id. Rerendering a component inside a form will fail in Firefox, you will get an error message in the console saying “f has no properties,” and all cliks on links from this point would not work. So either you would have to go for the ordinary submit/refresh, or you can only update areas of the page that doesn’t contain forms. So, applying this code, if you now trigger your submit in the popup page you will se that when it reloads it updates the parent window, but only the part you specified in the ajax-function we created. Avoiding a full refresh gives a better user experience as well as it saves you from retrieving data from the server more than necessary.

Then, what about form validation? Whether you use application validation (business-logic validation in your action methods), or you use the builtin jsf conversion/validation options, or both, it should be possible to handle this as well. If we click a link or button, but the validation fails, we don’t want to update the parent window. This can be done by adding a boolean on the top of our popup.jsp, as well as using this boolean in our updateParentWindow function. Whenever we do an action from the popup, this boolean must be set. If it is business logic validation, it would be easy to set a property if not all requirements are met. If you use the builtin conversion/validation in jsf, you can use the FacesContext.getMessages() to do this. In the popup.jsp, everytime it loads, we get the boolean from our backing bean and pass it on to the javascript. This would avoid the parentWindow to be updated if validation fails.

First, modify the script we use to update parent to respect a boolean parameter:

function updateParentWindow(validationFailed)
{
    if(validationFailed)
	return;
    else
   {
    var elementToGet = "updateParentForm"+ ":" + "updateParentButton";

    var form = document.forms['updateParentForm'];

    var button = form.elements[elementToGet];

    button.click();
   }
}

In the handler we need a boolean property that we can set depending on whether the validation failed or not. We create a private method that will help us to set this boolean, the important thing is that we call it from all our action methods. So the handler might look something like this:

public class MyHandler
{
    private boolean validationFailed = false;

    //Action
    public void doSomething()
    {
        ...
        Some code/business logic
        ...

        If some business logic/validation is not correct, pass this on to the validation method
            setValidation(true);
        If business logic/validation is correct
            setValidation(false);

    }

    private void setValidation(boolean applicationValidationFailed)
    {
        FacesContext context = FacesContext.getCurrentInstance();
        Iterator<FacesMessage> messages = context.getMessages();
        if(messages.hasNext() || applicationValidationFailed)
            setValidationFailed(true);
        else
            setValidationFailed(false);
    }
}

Then on top of our popup jsp inside <% %> tags we retrieve the handler, and we store the boolean property inside a local variable:

<%

MyHandler myHandler = (MyHandler)FacesContext.getCurrentInstance().getApplication ().
getVariableResolver().resolveVariable(FacesContext.getCurrentInstance(),"myHandler");
boolean validationFailed = myHandler.isValidationFailed();
%>

And then we also modify the onload of this page to pass the boolean to the javascript:


<f:verbatim>
    <body onload="if(window.opener.fromPopup)updateParentWindow(<%= validationFailed %>); window.opener.fromLink = false;">
</f:verbatim>

Done! Your popup-page should also respect the validation now, and not update the parent if it fails.

I hope that this example will be helpful, and if not completely adaptable to your requirements it might lead you into the right path. I end this post by showing the popup.jsp and the parent.jsp in whole, to make it easier for you to see what I’ve tried to explain:Parent.jsp

<f:view  >
    <t:document>
    <t:documentHead>
    ...
    </t:documentHead>

    <f:verbatim>
    <body>
    </f:verbatim>

  <f:verbatim>
    <script type="text/javascript">
    //Used in popup
    var fromLink = false;
    </script>
    </f:verbatim>            

    Some content
    ...

    ...

    <h:panelGrid id="myComponentOutsideAForm">
        THIS AREA/CONTENT WILL BE REFRESHED
    </h:panelGrid>

    <a4j:form id="updateParentForm" >
        <a4j:commandButton style="display: none;" id="updateParentButton"  reRender="myComponentOutsideAForm" ></a4j:commandButton>
   </a4j:form>  

<f:verbatim>
    </body>
</f:verbatim>

</t:document>

</f:view>

Popup.jsp

<f:view  >
    <t:document>
    <t:documentHead>
    ...
    </t:documentHead>
<%

MyHandler myHandler = (MyHandler)FacesContext.getCurrentInstance().getApplication ().
getVariableResolver().resolveVariable(FacesContext.getCurrentInstance(),"myHandler");
boolean validationFailed = myHandler.isValidationFailed();
%>
<f:verbatim>
<body onload="if(window.opener.fromPopup)updateParentWindow(<%= validationFailed %>); window.opener.fromLink = false;">

</f:verbatim>

    Some content
    ....

    ....

   <h:commandLink onclick="window.opener.fromPopup = true;" action="#{myHandler.myAction}">
    <h:outputText value="myAction"></h:outputText>
   </h:commandLink>  

<f:verbatim>
</body>
</f:verbatim>

</t:document>
</f:view>

Advertisements