Open a SharePoint Modal Dialog from an InfoPath Form: Part 5 of 5 (Vivek Soni)

We are developing an example application in five steps.  We completed the first step in Part 1, the second step in Part 2, the third step in Part 3, and the fourth step in Part 4. Today we complete the final step, “Connect the Components.” Then we will deploy the application and test it.

Here is the roadmap for the entire development process:

  1. Create a Search Application Page.   In this step, we design the user interface for the page that the modal dialog displays, and we write code to perform the search.
  2. Add JavaScript for the Modal Dialog.   In this step, we create a JavaScript file with code to open the search application page in a popup modal dialog. The function that opens the modal dialog specifies a callback function that returns information about the document that the user has selected to the page that opened the dialog.
  3. Design the InfoPath Form.   In this step, we create a form template that includes a Search button, and we write form code that notifies the form's host when a user clicks the button.
  4. Create a Web Part to Host the Form.   In this step, we write a custom Web Part that uses an XMLFormView control to host the InfoPath form.
  5. Connect the Components.   In this step, we bolt everything together. We write Web Part code that handles the NotifyHost event, code that invokes the modal dialog, and code that uses the dialog result to update the InfoPath form.

Step 5: Connect the Components

At this point we have successfully created a browser-enabled InfoPath form, a Web Part to host the form, a search application page to find a document, and a modal dialog to display the search page. The only task left is to bolt these components together.

We can start by writing code that makes the XMLFormView control respond to NotifyHost events raised by the form that it hosts. A NotifyHost event occurs when the form's Search button is clicked. We want the host to respond by popping up the modal dialog that displays the search application page. Then, after the dialog closes and the result of the user's search is posted back to the Web Part page, additional Web Part code should access the main data source for the InfoPath form and update the hyperlink field by setting its value to the URL of the document that the user has selected.

Handling the NotifyHost event

When we created the InfoPath form, we gave it a Search button, and we wrote a button click event handler that calls the XmlForm object's NotifyHost method. The NotifyHost method accepts a string argument, and we used this argument to pass the XPath for the form field that we want to update.

What we need to do now is to get the form's host ready to listen for NotifyHost events and respond by displaying a Search dialog. Under the hood, we also want to extract the XPath for the form field and put it aside for later use.

We will start by giving the XMLFormView control a NotifyHost event handler. In the handler, we want to do two things:

  • Extract the XPath from the Notification property of the NotifyHostEventArgs object and save it in the Web Part's view state.

  • Add JavaScript to the page so that the next time the page renders in the browser our modal dialog pops up.

   To add a NotifyHost event handler
  1. In Visual Studio, open the InfoPathFormHost.cs file in the ModalHost.WebParts project.

  2. Navigate to the CreateChildControls method. Immediately before the line of code that adds the XMLFormView control to the Web Part controls collection, copy and paste the following code:

     // Add a handler for the NotifyHost event.
    this.xmlFormView.NotifyHost += new EventHandler<NotifyHostEventArgs>(xmlFormView_NotifyHost);
    
  3. Just below the CreateChildControls method, define the xmlFormView_NotifyHost method by copying and pasting the following code:

     // Handles the event that fires when the button 
    // in the InfoPath form is clicked.
    void xmlFormView_NotifyHost(object sender, NotifyHostEventArgs e)
    {
        try
        {
            // Check if the argument contains the xpath.
            if (!string.IsNullOrEmpty(e.Notification))
            {
                // Save the InfoPath field xpath in the view state so it can be used later.
                ViewState["fieldXPath"] = e.Notification;            // Construct a JavaScript function to invoke the modal dialog.
                StringBuilder functionSyntax = new StringBuilder();
                functionSyntax.AppendLine("function popupparams() {");
                // Pass the current SharePoint web url as an argument.
                functionSyntax.AppendLine("var url ='" + SPContext.Current.Web.Url + "';");
                // Call the JavaScript function to popup the modal dialog.
                functionSyntax.AppendLine("popupmodalui(url);}");
                //Ensure the function popupparams is called after the UI is finished loading.
                functionSyntax.AppendLine("_spBodyOnLoadFunctionNames.push('popupparams');");            //Register the script on the page.
                Page.ClientScript.RegisterClientScriptBlock(typeof(Page), "ModalHostScript", functionSyntax.ToString(), true);
            }
        }
        catch
        {
            throw;
        }
    }
    

In the event handler we create and register a script block that defines the JavaScript function popupparams. This function calls the popupmodalui function that we defined Step 2 and passes in the URL for the current page. Then the event handler ensures that popupparams executes when the page body loads by adding it to the array _spBodyOnLoadFunctionNames.

Note: _spBodyOnLoadFunctionNames is a global variable that is defined in INIT.debug.js as an array of functions that are registered when a SharePoint page loads.  You cannot directly add a function to the onload event for the body of the page because content pages in SharePoint are based on a master page that contains the “body” element.  In order to work around this limitation, SharePoint provides indirect access to the onload event through the “_spBodyOnLoadFunctionNames” array. When the body of the page is loaded, the onload event handler executes each function whose name is contained in this array.

To finish wiring up the event handler, we need to register ModalHost.js on the page. ModalHost.js calls into the JQuery library, so we must register that as well. A good place to do the registration is in the handler for the Web Part's OnLoad event.

To register the script files

In the InfoPathFormHost class, just above the CreateChildControls method, paste the following code that overrides the OnLoad event handler:

 protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    //Register scripts to load and display the modal dialog.
    ScriptLink.Register(this.Page, "ModalHost.ApplicationPages/scripts/jquery-1.5.min.js", false);
    ScriptLink.Register(this.Page, "ModalHost.ApplicationPages/scripts/ModalHost.js", false);
}
Updating the Field in the InfoPath Form

The final task is to set the value of the URL field in the form to the URL of the document that the user selects before closing the modal Search dialog.

It may help if we first review the event flow:

  • A user clicks the Search button on the InfoPath form, raising the NotifyHost event on the host Web Part.
  • Code for the Web Part responds to the event by storing the XPath for the form field in the view state and then invokes JavaScript that cause the modal dialog to pop up.
  • The modal dialog displays an application page with an interface that allows the user to search for and select a document.
  • When the user selects a document, logic in the code behind the search application page places the URL for the selected document in a hidden text box on the application page and enables the dialog's OK button.
  • The user clicks OK. The handler for the button click event (ModalOk_click in ModalHost.js) extracts the URL from the hidden text box and passes it as an argument in a call to SP.UI.ModalDialog.commonModalDialogClose.
  • The commonModalDialogClose method closes the dialog and passes the URL along to the dialog's callback function (closecallback in ModalHost.js).
  • The callback function stores the URL in a hidden text box on the Web Part.

Therefore, to complete the final task we need code that retrieves the URL from the hidden text box on the Web Part, retrieves the XPath for the field from the view state, and uses both pieces of information to update the value of the field. If the user exits the modal dialog by clicking the OK button, the callback function forces a postback that refreshes the page that hosts the form. Therefore, a good place to put code that updates the form is in the Web Part's OnPreRender method.

   To add code that updates the URL field
  1. In Solution Explorer, right-click the node for the ModalHost.WebParts project. Then click Add Reference.

  2. In the Add Reference dialog, browse to :\Program Files\Microsoft Office Servers\14.0\Bin\Microsoft.Office.InfoPath.dll. Then click OK.

    If Visual Studio prompts a warning message, click Yes and continue adding the reference to the project.

  3. Open InfoPathFormHost.cs and add the following using statements at the top of the file:

     using System.Xml;
    using System.Xml.XPath;
    
  4. Navigate to the OnPreRender method. Add the following code immediately after the line that sets the XMLFormView control's EditingStatus property:

     // Retrieve the return value from the modal dialog.
    this.receivedValue = this.hiddenText.Text;
    // Check if the received value is not empty.
    if (!string.IsNullOrEmpty(this.receivedValue))
    {
        //Update the form datasource with the new value.
        this.UpdateFormMainDataSource();
    }
    
  5. Define the UpdateFormMainDataSource method by pasting the following code after the OnPreRender method:

     // Updates the form's datasource with the received value.
    private void UpdateFormMainDataSource()
    {
        if (ViewState["fieldXPath"] != null && ViewState["fieldXPath"].ToString() != string.Empty && !string.IsNullOrEmpty(this.receivedValue))
        {
            // Ensure the XMlFormView has access to the form's underlying datasource.
            this.xmlFormView.DataBind();
            // Retrieve values from the ; separated string.
            int index = this.receivedValue.IndexOf(";");
            if (index != -1)
            {
                // Get the document url.
                string url = this.receivedValue.Substring(0, index);
                // Get the document name.
                string text = this.receivedValue.Substring(index + 1);
                // Pass the target InfoPath field xpath and the received values.
                this.SetFormFieldValue(ViewState["fieldXPath"].ToString(), url, text);
            }
        }
    }
    
  6. Define the SetFormFieldValue method by pasting the following code after the UpdateFormMainDataSource method:

     // Sets the target InfoPath form field value with the received value.
    private void SetFormFieldValue(string xpath, string url, string value)
    {
        // Create an XPathNavigator positioned at the root of the form's main data source.
        XPathNavigator xNavMain = this.xmlFormView.XmlForm.MainDataSource.CreateNavigator();
        // Create an XmlNamespaceManager.
        XmlNamespaceManager xNameSpace = new XmlNamespaceManager(new NameTable());
    
        // Add the "my" namespace alias from the form's main data source.
        // Note: Replace the second argument with the correct namespace from the form template that you are using.
        xNameSpace.AddNamespace("my", "https://schemas.microsoft.com/office/infopath/2003/myXSD/2011-03-29T15:01:42");
    
        // Create an XPathNavigator positioned on the target form field.
        XPathNavigator formfield = xNavMain.SelectSingleNode(xpath, xNameSpace);
        // Set the form's hyperlink field to the received document url.
        formfield.SetValue(url);
        if (formfield.HasAttributes)
        {
            // Set the hyperlink's display text with document title.
            formfield.MoveToFirstAttribute();
            formfield.SetValue(value);
        }
    }
    
  7. Replace the second argument passed to the AddNamespace method with the correct namespace for your form template. To get the correct value, open the form template in Design mode, click Show Fields on the ribbon to display the Fields task pane, right-click myFields, and then click Details.

  8. Press F5 to build and deploy the solution.

This concludes the final phase of application development.

Testing the Application

Now it is time to be sure that everything works as expected.

   To test the application
  1. Navigate to your SharePoint website in your browser, and open the Web Part page where you previously added the InfoPathFormHost Web Part.

    The form should display.

    image22

  2. On the Contoso Resource Planning form, click Search.

    A modal dialog should open. The OK button should be disabled.

    image23

  3. Type a search term in the text box, and then click the magnifying glass.

    The names of any matching documents should fill the results grid.

    image24

  4. Select a document by clicking Select.

    The row containing the selected document should be bold, and the OK button should be enabled.

    image25

  5. Click OK.

    The dialog should close, and a link to the selected document should appear in the URL field of the form.

    image26