Sunday, March 27, 2011

Open ISD Pages in Popup Windows: A Comprehensive Guide

Introduction

It is sometimes necessary to open a page in a popup window, for example, a printable version of an invoice page. ISD does this itself on large-list-selector (LLS) and quick-add pages. As a developer, when you make a page to be opened in a popup window (instead of redirecting in the main window), you explicitly or implicitly make the following decisions:
  • Where do you open the popup? Client-side or server-side?
  • How do you pass parameters from the main window to the popup?
  • How do you send information back to the main window from the popup?
Different scenarios need different combination of the decisions. This guide will help you make informed decisions.

Note: A theme button named "PopButton" will be used as the trigger to open the popup throughout the guide.

Primer: window.open()

Before we examine the above three decisions, let's first review window.open, for those who are not familiar with the JavaScript function.

No matter where and how you open a popup, you need to use the JavaScript function:
window.open(url, windowName, windowFeatures)
Theoretically, all three parameters are optional. In practice, however, the first parameter url is usually provided. It is meaningless to open a blank popup without an url, unless you are trying to annoy your users.

The second parameter windowName is optional. It assigns the name to the new popup window. If there is already an open window with that name, the function will not popup a new window. In stead, it just opens the url in the existing window. You can use this parameter to control multiple pages poping up in a single window or multiple windows.

The third optional parameter, windowFeatures, controls the popup's features, such as size, position, status bar, address bar, scroll bar, toolbar, etc.

With the basics out of the way, let us get back to the first decision, whether to open the popup at client-side or server-side.

Client- vs. server-side popup

To popup at client-side, call window.open() in PopButton's client-side OnClick event handler. This can be done within ISD through PopButton's Custom properties. For example,
PropertyValue
Button-HtmlAttributes-OnClickwindow.open('MyPage.aspx'); return false;
Button-OnClientClick
The two properties are synonymous, so they can be used interchangeably. Don't forget "return false" statement after window.open. This is to prevent the button from triggering postback.

Alternatively, it can be setup in code, especially if you need to formulate the URL at run-time. For example,
string url = ...; // Formulate url
PopButton.Button.OnClientClick = 
  string.Format("window.open('{0}'); return false;", url);
Or
string url = ...; // Formulate url
PopButton.Button.Attributes["OnClick"] = 
  string.Format("window.open('{0}'); return false;", url);

To popup at server-side, register window.open() in PopButton's server-side OnClick event handler. For example,
public override void PopButton_OnClick(object sender, EventArgs e){
  string url = ...; // Formulate url
  string script = string.Format("window.open('{0}');",, url);
  ScriptManager.RegisterStartupScript(Page, GetType(), "OpenWindow", script, true);
}

From coding point of view, there is little difference between client- and server-side popup. From efficiency and performance point of view, however, there are huge differences. Client-side popup does not pose any overhead on the server and the network; and you get instant response. On the other hand, server-side popup triggers a round trip between the server and the client; and the server undergoes a full page life cycle. Therefore, client-side popup is almost always favored over server-side popup.

Send information to popup window

More often than not, you need to pass some information from the main window to the popup. For example, the invoice number to be printed, or the lookup table name in an LLS. There are two ways to pass the information, URL parameters and session variables. URL parameters are suitable for small number of primitive values, such as strings, integers, etc. For complex data (e.g. arrays, lists, records, etc.), session variables work better. Session variable also has the advantage to avoid sensitive information to be seen in address bar.

Send information back to main window

It is not always necessary to send information back to the main window. For example, after a user prints his invoice in a popup, he simply close the popup and continue shopping in the main window. If you do need to send information back to the main window, there are again two ways to do that, from client-side or server-side.

ISD's built-in LLS is a typical example for the client-side mechanism. When a value is selected within the LLS, it is sent back to a Textbox or DropdownList in the main window via client-side JavaScript. The key in the communication is the receiving control's ClientID. It is sent to the popup window as a URL parameter. The popup uses the ID to find the control in the main window and manipulate its value. Below is the built-in JavaScript function (in ApplicationWebForm.js) to update a control in the main window.
function updateTarget(targetName, selectedValue, selectedDisplayValue){
    if (window.opener && !window.opener.closed) {
        var target = window.opener.document.getElementById(targetName);
        
        var bSuccess = false;
        if (!bSuccess){
            if (Fev_ReplaceLastListControlOption(target, selectedValue, selectedDisplayValue) ){
                //try setting the selection again
                bSuccess = Fev_SetFormControlValue(target, selectedValue);
            }
        }
        
        if (bSuccess){
            if(target != null) {
                if (navigator.appName == "Netscape") {
                    var myevent = document.createEvent("HTMLEvents")
                    myevent.initEvent("change", true, true)
                    target.dispatchEvent(myevent);
                }
                else { // IE
                    target.fireEvent('onchange');
                }
            }
        }           

        window.close();
    }
}

Here is the scenario you may want to try the server-side mechanism. On a typical ShowTable page, there is an New ImageButton, which redirects to an AddRecord page. Instead of redirect, you make the New button to open the AddRecord page in a popup. When a new record is saved in the popup and the popup is closed, you would like the main page refreshed to show the newly added record. This is not an issue in the redirect scenario. After redirecting back from the AddRecord page, the ShowTable page is automatically refreshed. In the popup scenario, however, the main window won't automatically refresh when the popup is closed. You have to trigger a postback in the main window from the popup. Below are the steps to implement this mechanism.

Step 1: Set New button's OnClientClick to open popup

In ShowTable page's TableControl DataBind() method, insert the following 2 lines of code:
string url = "AddRecord.aspx?TargetID=" + NewButton.Button.UniqueID; // Formulate url
NewButton.Button.OnClientClick = 
  string.Format("window.open('{0}'); return false;", url);
Applying the above-mentioned decision making process, it is a no-brainer to popup at client-side and pass URL parameters. The AddRecord page itself does not need any parameters. We pass the NewButton's UniqueID so that the popup can use it to trigger postback. Please note it is the UniqueID that triggers server-side postback, not the ClientID for client-side manipulation.

Step 2: Change New button's action from redirect to refresh

Now the New button's server-side function has been changed. We no longer need it to redirect to AddRecord page. We need it to refresh the current page. This can be easily configured in ISD. Please don't forget to change "Redirect and Send Email Actions" to "Stay on the current page."

Step 3: Set popup's Save button to trigger postback in main window

Insert the following lines into AddRecord Save button's Click handler:
public void SaveButton_Click(object sender, EventArgs args) {

 // Click handler for SaveButton.
 // Customize by adding code before the call or replace the call to the Base function with your own code.
 SaveButton_Click_Base(sender, args);
 // NOTE: If the Base function redirects to another page, any code here will not be executed.

 if (!ErrorOnPage) {
  string script = string.Format("window.opener.__doPostBack('{0}', '');window.close();", Request.QueryString["TargetID"]);
  ScriptManager.RegisterStartupScript(Page, GetType(), "TriggerPostback", script, true);
 }
}
Please don't forget to change Save button's "Redirect and Send Email Actions" to "Stay on the current page." Otherwise, the inserted code won't get executed as commented in the method.

Conclusion

Using popups discretionally can improve your web applications' workflow. The tips in this post may help you decide whether to popup or how to popup.

2 comments:

steamrally.cw said...

I have inherited an ironpeed project that i am finishing useing only code - i have a problem where after opening a modal popup from an ironspeed parent page, i select what i want from my popup and send it back to the ironspeed parent page and auto close popup. this works fine - however, when I click the ironspeed form Save or cancel button, instaed of saving or cancelling it takes me to the url of my modal popup - how can i stop this?

thanks

craig
steamrally.cw@gmail.com

Jing said...

In popup page class, override UpdateSessionNavigationHistory() method. Comment out base.UpdateSessionNavigationHistory(). Do nothing in the mothod.